Minor tweaks (#8145)
Some checks failed
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Documentation / Build and Deploy Documentation (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
Deploy website / Build & deploy website (push) Has been cancelled

This commit is contained in:
Elian Doran 2025-12-24 09:42:29 +02:00 committed by GitHub
commit d6d75a0e48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 487 additions and 438 deletions

View File

@ -46,17 +46,7 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`);
}.call(apiContext);
} catch (e: any) {
const note = await froca.getNote(bundle.noteId);
toastService.showPersistent({
id: `custom-script-failure-${note?.noteId}`,
title: t("toast.bundle-error.title"),
icon: "bx bx-error-circle",
message: t("toast.bundle-error.message", {
id: note?.noteId,
title: note?.title,
message: e.message
})
});
showErrorForScriptNote(bundle.noteId, t("toast.bundle-error.message", { message: e.message }));
logError("Widget initialization failed: ", e);
}
}
@ -151,17 +141,7 @@ async function getWidgetBundlesByParent() {
}
} catch (e: any) {
const noteId = bundle.noteId;
const note = await froca.getNote(noteId);
toastService.showPersistent({
id: `custom-script-failure-${noteId}`,
title: t("toast.bundle-error.title"),
icon: "bx bx-error-circle",
message: t("toast.bundle-error.message", {
id: noteId,
title: note?.title,
message: e.message
})
});
showErrorForScriptNote(noteId, t("toast.bundle-error.message", { message: e.message }));
logError("Widget initialization failed: ", e);
continue;

View File

@ -69,7 +69,7 @@ export async function showErrorForScriptNote(noteId: string, message: string) {
showPersistent({
id: `custom-widget-failure-${noteId}`,
title: note?.title ?? "",
title: t("toast.scripting-error", { title: note?.title ?? "" }),
icon: note?.getIcon() ?? "bx bx-error-circle",
message,
timeout: 15_000,

View File

@ -21,7 +21,7 @@
},
"bundle-error": {
"title": "Failed to load a custom script",
"message": "Script from note with ID \"{{id}}\", titled \"{{title}}\" could not be executed due to:\n\n{{message}}"
"message": "Script could not be executed due to:\n\n{{message}}"
},
"widget-list-error": {
"title": "Failed to obtain the list of widgets from the server"
@ -30,7 +30,8 @@
"title": "Failed to render a custom React widget"
},
"widget-missing-parent": "Custom widget does not have mandatory '{{property}}' property defined.",
"open-script-note": "Open script note"
"open-script-note": "Open script note",
"scripting-error": "Custom script error: {{title}}"
},
"add_link": {
"add_link": "Add link",
@ -1779,7 +1780,8 @@
"note_type_switcher_others": "Other note type",
"note_type_switcher_templates": "Template",
"note_type_switcher_collection": "Collection",
"edited_notes": "Edited notes"
"edited_notes": "Notes edited on this day",
"promoted_attributes": "Promoted attributes"
},
"search_result": {
"no_notes_found": "No notes have been found for given search parameters.",

View File

@ -15,7 +15,7 @@ export default function TabHistoryNavigationButtons() {
const legacyBackVisible = useLauncherVisibility("_lbBackInHistory");
const legacyForwardVisible = useLauncherVisibility("_lbForwardInHistory");
return (isElectron() &&
return (
<div className="tab-history-navigation-buttons">
{!legacyBackVisible && <ActionButton
icon="bx bx-left-arrow-alt"

View File

@ -54,6 +54,16 @@
display: flex;
gap: 1em;
justify-content: space-between;
.btn {
color: var(--bs-toast-color);
background: var(--modal-control-button-background);
&:hover {
background: var(--modal-control-button-hover-background);
color: var(--bs-toast-color);
}
}
}
.toast-progress {

View File

@ -4,7 +4,14 @@
position: relative;
}
.note-split.type-code:not(.mime-text-x-sqlite) > .scrolling-container {
background-color: var(--code-background-color);
--scrollbar-background-color: var(--main-background-color);
.note-split.type-code:not(.mime-text-x-sqlite) {
&> .scrolling-container {
background-color: var(--code-background-color);
--scrollbar-background-color: var(--main-background-color);
}
.inline-title,
.title-actions {
background-color: var(--main-background-color);
}
}

View File

@ -32,7 +32,7 @@ import { ParentComponent } from "../react/react_utils";
const COLLAPSE_THRESHOLD = 5;
const INITIAL_ITEMS = 2;
const FINAL_ITEMS = 2;
const FINAL_ITEMS = 3;
export default function Breadcrumb() {
const { note, notePath, notePaths, noteContext } = useNotePaths();
@ -65,8 +65,7 @@ export default function Breadcrumb() {
? <BreadcrumbRoot noteContext={noteContext} />
: <BreadcrumbItem index={index} notePath={item} notePathLength={notePaths.length} noteContext={noteContext} parentComponent={parentComponent} />
}
{(index < notePaths.length - 1 || note?.hasChildren()) &&
<BreadcrumbSeparator notePath={item} activeNotePath={notePaths[index + 1]} {...separatorProps} />}
<BreadcrumbSeparator notePath={item} activeNotePath={notePaths[index + 1]} {...separatorProps} />
</Fragment>
))
)}
@ -226,7 +225,7 @@ function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNoteP
</li>;
})}
<FormDropdownDivider />
{childNotes.length > 0 && <FormDropdownDivider />}
<FormListItem
icon="bx bx-plus"
onClick={() => note_create.createNote(notePath, { activate: true })}

View File

@ -75,10 +75,6 @@
}
}
.note-split.type-code:not(.mime-text-x-sqlite) .inline-title {
background-color: var(--main-background-color);
}
body.prefers-centered-content .inline-title {
margin-inline: auto;
}
@ -99,58 +95,3 @@ body.prefers-centered-content .inline-title {
font-weight: 500;
}
}
@keyframes note-type-switcher-intro {
from {
opacity: 0;
} to {
opacity: 1;
}
}
.note-type-switcher {
--badge-radius: 12px;
position: relative;
top: 5px;
padding: .25em 0;
display: flex;
align-items: center;
overflow-x: auto;
min-width: 0;
gap: 5px;
min-height: 35px;
>* {
flex-shrink: 0;
animation: note-type-switcher-intro 200ms ease-in;
}
.ext-badge {
--color: var(--input-background-color);
color: var(--main-text-color);
font-size: 0.9rem;
flex-shrink: 0;
}
}
.edited-notes {
padding: 1.5em 0;
.collapsible-inner-body {
display: flex;
flex-wrap: wrap;
gap: 0.3em;
.badge {
margin: 0;
a.tn-link {
color: inherit;
text-transform: none;
text-decoration: none;
display: inline-block;
}
}
}
}

View File

@ -3,28 +3,16 @@ import "./InlineTitle.css";
import { NoteType } from "@triliumnext/commons";
import clsx from "clsx";
import { ComponentChild } from "preact";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { useLayoutEffect, useRef, useState } from "preact/hooks";
import { Trans } from "react-i18next";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import { ViewScope } from "../../services/link";
import { NOTE_TYPES, NoteTypeMapping } from "../../services/note_types";
import server from "../../services/server";
import { formatDateTime } from "../../utils/formatters";
import NoteIcon from "../note_icon";
import NoteTitleWidget from "../note_title";
import SimpleBadge, { Badge, BadgeWithDropdown } from "../react/Badge";
import Collapsible from "../react/Collapsible";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useNoteBlob, useNoteContext, useNoteLabel, useNoteProperty, useStaticTooltip, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
import NoteLink from "../react/NoteLink";
import { useNoteContext, useNoteProperty, useStaticTooltip } from "../react/hooks";
import { joinElements } from "../react/react_utils";
import { useEditedNotes } from "../ribbon/EditedNotesTab";
import { useNoteMetadata } from "../ribbon/NoteInfoTab";
import { onWheelHorizontalScroll } from "../widget_utils";
const supportedNoteTypes = new Set<NoteType>([
"text", "code"
@ -76,9 +64,6 @@ export default function InlineTitle() {
<NoteTitleDetails />
</div>
</div>
<EditedNotes />
<NoteTypeSwitcher />
</div>
);
}
@ -142,205 +127,4 @@ function TextWithValue({ i18nKey, value, valueTooltip }: {
}
//#endregion
//#region Note type switcher
const SWITCHER_PINNED_NOTE_TYPES = new Set<NoteType>([ "text", "code", "book", "canvas" ]);
function NoteTypeSwitcher() {
const { note } = useNoteContext();
const blob = useNoteBlob(note);
const currentNoteType = useNoteProperty(note, "type");
const { pinnedNoteTypes, restNoteTypes } = useMemo(() => {
const pinnedNoteTypes: NoteTypeMapping[] = [];
const restNoteTypes: NoteTypeMapping[] = [];
for (const noteType of NOTE_TYPES) {
if (noteType.reserved || noteType.static || noteType.type === "book") continue;
if (SWITCHER_PINNED_NOTE_TYPES.has(noteType.type)) {
pinnedNoteTypes.push(noteType);
} else {
restNoteTypes.push(noteType);
}
}
return { pinnedNoteTypes, restNoteTypes };
}, []);
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
<div
className="note-type-switcher"
onWheel={onWheelHorizontalScroll}
>
{note && blob?.contentLength === 0 && (
<>
<div className="intro">{t("note_title.note_type_switcher_label", { type: currentNoteTypeData?.title.toLocaleLowerCase() })}</div>
{pinnedNoteTypes.map(noteType => noteType.type !== currentNoteType && (
<Badge
key={noteType.type}
text={noteType.title}
icon={`bx ${noteType.icon}`}
onClick={() => switchNoteType(note.noteId, noteType)}
/>
))}
{collectionTemplates.length > 0 && <CollectionNoteTypes noteId={note.noteId} collectionTemplates={collectionTemplates} />}
{builtinTemplates.length > 0 && <TemplateNoteTypes noteId={note.noteId} builtinTemplates={builtinTemplates} />}
{restNoteTypes.length > 0 && <MoreNoteTypes noteId={note.noteId} restNoteTypes={restNoteTypes} />}
</>
)}
</div>
);
}
function MoreNoteTypes({ noteId, restNoteTypes }: { noteId: string, restNoteTypes: NoteTypeMapping[] }) {
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_others")}
icon="bx bx-dots-vertical-rounded"
>
{restNoteTypes.map(noteType => (
<FormListItem
key={noteType.type}
icon={`bx ${noteType.icon}`}
onClick={() => switchNoteType(noteId, noteType)}
>{noteType.title}</FormListItem>
))}
</BadgeWithDropdown>
);
}
function CollectionNoteTypes({ noteId, collectionTemplates }: { noteId: string, collectionTemplates: FNote[] }) {
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_collection")}
icon="bx bx-book"
>
{collectionTemplates.map(collectionTemplate => (
<FormListItem
key={collectionTemplate.noteId}
icon={collectionTemplate.getIcon()}
onClick={() => setTemplate(noteId, collectionTemplate.noteId)}
>{collectionTemplate.title}</FormListItem>
))}
</BadgeWithDropdown>
);
}
function TemplateNoteTypes({ noteId, builtinTemplates }: { noteId: string, builtinTemplates: FNote[] }) {
const [ userTemplates, setUserTemplates ] = useState<FNote[]>([]);
async function refreshTemplates() {
const templateNoteIds = await server.get<string[]>("search-templates");
const templateNotes = await froca.getNotes(templateNoteIds);
setUserTemplates(templateNotes);
}
// First load.
useEffect(() => {
refreshTemplates();
}, []);
// React to external changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(attr => attr.type === "label" && attr.name === "template")) {
refreshTemplates();
}
});
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_templates")}
icon="bx bx-copy-alt"
>
{userTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
{userTemplates.length > 0 && <FormDropdownDivider />}
{builtinTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
</BadgeWithDropdown>
);
}
function TemplateItem({ noteId, template }: { noteId: string, template: FNote }) {
return (
<FormListItem
icon={template.getIcon()}
onClick={() => setTemplate(noteId, template.noteId)}
>{template.title}</FormListItem>
);
}
function switchNoteType(noteId: string, { type, mime }: NoteTypeMapping) {
return server.put(`notes/${noteId}/type`, { type, mime });
}
function setTemplate(noteId: string, templateId: string) {
return attributes.setRelation(noteId, "template", templateId);
}
function useBuiltinTemplates() {
const [ templates, setTemplates ] = useState<{
builtinTemplates: FNote[];
collectionTemplates: FNote[];
}>({
builtinTemplates: [],
collectionTemplates: []
});
async function loadBuiltinTemplates() {
const templatesRoot = await froca.getNote("_templates");
if (!templatesRoot) return;
const childNotes = await templatesRoot.getChildNotes();
const builtinTemplates: FNote[] = [];
const collectionTemplates: FNote[] = [];
for (const childNote of childNotes) {
if (!childNote.hasLabel("template")) continue;
if (childNote.hasLabel("collection")) {
collectionTemplates.push(childNote);
} else {
builtinTemplates.push(childNote);
}
}
setTemplates({ builtinTemplates, collectionTemplates });
}
useEffect(() => {
loadBuiltinTemplates();
}, []);
return templates;
}
//#endregion
//#region Edited Notes
function EditedNotes() {
const { note } = useNoteContext();
const [ dateNote ] = useNoteLabel(note, "dateNote");
const [ editedNotesOpenInRibbon ] = useTriliumOptionBool("editedNotesOpenInRibbon");
return (note && dateNote &&
<Collapsible
className="edited-notes"
title={t("note_title.edited_notes")}
initiallyExpanded={editedNotesOpenInRibbon}
>
<EditedNotesContent note={note} />
</Collapsible>
);
}
function EditedNotesContent({ note }: { note: FNote }) {
const editedNotes = useEditedNotes(note);
return (editedNotes !== undefined &&
(editedNotes.length > 0 ? editedNotes?.map(editedNote => (
<SimpleBadge
key={editedNote.noteId}
title={(
<NoteLink
notePath={editedNote.noteId}
showNoteIcon
/>
)}
/>
)) : (
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
)));
}
//#endregion

View File

@ -16,6 +16,13 @@
&.share-badge {--color: var(--badge-share-background-color);}
&.clipped-note-badge {--color: var(--badge-clipped-note-background-color);}
&.execute-badge {--color: var(--badge-execute-background-color);}
min-width: 0;
.text {
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
}
.dropdown-badge {

View File

@ -4,8 +4,41 @@ body.experimental-feature-new-layout {
}
.title-actions {
&.visible:not(:empty) {
display: flex;
flex-direction: column;
gap: 0.5em;
&:not(:empty) {
padding: 0.75em 15px;
}
.edited-notes {
.collapsible-inner-body {
display: flex;
flex-wrap: wrap;
gap: 0.3em;
.badge {
margin: 0;
color: inherit;
text-transform: none;
text-decoration: none;
display: inline-block;
transition: background-color 250ms ease-in, color 250ms ease-in;
&:hover {
background-color: var(--link-hover-background);
color: var(--link-hover-color);
}
}
}
}
.promoted-attributes-widget {
.promoted-attributes-container {
margin: 0;
padding: 0;
}
}
}
}

View File

@ -1,7 +1,7 @@
import "./NoteTitleActions.css";
import clsx from "clsx";
import { useEffect, useRef, useState } from "preact/hooks";
import { useEffect, useState } from "preact/hooks";
import NoteContext from "../../components/note_context";
import FNote from "../../entities/fnote";
@ -9,29 +9,31 @@ import { t } from "../../services/i18n";
import CollectionProperties from "../note_bars/CollectionProperties";
import { checkFullHeight, getExtendedWidgetType } from "../NoteDetail";
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
import SimpleBadge from "../react/Badge";
import Collapsible, { ExternallyControlledCollapsible } from "../react/Collapsible";
import { useNoteContext, useNoteProperty, useTriliumEvent } from "../react/hooks";
import { useNoteContext, useNoteLabel, useNoteProperty, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
import NoteLink, { NewNoteLink } from "../react/NoteLink";
import { useEditedNotes } from "../ribbon/EditedNotesTab";
import SearchDefinitionTab from "../ribbon/SearchDefinitionTab";
import NoteTypeSwitcher from "./NoteTypeSwitcher";
export default function NoteTitleActions() {
const { note, ntxId, componentId, noteContext } = useNoteContext();
const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_");
const noteType = useNoteProperty(note, "type");
const items = [
note && <PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />,
note && noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />,
note && !isHiddenNote && noteType === "book" && <CollectionProperties note={note} />
].filter(Boolean);
return (
<div className={clsx("title-actions", items.length > 0 && "visible")}>
{items}
<div className="title-actions">
<PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />
{noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />}
{!isHiddenNote && note && noteType === "book" && <CollectionProperties note={note} />}
<EditedNotes />
<NoteTypeSwitcher />
</div>
);
}
function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null | undefined }) {
function SearchProperties({ note, ntxId }: { note: FNote | null | undefined, ntxId: string | null | undefined }) {
return (note &&
<Collapsible
title={t("search_definition.search_parameters")}
@ -64,10 +66,43 @@ function PromotedAttributes({ note, componentId, noteContext }: {
return (note && (
<ExternallyControlledCollapsible
key={note.noteId}
title={t("promoted_attributes.promoted_attributes")}
title={t("note_title.promoted_attributes")}
expanded={expanded} setExpanded={setExpanded}
>
<PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />
</ExternallyControlledCollapsible>
));
}
//#region Edited Notes
function EditedNotes() {
const { note } = useNoteContext();
const [ dateNote ] = useNoteLabel(note, "dateNote");
const [ editedNotesOpenInRibbon ] = useTriliumOptionBool("editedNotesOpenInRibbon");
return (note && dateNote &&
<Collapsible
className="edited-notes"
title={t("note_title.edited_notes")}
initiallyExpanded={editedNotesOpenInRibbon}
>
<EditedNotesContent note={note} />
</Collapsible>
);
}
function EditedNotesContent({ note }: { note: FNote }) {
const editedNotes = useEditedNotes(note);
return (editedNotes !== undefined &&
(editedNotes.length > 0 ? editedNotes?.map(editedNote => (
<NewNoteLink
className="badge"
notePath={editedNote.noteId}
showNoteIcon
/>
)) : (
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
)));
}
//#endregion

View File

@ -0,0 +1,33 @@
@keyframes note-type-switcher-intro {
from {
opacity: 0;
} to {
opacity: 1;
}
}
.note-type-switcher {
--badge-radius: 12px;
position: relative;
top: 5px;
padding: .25em 0;
display: flex;
align-items: center;
overflow-x: auto;
min-width: 0;
gap: 5px;
min-height: 35px;
>* {
flex-shrink: 0;
animation: note-type-switcher-intro 200ms ease-in;
}
.ext-badge {
--color: var(--input-background-color);
color: var(--main-text-color);
font-size: 0.9rem;
flex-shrink: 0;
}
}

View File

@ -0,0 +1,182 @@
import "./NoteTypeSwitcher.css";
import { NoteType } from "@triliumnext/commons";
import { useEffect, useMemo, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import { NOTE_TYPES, NoteTypeMapping } from "../../services/note_types";
import server from "../../services/server";
import { Badge, BadgeWithDropdown } from "../react/Badge";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useNoteBlob, useNoteContext, useNoteProperty, useTriliumEvent } from "../react/hooks";
import { onWheelHorizontalScroll } from "../widget_utils";
const SWITCHER_PINNED_NOTE_TYPES = new Set<NoteType>([ "text", "code", "book", "canvas" ]);
const supportedNoteTypes = new Set<NoteType>([
"text", "code"
]);
export default function NoteTypeSwitcher() {
const { note } = useNoteContext();
const blob = useNoteBlob(note);
const currentNoteType = useNoteProperty(note, "type");
const { pinnedNoteTypes, restNoteTypes } = useMemo(() => {
const pinnedNoteTypes: NoteTypeMapping[] = [];
const restNoteTypes: NoteTypeMapping[] = [];
for (const noteType of NOTE_TYPES) {
if (noteType.reserved || noteType.static || noteType.type === "book") continue;
if (SWITCHER_PINNED_NOTE_TYPES.has(noteType.type)) {
pinnedNoteTypes.push(noteType);
} else {
restNoteTypes.push(noteType);
}
}
return { pinnedNoteTypes, restNoteTypes };
}, []);
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
<div
className="note-type-switcher"
onWheel={onWheelHorizontalScroll}
>
{note && blob?.contentLength === 0 && (
<>
<div className="intro">{t("note_title.note_type_switcher_label", { type: currentNoteTypeData?.title.toLocaleLowerCase() })}</div>
{pinnedNoteTypes.map(noteType => noteType.type !== currentNoteType && (
<Badge
key={noteType.type}
text={noteType.title}
icon={`bx ${noteType.icon}`}
onClick={() => switchNoteType(note.noteId, noteType)}
/>
))}
{collectionTemplates.length > 0 && <CollectionNoteTypes noteId={note.noteId} collectionTemplates={collectionTemplates} />}
{builtinTemplates.length > 0 && <TemplateNoteTypes noteId={note.noteId} builtinTemplates={builtinTemplates} />}
{restNoteTypes.length > 0 && <MoreNoteTypes noteId={note.noteId} restNoteTypes={restNoteTypes} />}
</>
)}
</div>
);
}
function MoreNoteTypes({ noteId, restNoteTypes }: { noteId: string, restNoteTypes: NoteTypeMapping[] }) {
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_others")}
icon="bx bx-dots-vertical-rounded"
>
{restNoteTypes.map(noteType => (
<FormListItem
key={noteType.type}
icon={`bx ${noteType.icon}`}
onClick={() => switchNoteType(noteId, noteType)}
>{noteType.title}</FormListItem>
))}
</BadgeWithDropdown>
);
}
function CollectionNoteTypes({ noteId, collectionTemplates }: { noteId: string, collectionTemplates: FNote[] }) {
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_collection")}
icon="bx bx-book"
>
{collectionTemplates.map(collectionTemplate => (
<FormListItem
key={collectionTemplate.noteId}
icon={collectionTemplate.getIcon()}
onClick={() => setTemplate(noteId, collectionTemplate.noteId)}
>{collectionTemplate.title}</FormListItem>
))}
</BadgeWithDropdown>
);
}
function TemplateNoteTypes({ noteId, builtinTemplates }: { noteId: string, builtinTemplates: FNote[] }) {
const [ userTemplates, setUserTemplates ] = useState<FNote[]>([]);
async function refreshTemplates() {
const templateNoteIds = await server.get<string[]>("search-templates");
const templateNotes = await froca.getNotes(templateNoteIds);
setUserTemplates(templateNotes);
}
// First load.
useEffect(() => {
refreshTemplates();
}, []);
// React to external changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(attr => attr.type === "label" && attr.name === "template")) {
refreshTemplates();
}
});
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_templates")}
icon="bx bx-copy-alt"
>
{userTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
{userTemplates.length > 0 && <FormDropdownDivider />}
{builtinTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
</BadgeWithDropdown>
);
}
function TemplateItem({ noteId, template }: { noteId: string, template: FNote }) {
return (
<FormListItem
icon={template.getIcon()}
onClick={() => setTemplate(noteId, template.noteId)}
>{template.title}</FormListItem>
);
}
function switchNoteType(noteId: string, { type, mime }: NoteTypeMapping) {
return server.put(`notes/${noteId}/type`, { type, mime });
}
function setTemplate(noteId: string, templateId: string) {
return attributes.setRelation(noteId, "template", templateId);
}
function useBuiltinTemplates() {
const [ templates, setTemplates ] = useState<{
builtinTemplates: FNote[];
collectionTemplates: FNote[];
}>({
builtinTemplates: [],
collectionTemplates: []
});
async function loadBuiltinTemplates() {
const templatesRoot = await froca.getNote("_templates");
if (!templatesRoot) return;
const childNotes = await templatesRoot.getChildNotes();
const builtinTemplates: FNote[] = [];
const collectionTemplates: FNote[] = [];
for (const childNote of childNotes) {
if (!childNote.hasLabel("template")) continue;
if (childNote.hasLabel("collection")) {
collectionTemplates.push(childNote);
} else {
builtinTemplates.push(childNote);
}
}
setTemplates({ builtinTemplates, collectionTemplates });
}
useEffect(() => {
loadBuiltinTemplates();
}, []);
return templates;
}

View File

@ -37,7 +37,7 @@ body.experimental-feature-new-layout {
.title-row {
container-type: size;
transition: border 400ms ease-out;
&.note-split-title {
border-bottom: 1px solid var(--main-border-color);
@ -56,13 +56,13 @@ body.experimental-feature-new-layout {
--note-icon-container-padding-size: 6px;
margin-inline: 0;
}
.note-title-widget {
--note-title-size: 18px;
--note-title-padding-inline: 0;
}
@container (max-width: 700px) {
@container (max-width: 600px) {
.note-title-widget {
--note-title-size: 1.25rem;
--note-title-padding-inline: 4px;
@ -80,7 +80,7 @@ body.experimental-feature-new-layout {
.ext-badge .text {
display: none;
}
.ext-badge {
--size: 2em;
width: var(--size);

View File

@ -50,16 +50,30 @@
white-space: nowrap;
border-radius: var(--badge-radius);
button,
.btn {
min-width: 0;
overflow: hidden;
}
.ext-badge {
border-radius: 0;
.text {
display: inline-flex;
align-items: center;
min-width: 0;
.text-inner {
min-width: 0;
text-overflow: ellipsis;
overflow: hidden;
}
.arrow {
font-size: 1.3em;
margin-left: 0.25em;
margin-inline-start: 0.25em;
margin-inline-end: 0;
}
}
}

View File

@ -60,7 +60,10 @@ export function BadgeWithDropdown({ text, children, tooltip, className, dropdown
<Dropdown
className={`dropdown-badge dropdown-${className}`}
text={<Badge
text={<>{text} <Icon className="arrow" icon="bx bx-chevron-down" /></>}
text={<>
<span class="text-inner">{text}</span>
<Icon className="arrow" icon="bx bx-chevron-down" />
</>}
className={className}
{...props}
/>}

View File

@ -2,10 +2,11 @@ import { Dropdown as BootstrapDropdown, Tooltip } from "bootstrap";
import { ComponentChildren, HTMLAttributes } from "preact";
import { CSSProperties, HTMLProps } from "preact/compat";
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
import { useTooltip, useUniqueName } from "./hooks";
type DataAttributes = {
[key: `data-${string}`]: string | number | boolean | undefined;
[key: `data-${string}`]: string | number | boolean | undefined;
};
export interface DropdownProps extends Pick<HTMLProps<HTMLDivElement>, "id" | "className"> {
@ -66,14 +67,14 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
return () => {
resizeObserver.disconnect();
dropdown.dispose();
}
};
}, []);
const onShown = useCallback(() => {
setShown(true);
externalOnShown?.();
hideTooltip();
}, [ hideTooltip ])
}, [ hideTooltip ]);
const onHidden = useCallback(() => {
setShown(false);
@ -122,7 +123,7 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
{...buttonProps}
>
{text}
<span className="caret"></span>
<span className="caret" />
</button>
<ul
@ -130,9 +131,15 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
style={dropdownContainerStyle}
aria-labelledby={ariaId}
ref={dropdownContainerRef}
onClick={(e) => {
// Prevent clicks directly inside the dropdown from closing.
if (e.target === dropdownContainerRef.current) {
e.stopPropagation();
}
}}
>
{shown && children}
</ul>
</div>
)
);
}

View File

@ -105,22 +105,17 @@ export function NewNoteLink({ notePath, viewScope, noContextMenu, showNoteIcon,
const [ archived ] = useNoteLabelBoolean(note, "archived");
return (
<span>
<span>
{icon && <Icon icon={icon} />}
<a
className={clsx("tn-link", colorClass, {
"no-tooltip-preview": noPreview,
archived
})}
href={calculateHash({ notePath, viewScope })}
data-no-context-menu={noContextMenu}
{...linkProps}
>
{title}
</a>
</span>
</span>
<a
className={clsx("tn-link", colorClass, {
"no-tooltip-preview": noPreview,
archived
})}
href={calculateHash({ notePath, viewScope })}
data-no-context-menu={noContextMenu}
{...linkProps}
>
{icon && <><Icon icon={icon} />&nbsp;</>}
{title}
</a>
);
}

View File

@ -369,7 +369,8 @@ export function useActiveNoteContext() {
useEffect(() => {
setNote(noteContext?.note);
}, [ notePath ]);
setNotePath(noteContext?.notePath);
}, [ notePath, noteContext?.note, noteContext?.notePath ]);
useTriliumEvents([ "setNoteContext", "activeContextChanged", "noteSwitchedAndActivated", "noteSwitched" ], () => {
const noteContext = appContext.tabManager.getActiveContext() ?? undefined;
@ -634,7 +635,8 @@ export function useLegacyWidget<T extends BasicWidget>(widgetFactory: () => T, {
const renderedWidget = widget.render();
return [ widget, renderedWidget ];
}, [ noteContext, parentComponent, widgetFactory]);
}, [ noteContext, parentComponent ]); // eslint-disable-line react-hooks/exhaustive-deps
// widgetFactory() is intentionally left out
// Attach the widget to the parent.
useEffect(() => {

View File

@ -5,6 +5,7 @@ import { useEffect, useState } from "preact/hooks";
import FNote from "../entities/fnote";
import attributes from "../services/attributes";
import { t } from "../services/i18n";
import { isElectron } from "../services/utils";
import HelpButton from "./react/HelpButton";
import { useNoteContext, useTriliumEvent, useTriliumOption } from "./react/hooks";
import InfoBar from "./react/InfoBar";
@ -68,7 +69,11 @@ export function useShareInfo(note: FNote | null | undefined) {
}
});
return { link, linkHref, isSharedExternally: !!syncServerHost };
return {
link,
linkHref,
isSharedExternally: !isElectron() || !!syncServerHost // on server we can't reliably detect if the note is shared locally or available publicly.
};
}
function getShareId(note: FNote) {

View File

@ -1,15 +1,15 @@
<p>When you run Trilium for the first time, it will generate a new database
containing demo notes. These notes showcase its many features, such as:</p>
<ul>
<li data-list-item-id="e1332d0e5bd3075dff093a222c3396eba"><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
<li><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
</li>
<li data-list-item-id="e15271c447822c455fcc523deb51fb9eb"><a class="reference-link" href="#root/_help_l0tKav7yLHGF">Day Notes</a>
<li><a class="reference-link" href="#root/_help_l0tKav7yLHGF">Day Notes</a>
</li>
<li data-list-item-id="ee1a63745a43d7a8109f69dcdaf79ee28"><a class="reference-link" href="#root/_help_R7abl2fc6Mxi">Weight Tracker</a>
<li><a class="reference-link" href="#root/_help_R7abl2fc6Mxi">Weight Tracker</a>
</li>
<li data-list-item-id="ed1b10c8014e5fbaab923dd9ed7e4b122"><a class="reference-link" href="#root/_help_xYjQUYhpbUEW">Task Manager</a>
<li><a class="reference-link" href="#root/_help_xYjQUYhpbUEW">Task Manager</a>
</li>
<li data-list-item-id="edbd34e77d6f5c13372f6ff6135fcfb95"><a class="reference-link" href="#root/_help_Wy267RK4M69c">Themes</a>
<li><a class="reference-link" href="#root/_help_Wy267RK4M69c">Themes</a>
</li>
</ul>
<h3>Restoring Demo Notes</h3>
@ -21,10 +21,10 @@
<p>You can easily restore the demo notes by using Trilium's built-in import
feature by importing them:</p>
<ul>
<li data-list-item-id="eaa8db14b035c1cc6b11d54b19acf9803">Download <a href="https://github.com/TriliumNext/Trilium/raw/refs/heads/main/apps/server/src/assets/db/demo.zip">the .zip archive</a> with
<li>Download <a href="https://github.com/TriliumNext/Trilium/raw/refs/heads/main/apps/server/src/assets/db/demo.zip">the .zip archive</a> with
the latest version of the demo notes</li>
<li data-list-item-id="e3aa9c23f36af5aa9931ecf72de36083f">Right click on any note in your tree under which you would like the demo
<li>Right click on any note in your tree under which you would like the demo
notes to be imported</li>
<li data-list-item-id="e08987d861bb67c9457e73122c134a641">Click "Import into note"</li>
<li data-list-item-id="ee8b17222734db674b929a1d2f55a6840">Select the .zip archive to import it</li>
<li>Click "Import into note"</li>
<li>Select the .zip archive to import it</li>
</ul>

View File

@ -24,7 +24,7 @@ The current date &amp; time is &lt;span class="date"&gt;&lt;/span&gt;</code></pr
<p>Now we need to add the script. Create another&nbsp;<a class="reference-link"
href="#root/_help_6f9hih2hXXZk">Code</a>, but this time of JavaScript (frontend)
language. Make sure the newly created note is a direct child of the HTML
note created previously; with the following content:</p><pre><code class="language-text-x-trilium-auto">const $dateEl = api.$container.find(".date");
note created previously; with the following content:</p><pre><code class="language-application-javascript-env-backend">const $dateEl = api.$container.find(".date");
$dateEl.text(new Date());</code></pre>
<p>Now create a render note at any place and set its <code>~renderNote</code> relation
to point to the HTML note. When the render note is accessed it will display:</p>

View File

@ -32,41 +32,30 @@
<p>Let's start by creating a widget that shows a message near the content
area. Follow the previous section to create a code note, and use the following
content.</p>
<table>
<thead>
<tr>
<th>Legacy</th>
<th>Preact (v0.101.0+)</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre><code class="language-text-x-trilium-auto">class HelloNoteDetail extends api.BasicWidget {
<h3>Legacy version (jQuery)</h3><pre><code class="language-text-x-trilium-auto">class HelloCenterPane extends api.BasicWidget {
<pre><code class="language-text-x-trilium-auto">constructor() {
super();
this.contentSized();
constructor() {
super();
this.contentSized();
}
get parentWidget() { return "center-pane" }
doRender() {
this.$widget = $("&lt;span&gt;Center pane&lt;/span&gt;");
}
}
get parentWidget() { return "center-pane" }
module.exports = new HelloCenterPane();</code></pre>
<p><a href="#root/_help_s8alTXmpFR61">Refresh the application</a> and the widget
should appear underneath the content area.</p>
<h3>Preact version</h3><pre><code class="language-text-x-trilium-auto">import { defineWidget } from "trilium:preact";
doRender() {
this.&lt;!--FORMULA_INLINE_1766526977514_0--&gt;("&amp;lt;span&amp;gt;Center pane&amp;lt;/span&amp;gt;");
}</code></pre>
<p>}</p>
<p>module.exports = new HelloNoteDetail();</p>
</code>
</pre>
</td>
<td><pre><code class="language-text-x-trilium-auto">import { defineWidget } from "trilium:preact";<p></p><p>export default defineWidget({
export default defineWidget({
parent: "center-pane",
render: () =&gt; &lt;span&gt;Center pane from Preact.&lt;/span&gt;
});</p></code></pre>
</td>
</tr>
</tbody>
</table>
<p></p>
});</code></pre>
<p><a href="#root/_help_s8alTXmpFR61">Refresh the application</a> and the widget
should appear underneath the content area.</p>
<h2>Widget location (parent widget)</h2>

View File

@ -30,7 +30,7 @@ class NoteTitleWidget extends api.NoteContextAwareWidget {
}
module.exports = new NoteTitleWidget();</code></pre>
<h2>Preact widget (v0.101.0+)</h2><pre><code class="language-text-x-trilium-auto">import { defineLauncherWidget, useActiveNoteContext } from "trilium:preact";
<h2>Preact widget (v0.101.0+)</h2><pre><code class="language-text-jsx">import { defineLauncherWidget, useActiveNoteContext } from "trilium:preact";
export default defineLauncherWidget({
render: () =&gt; {

View File

@ -2,9 +2,9 @@
support for JSX.</p>
<p>Preact can be used for:</p>
<ul>
<li><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>, where
<li data-list-item-id="e5c224943359315528b8eb20e0e16bf02"><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>, where
a JSX code note is used instead of a HTML one.</li>
<li><a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>,
<li data-list-item-id="eccf375685d8f35e66c9e62619f013b60"><a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>,
where JSX can be used to replace the old, jQuery-based mechanism.</li>
</ul>
<p>To get started, the first step is to enable JSX in the list of Code languages.
@ -20,22 +20,23 @@
</aside>
<h2>Import/exports</h2>
<p>When using Preact with JSX, there is a special syntax which provides ES-like
imports. This <code>import</code> syntax makes way for a more intuitive that
doesn't make use of global objects and paves the way for better auto-completion
support that might be introduced in the future.&nbsp;</p>
imports. This <code spellcheck="false">import</code> syntax makes way for
a more intuitive that doesn't make use of global objects and paves the
way for better auto-completion support that might be introduced in the
future.&nbsp;</p>
<h3>API imports</h3>
<p>Instead of:</p><pre><code class="language-text-x-trilium-auto">api.showMessage("Hello");</code></pre>
<p>the JSX version looks like this:</p><pre><code class="language-text-x-trilium-auto">import { showMessage } from "trilium:api";
<p>Instead of:</p><pre><code class="language-text-jsx">api.showMessage("Hello");</code></pre>
<p>the JSX version looks like this:</p><pre><code class="language-text-jsx">import { showMessage } from "trilium:api";
showMessage("hello");</code></pre>
<h3>Preact API imports (hooks, components)</h3>
<p>There's a new&nbsp;<a class="reference-link" href="#root/_help_GLks18SNjxmC">Script API</a>&nbsp;dedicated
to Preact, which provides shared components that are also used by Trilium
internally as well as hooks, for example.</p><pre><code class="language-text-x-trilium-auto">import { useState } from "trilium:preact";
internally as well as hooks, for example.</p><pre><code class="language-text-jsx">import { useState } from "trilium:preact";
const [ myState, setMyState ] = useState("Hi");</code></pre>
<h3>Exporting</h3>
<p>JSX notes can export a component for use in&nbsp;<a class="reference-link"
href="#root/_help_HcABDtFCkbFN">Render Note</a>&nbsp;or for&nbsp;<a class="reference-link"
href="#root/_help_Bqde6BvPo05g">Component libraries</a>:</p><pre><code class="language-text-x-trilium-auto">export default function() {
href="#root/_help_Bqde6BvPo05g">Component libraries</a>:</p><pre><code class="language-text-jsx">export default function() {
return (
&lt;&gt;
&lt;p&gt;Hello world.&lt;/p&gt;
@ -43,13 +44,15 @@ const [ myState, setMyState ] = useState("Hi");</code></pre>
);
}</code></pre>
<h3>Import/export are not required</h3>
<p>These imports are syntactic sugar meant to replace the usage for the <code>api</code> global
object (see&nbsp;<a class="reference-link" href="#root/_help_GLks18SNjxmC">Script API</a>).&nbsp;</p>
<p>These imports are syntactic sugar meant to replace the usage for the
<code
spellcheck="false">api</code>global object (see&nbsp;<a class="reference-link" href="#root/_help_GLks18SNjxmC">Script API</a>).&nbsp;</p>
<aside
class="admonition note">
<p>The <code>import</code> and <code>export</code> syntax work only for JSX notes.
Standard/jQuery code notes still need to use the <code>api</code> global
and <code>module.exports</code>.</p>
<p>The <code spellcheck="false">import</code> and <code spellcheck="false">export</code> syntax
work only for JSX notes. Standard/jQuery code notes still need to use the
<code
spellcheck="false">api</code>global and <code spellcheck="false">module.exports</code>.</p>
</aside>
<h2>Under the hood</h2>
<p>Unlike JavaScript, JSX requires pre-processing to turn it into JavaScript

View File

@ -7,8 +7,8 @@
also available to&nbsp;<a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>&nbsp;and&nbsp;
<a
class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>.</p>
<p>To use these components, simply import them from <code>trilium:preact</code>:</p><pre><code class="language-text-x-trilium-auto">import { ActionButton, Button, LinkButton } from "trilium:preact";</code></pre>
<p>and then use them:</p><pre><code class="language-text-x-trilium-auto">export default function MyRenderNote() {
<p>To use these components, simply import them from <code spellcheck="false">trilium:preact</code>:</p><pre><code class="language-text-jsx">import { ActionButton, Button, LinkButton } from "trilium:preact";</code></pre>
<p>and then use them:</p><pre><code class="language-text-jsx">export default function MyRenderNote() {
const onClick = () =&gt; showMessage("A button was pressed");
return (
@ -33,12 +33,12 @@
to custom widgets and JSX render notes.</p>
<p>To use it, simply:</p>
<ol>
<li>Create a render note.</li>
<li>Create a child code note of JSX type with the content of this file:&nbsp;
<li data-list-item-id="e201c26633c3655e16c1408da3d277352">Create a render note.</li>
<li data-list-item-id="e45bace7ccf33bec8fbe7103e28e21c7f">Create a child code note of JSX type with the content of this file:&nbsp;
<a
class="reference-link" href="#root/_help_i9B4IW7b6V6z">Widget showcase</a>
</li>
<li>Set the <code>~renderNote</code> relation of the parent note to the child
note.</li>
<li>Refresh the render note to see the results.</li>
<li data-list-item-id="e35635c3e08829316c6a1b68b80ff8ebd">Set the <code spellcheck="false">~renderNote</code> relation of the parent
note to the child note.</li>
<li data-list-item-id="edd02eaa6bb298854826ae5c7cd2d32d3">Refresh the render note to see the results.</li>
</ol>

View File

@ -1,4 +1,4 @@
<h2>Inline styles</h2><pre><code class="language-text-x-trilium-auto">&lt;div style={{
<h2>Inline styles</h2><pre><code class="language-text-jsx">&lt;div style={{
display: "flex",
height: "53px",
width: "fit-content",
@ -9,4 +9,4 @@
<h2>Custom CSS file</h2>
<p>Simply create a&nbsp;<a class="reference-link" href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>.
Make sure the class names are unique enough to not intersect with other
UI elements, consider adding a prefix (e.g. <code>x-mywidget-</code>).</p>
UI elements, consider adding a prefix (e.g. <code spellcheck="false">x-mywidget-</code>).</p>

View File

@ -1,5 +1,5 @@
# Documentation
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/nfSRd3FnBEBK/Documentation_image.png" width="205" height="162">
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/LZ93RWFQwIO3/Documentation_image.png" width="205" height="162">
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.

View File

@ -24,7 +24,7 @@ The current date & time is <span class="date"></span>
Now we need to add the script. Create another <a class="reference-link" href="Code.md">Code</a>, but this time of JavaScript (frontend) language. Make sure the newly created note is a direct child of the HTML note created previously; with the following content:
```
```javascript
const $dateEl = api.$container.find(".date");
$dateEl.text(new Date());
```

View File

@ -23,21 +23,39 @@ Wherever possible, widget examples will be both in the legacy and Preact format.
Let's start by creating a widget that shows a message near the content area. Follow the previous section to create a code note, and use the following content.
<table><thead><tr><th>Legacy</th><th>Preact (v0.101.0+)</th></tr></thead><tbody><tr><td><pre><code class="language-text-x-trilium-auto">class HelloNoteDetail extends api.BasicWidget {
### Legacy version (jQuery)
<pre><code class="language-text-x-trilium-auto">constructor() {
super();
this.contentSized();
```
class HelloCenterPane extends api.BasicWidget {
constructor() {
super();
this.contentSized();
}
get parentWidget() { return "center-pane" }
doRender() {
this.$widget = $("<span>Center pane</span>");
}
}
get parentWidget() { return "center-pane" }
module.exports = new HelloCenterPane();
```
doRender() {
this.&lt;!--FORMULA_INLINE_1766526977514_0--&gt;("&amp;lt;span&amp;gt;Center pane&amp;lt;/span&amp;gt;");
}</code></pre><p>}</p><p>module.exports = new HelloNoteDetail();</p></code></pre></td><td><pre><code class="language-text-x-trilium-auto">import { defineWidget } from "trilium:preact";<p></p><p>export default defineWidget({
[Refresh the application](../../Troubleshooting/Refreshing%20the%20application.md) and the widget should appear underneath the content area.
### Preact version
```
import { defineWidget } from "trilium:preact";
export default defineWidget({
parent: "center-pane",
render: () =&gt; &lt;span&gt;Center pane from Preact.&lt;/span&gt;
});</p></code></pre></td></tr></tbody></table>
render: () => <span>Center pane from Preact.</span>
});
```
[Refresh the application](../../Troubleshooting/Refreshing%20the%20application.md) and the widget should appear underneath the content area.

View File

@ -35,7 +35,7 @@ module.exports = new NoteTitleWidget();
## Preact widget (v0.101.0+)
```
```jsx
import { defineLauncherWidget, useActiveNoteContext } from "trilium:preact";
export default defineLauncherWidget({

View File

@ -19,13 +19,13 @@ When using Preact with JSX, there is a special syntax which provides ES-like imp
Instead of:
```
```jsx
api.showMessage("Hello");
```
the JSX version looks like this:
```
```jsx
import { showMessage } from "trilium:api";
showMessage("hello");
```
@ -34,7 +34,7 @@ showMessage("hello");
There's a new <a class="reference-link" href="../Script%20API.md">Script API</a> dedicated to Preact, which provides shared components that are also used by Trilium internally as well as hooks, for example.
```
```jsx
import { useState } from "trilium:preact";
const [ myState, setMyState ] = useState("Hi");
```
@ -43,7 +43,7 @@ const [ myState, setMyState ] = useState("Hi");
JSX notes can export a component for use in <a class="reference-link" href="../../Note%20Types/Render%20Note.md">Render Note</a> or for <a class="reference-link" href="Preact/Component%20libraries.md">Component libraries</a>:
```
```jsx
export default function() {
return (
<>

View File

@ -5,13 +5,13 @@ Trilium comes with its own set of Preact components, some of which are also avai
To use these components, simply import them from `trilium:preact`:
```
```jsx
import { ActionButton, Button, LinkButton } from "trilium:preact";
```
and then use them:
```
```jsx
export default function MyRenderNote() {
const onClick = () => showMessage("A button was pressed");

View File

@ -1,7 +1,7 @@
# CSS
## Inline styles
```
```jsx
<div style={{
display: "flex",
height: "53px",