From 9942950710cde6ad2ae9579e93b078e1f4398fc3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 14:54:57 +0200 Subject: [PATCH 001/873] feat(layout): relocate title into scrollable region --- apps/client/src/layouts/desktop_layout.tsx | 20 ++++++++++++++------ apps/client/src/widgets/note_title.css | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 50dc05d99e..90b8566e9f 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -117,13 +117,11 @@ export default class DesktopLayout { new NoteWrapperWidget() .child( new FlexContainer("row") - .class("title-row") - .css("height", "50px") - .css("min-height", "50px") + .class("breadcrumb-row") + .css("height", "30px") + .css("min-height", "30px") .css("align-items", "center") - .cssBlock(".title-row > * { margin: 5px; }") - .child() - .child() + .cssBlock(".breadcrumb-row > * { margin: 5px; }") .child() .child() .child() @@ -137,6 +135,16 @@ export default class DesktopLayout { new ScrollingContainer() .filling() .child(new ContentHeader() + .child(new FlexContainer("row") + .class("title-row") + .css("height", "50px") + .css("margin", "1em") + .css("min-height", "50px") + .css("align-items", "center") + .cssBlock(".title-row > * { margin: 5px; }") + .child() + .child() + ) .child() .child() ) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index dd56edf96c..477d93209b 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -27,4 +27,4 @@ body.mobile .note-title-widget input.note-title { body.desktop .note-title-widget input.note-title { font-size: 180%; -} \ No newline at end of file +} From d02ec47d7710475716f107503c188b688a4984e0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 15:16:06 +0200 Subject: [PATCH 002/873] feat(breadcrumb): get breadcrumb to render --- apps/client/src/layouts/desktop_layout.tsx | 2 ++ apps/client/src/widgets/Breadcrumb.css | 4 +++ apps/client/src/widgets/Breadcrumb.tsx | 36 +++++++++++++++++++ apps/client/src/widgets/react/react_utils.tsx | 2 +- 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/Breadcrumb.css create mode 100644 apps/client/src/widgets/Breadcrumb.tsx diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 90b8566e9f..0347acf1e4 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -44,6 +44,7 @@ import NoteDetail from "../widgets/NoteDetail.jsx"; import PromotedAttributes from "../widgets/PromotedAttributes.jsx"; import SpacerWidget from "../widgets/launch_bar/SpacerWidget.jsx"; import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx"; +import Breadcrumb from "../widgets/Breadcrumb.jsx"; export default class DesktopLayout { @@ -122,6 +123,7 @@ export default class DesktopLayout { .css("min-height", "30px") .css("align-items", "center") .cssBlock(".breadcrumb-row > * { margin: 5px; }") + .child() .child() .child() .child() diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css new file mode 100644 index 0000000000..fe081ee3fc --- /dev/null +++ b/apps/client/src/widgets/Breadcrumb.css @@ -0,0 +1,4 @@ +.component.breadcrumb { + contain: none; + margin: 0 10px; +} diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx new file mode 100644 index 0000000000..0dd3068680 --- /dev/null +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -0,0 +1,36 @@ +import "./Breadcrumb.css"; +import { useNoteContext } from "./react/hooks"; +import NoteLink from "./react/NoteLink"; +import { joinElements } from "./react/react_utils"; + +export default function Breadcrumb() { + const { noteContext } = useNoteContext(); + const notePath = buildNotePaths(noteContext?.notePathArray); + console.log("Render with ", notePath); + + return ( +
+ {joinElements(notePath.map(item => ( + + )), <> › )} +
+ ) +} + +function BreadcrumbItem({ notePath }: { notePath: string }) { + return ( + + ) +} + +function buildNotePaths(notePathArray: string[] | undefined) { + if (!notePathArray) return []; + + let prefix = ""; + const output: string[] = []; + for (const notePath of notePathArray) { + output.push(`${prefix}${notePath}`); + prefix += `${notePath}/`; + } + return output; +} diff --git a/apps/client/src/widgets/react/react_utils.tsx b/apps/client/src/widgets/react/react_utils.tsx index 468a0e73ef..3bbce9066f 100644 --- a/apps/client/src/widgets/react/react_utils.tsx +++ b/apps/client/src/widgets/react/react_utils.tsx @@ -44,7 +44,7 @@ export function disposeReactWidget(container: Element) { render(null, container); } -export function joinElements(components: ComponentChild[] | undefined, separator = ", ") { +export function joinElements(components: ComponentChild[] | undefined, separator: ComponentChild = ", ") { if (!components) return <>; const joinedComponents: ComponentChild[] = []; From 43ceb1982dbcdd874bf00a10e86093ffde1e9c98 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 15:53:08 +0200 Subject: [PATCH 003/873] feat(breadcrumb): hide last note --- apps/client/src/widgets/Breadcrumb.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 0dd3068680..9055a1e539 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -6,7 +6,6 @@ import { joinElements } from "./react/react_utils"; export default function Breadcrumb() { const { noteContext } = useNoteContext(); const notePath = buildNotePaths(noteContext?.notePathArray); - console.log("Render with ", notePath); return (
@@ -28,7 +27,7 @@ function buildNotePaths(notePathArray: string[] | undefined) { let prefix = ""; const output: string[] = []; - for (const notePath of notePathArray) { + for (const notePath of notePathArray.slice(0, notePathArray.length - 1)) { output.push(`${prefix}${notePath}`); prefix += `${notePath}/`; } From 6e29fe8d58a90a0e9e17d1cfe5d599625383873d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 15:56:03 +0200 Subject: [PATCH 004/873] feat(breadcrumb): hide preview --- apps/client/src/widgets/Breadcrumb.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 9055a1e539..24a859eac6 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -18,7 +18,10 @@ export default function Breadcrumb() { function BreadcrumbItem({ notePath }: { notePath: string }) { return ( - + ) } From 5f215b14c2149d71a9e9953163be7ecd234cd379 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 16:01:34 +0200 Subject: [PATCH 005/873] feat(breadcrumb): start implementing interactive breadcrumbs --- apps/client/src/widgets/Breadcrumb.css | 9 +++++++++ apps/client/src/widgets/Breadcrumb.tsx | 21 +++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index fe081ee3fc..1993e57ca7 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -1,4 +1,13 @@ .component.breadcrumb { contain: none; margin: 0 10px; + display: flex; + align-items: center; + font-size: 0.9em; + gap: 0.25em; + + a { + color: inherit; + text-decoration: none; + } } diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 24a859eac6..dc1b072332 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -1,7 +1,8 @@ +import { Fragment } from "preact/jsx-runtime"; import "./Breadcrumb.css"; +import ActionButton from "./react/ActionButton"; import { useNoteContext } from "./react/hooks"; import NoteLink from "./react/NoteLink"; -import { joinElements } from "./react/react_utils"; export default function Breadcrumb() { const { noteContext } = useNoteContext(); @@ -9,9 +10,12 @@ export default function Breadcrumb() { return (
- {joinElements(notePath.map(item => ( - - )), <> › )} + {notePath.map(item => ( + + + + + ))}
) } @@ -25,6 +29,15 @@ function BreadcrumbItem({ notePath }: { notePath: string }) { ) } +function BreadcrumbSeparator({ notePath }: { notePath: string}) { + return ( + + ) +} + function buildNotePaths(notePathArray: string[] | undefined) { if (!notePathArray) return []; From a02235f2bdf40fddbb5111077843ee2790e84431 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 16:03:12 +0200 Subject: [PATCH 006/873] feat(breadcrumb): set up dropdown --- apps/client/src/widgets/Breadcrumb.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index dc1b072332..eacf79ed70 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -3,6 +3,8 @@ import "./Breadcrumb.css"; import ActionButton from "./react/ActionButton"; import { useNoteContext } from "./react/hooks"; import NoteLink from "./react/NoteLink"; +import Dropdown from "./react/Dropdown"; +import Icon from "./react/Icon"; export default function Breadcrumb() { const { noteContext } = useNoteContext(); @@ -31,10 +33,14 @@ function BreadcrumbItem({ notePath }: { notePath: string }) { function BreadcrumbSeparator({ notePath }: { notePath: string}) { return ( - + } + noSelectButtonStyle + buttonClassName="icon-action" + hideToggleArrow + > + Content goes here. + ) } From c4285772b378572ca31b55f0fac2b447fdd8a28b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 16:34:40 +0200 Subject: [PATCH 007/873] feat(breadcrumb): basic navigation in separator --- apps/client/src/widgets/Breadcrumb.css | 7 ++++++ apps/client/src/widgets/Breadcrumb.tsx | 31 +++++++++++++++++++++---- apps/client/src/widgets/react/hooks.tsx | 9 ++++--- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 1993e57ca7..12ee1c759a 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -10,4 +10,11 @@ color: inherit; text-decoration: none; } + + ul { + flex-direction: column; + list-style-type: none; + margin: 0; + padding: 0; + } } diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index eacf79ed70..3adf46b0aa 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -1,10 +1,11 @@ import { Fragment } from "preact/jsx-runtime"; import "./Breadcrumb.css"; -import ActionButton from "./react/ActionButton"; -import { useNoteContext } from "./react/hooks"; +import { useChildNotes, useNoteContext } from "./react/hooks"; import NoteLink from "./react/NoteLink"; import Dropdown from "./react/Dropdown"; import Icon from "./react/Icon"; +import { FormListItem } from "./react/FormList"; +import NoteContext from "../components/note_context"; export default function Breadcrumb() { const { noteContext } = useNoteContext(); @@ -15,7 +16,7 @@ export default function Breadcrumb() { {notePath.map(item => ( - + ))}
@@ -31,7 +32,7 @@ function BreadcrumbItem({ notePath }: { notePath: string }) { ) } -function BreadcrumbSeparator({ notePath }: { notePath: string}) { +function BreadcrumbSeparator({ notePath, noteContext }: { notePath: string, noteContext: NoteContext | undefined }) { return ( } @@ -39,11 +40,31 @@ function BreadcrumbSeparator({ notePath }: { notePath: string}) { buttonClassName="icon-action" hideToggleArrow > - Content goes here. + ) } +function BreadcrumbSeparatorDropdownContent({ notePath, noteContext }: { notePath: string, noteContext: NoteContext | undefined }) { + const notePathComponents = notePath.split("/"); + const parentNoteId = notePathComponents.pop(); + const childNotes = useChildNotes(parentNoteId); + const notePathPrefix = notePathComponents.join("/"); // last item was removed already. + + return ( + + ) +} + function buildNotePaths(notePathArray: string[] | undefined) { if (!notePathArray) return []; diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 24a7948dc1..0c9883df2a 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -886,12 +886,15 @@ async function isNoteReadOnly(note: FNote, noteContext: NoteContext) { return true; } -export function useChildNotes(parentNoteId: string) { +export function useChildNotes(parentNoteId: string | undefined) { const [ childNotes, setChildNotes ] = useState([]); useEffect(() => { (async function() { - const parentNote = await froca.getNote(parentNoteId); - const childNotes = await parentNote?.getChildNotes(); + let childNotes: FNote[] | undefined; + if (parentNoteId) { + const parentNote = await froca.getNote(parentNoteId); + childNotes = await parentNote?.getChildNotes(); + } setChildNotes(childNotes ?? []); })(); }, [ parentNoteId ]); From adc356eff33806e9e318a1567276123bb8620c0b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 16:41:06 +0200 Subject: [PATCH 008/873] fix(breadcrumb): navigation on first level not working --- apps/client/src/widgets/Breadcrumb.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 3adf46b0aa..945f55e643 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -47,7 +47,7 @@ function BreadcrumbSeparator({ notePath, noteContext }: { notePath: string, note function BreadcrumbSeparatorDropdownContent({ notePath, noteContext }: { notePath: string, noteContext: NoteContext | undefined }) { const notePathComponents = notePath.split("/"); - const parentNoteId = notePathComponents.pop(); + const parentNoteId = notePathComponents.length > 1 ? notePathComponents.pop() : "root"; const childNotes = useChildNotes(parentNoteId); const notePathPrefix = notePathComponents.join("/"); // last item was removed already. From bedca9f82c929759a937f86b601f027cdd842378 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 16:45:26 +0200 Subject: [PATCH 009/873] feat(breadcrumb): hide root icon --- apps/client/src/services/link.ts | 2 +- apps/client/src/widgets/Breadcrumb.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/services/link.ts b/apps/client/src/services/link.ts index a596e71366..79bc18c2ac 100644 --- a/apps/client/src/services/link.ts +++ b/apps/client/src/services/link.ts @@ -99,7 +99,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio const viewMode = viewScope.viewMode || "default"; let linkTitle = options.title; - if (!linkTitle) { + if (linkTitle === undefined) { if (viewMode === "attachments" && viewScope.attachmentId) { const attachment = await froca.getAttachment(viewScope.attachmentId); diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 945f55e643..18ec9e8456 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -24,10 +24,13 @@ export default function Breadcrumb() { } function BreadcrumbItem({ notePath }: { notePath: string }) { + const isRootNote = (notePath === "root"); return ( ) } From c5c4ecd6e63bc4e73dd7f7111d9fa274d76d565b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 17:04:08 +0200 Subject: [PATCH 010/873] feat(breadcrumb): show current item --- apps/client/src/widgets/Breadcrumb.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 18ec9e8456..1b3ae48881 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -73,7 +73,7 @@ function buildNotePaths(notePathArray: string[] | undefined) { let prefix = ""; const output: string[] = []; - for (const notePath of notePathArray.slice(0, notePathArray.length - 1)) { + for (const notePath of notePathArray) { output.push(`${prefix}${notePath}`); prefix += `${notePath}/`; } From 200fd76929129cb30d0ef687dc6742ba284ae0d0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 21:52:36 +0200 Subject: [PATCH 011/873] feat(breadcrumb): display a checkbox for the current note --- apps/client/src/widgets/Breadcrumb.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 1b3ae48881..a4874599db 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -13,10 +13,10 @@ export default function Breadcrumb() { return (
- {notePath.map(item => ( + {notePath.map((item, index) => ( - + ))}
@@ -35,7 +35,7 @@ function BreadcrumbItem({ notePath }: { notePath: string }) { ) } -function BreadcrumbSeparator({ notePath, noteContext }: { notePath: string, noteContext: NoteContext | undefined }) { +function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) { return ( } @@ -43,27 +43,29 @@ function BreadcrumbSeparator({ notePath, noteContext }: { notePath: string, note buttonClassName="icon-action" hideToggleArrow > - + ) } -function BreadcrumbSeparatorDropdownContent({ notePath, noteContext }: { notePath: string, noteContext: NoteContext | undefined }) { +function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) { const notePathComponents = notePath.split("/"); + const notePathPrefix = notePathComponents.join("/"); // last item was removed already. const parentNoteId = notePathComponents.length > 1 ? notePathComponents.pop() : "root"; const childNotes = useChildNotes(parentNoteId); - const notePathPrefix = notePathComponents.join("/"); // last item was removed already. return ( ) } From 223ba4643ff73e871c91c259e6dd5d80e466d92f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 21:57:51 +0200 Subject: [PATCH 012/873] fix(breadcrumb): breadcrumb shown if there are no children --- apps/client/src/widgets/Breadcrumb.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index a4874599db..a624e2e58a 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -8,7 +8,7 @@ import { FormListItem } from "./react/FormList"; import NoteContext from "../components/note_context"; export default function Breadcrumb() { - const { noteContext } = useNoteContext(); + const { note, noteContext } = useNoteContext(); const notePath = buildNotePaths(noteContext?.notePathArray); return ( @@ -16,7 +16,8 @@ export default function Breadcrumb() { {notePath.map((item, index) => ( - + {(index < notePath.length - 1 || note?.hasChildren()) && + } ))} From 1e5fcf635e2eb2a8148f7945c1472319a2ed246e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 22:05:09 +0200 Subject: [PATCH 013/873] feat(breadcrumb): show root title if it's the one active --- apps/client/src/widgets/Breadcrumb.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index a624e2e58a..e24704e0ac 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -15,7 +15,7 @@ export default function Breadcrumb() {
{notePath.map((item, index) => ( - + {(index < notePath.length - 1 || note?.hasChildren()) && } @@ -24,13 +24,13 @@ export default function Breadcrumb() { ) } -function BreadcrumbItem({ notePath }: { notePath: string }) { +function BreadcrumbItem({ notePath, activeNotePath }: { notePath: string, activeNotePath: string }) { const isRootNote = (notePath === "root"); return ( ) From 11467775b67b611c495524e9ae09a9f0c95f8883 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 22:09:46 +0200 Subject: [PATCH 014/873] feat(breadcrumb): basic overflow support --- apps/client/src/widgets/Breadcrumb.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 12ee1c759a..df9f5c6f06 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -5,10 +5,18 @@ align-items: center; font-size: 0.9em; gap: 0.25em; + flex-wrap: nowrap; + overflow: hidden; a { color: inherit; text-decoration: none; + width: 100%; + max-width: 200px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; } ul { From 3fe45db6ef972ae8b76752f7fd218996fb3aeb72 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 22:17:49 +0200 Subject: [PATCH 015/873] feat(breadcrumb): improve overflow support --- apps/client/src/widgets/Breadcrumb.css | 15 +++++++++++++-- apps/client/src/widgets/Breadcrumb.tsx | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index df9f5c6f06..fee2e97998 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -1,3 +1,7 @@ +.breadcrumb-row { + position: relative; +} + .component.breadcrumb { contain: none; margin: 0 10px; @@ -8,11 +12,18 @@ flex-wrap: nowrap; overflow: hidden; + > span, + > span > span { + display: flex; + align-items: center; + min-width: 0; + } + a { color: inherit; text-decoration: none; - width: 100%; - max-width: 200px; + min-width: 0; + max-width: 100px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index e24704e0ac..632d2c9719 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -43,6 +43,7 @@ function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePa noSelectButtonStyle buttonClassName="icon-action" hideToggleArrow + dropdownOptions={{ popperConfig: { strategy: "fixed" } }} > From 70ded4c2cd18729669f19c8f6855ac73ad8d8d96 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 22:38:06 +0200 Subject: [PATCH 016/873] chore(breadcrumb): use bold for highlighting active entry --- apps/client/src/widgets/Breadcrumb.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 632d2c9719..8f09fdd2b8 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -64,8 +64,11 @@ function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNoteP noteContext?.setNote(childNotePath)} - checked={childNotePath === activeNotePath} - >{note.title} + > + {childNotePath !== activeNotePath + ? note.title + : {note.title}} + })} From 4cfa40365758629e95097ef4fd608ce3519d3a72 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 22:40:37 +0200 Subject: [PATCH 017/873] feat(breadcrumb): apply ellipsis to dropdown --- apps/client/src/widgets/Breadcrumb.css | 9 +++++++++ apps/client/src/widgets/Breadcrumb.tsx | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index fee2e97998..c64b6b5171 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -36,4 +36,13 @@ margin: 0; padding: 0; } + + .dropdown-item span, + .dropdown-item strong { + max-width: 200px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; + } } diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 8f09fdd2b8..bf26fef044 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -66,7 +66,7 @@ function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNoteP onClick={() => noteContext?.setNote(childNotePath)} > {childNotePath !== activeNotePath - ? note.title + ? {note.title} : {note.title}} From eca2116adc54d7678152eb955f9f1f4b6c0fbc77 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 22:46:04 +0200 Subject: [PATCH 018/873] feat(breadcrumb): make the root note clickable --- apps/client/src/widgets/Breadcrumb.tsx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index bf26fef044..1c4f6a4dc0 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -1,11 +1,14 @@ import { Fragment } from "preact/jsx-runtime"; import "./Breadcrumb.css"; -import { useChildNotes, useNoteContext } from "./react/hooks"; +import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty, useStaticTooltip } from "./react/hooks"; import NoteLink from "./react/NoteLink"; import Dropdown from "./react/Dropdown"; import Icon from "./react/Icon"; import { FormListItem } from "./react/FormList"; import NoteContext from "../components/note_context"; +import ActionButton from "./react/ActionButton"; +import { useMemo } from "preact/hooks"; +import froca from "../services/froca"; export default function Breadcrumb() { const { note, noteContext } = useNoteContext(); @@ -15,7 +18,10 @@ export default function Breadcrumb() {
{notePath.map((item, index) => ( - + {index === 0 && notePath.length > 1 + ? + : + } {(index < notePath.length - 1 || note?.hasChildren()) && } @@ -24,6 +30,20 @@ export default function Breadcrumb() { ) } +function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined }) { + const note = useMemo(() => froca.getNoteFromCache("root"), []); + useNoteLabel(note, "iconClass"); + const title = useNoteProperty(note, "title"); + + return (note && + noteContext?.setNote("root")} + /> + ) +} + function BreadcrumbItem({ notePath, activeNotePath }: { notePath: string, activeNotePath: string }) { const isRootNote = (notePath === "root"); return ( From a365814aaa451de22f017392730da759a64d7093 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 22:54:31 +0200 Subject: [PATCH 019/873] feat(breadcrumb): improve overflow slightly --- apps/client/src/widgets/Breadcrumb.css | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index c64b6b5171..810d547975 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -11,23 +11,30 @@ gap: 0.25em; flex-wrap: nowrap; overflow: hidden; + max-width: 85%; > span, > span > span { display: flex; align-items: center; min-width: 0; + + a { + color: inherit; + text-decoration: none; + min-width: 0; + max-width: 150px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; + flex-shrink: 2; + } } - a { - color: inherit; - text-decoration: none; - min-width: 0; - max-width: 100px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - display: block; + > span:last-of-type a { + max-width: 300px; + flex-shrink: 1; } ul { @@ -39,7 +46,6 @@ .dropdown-item span, .dropdown-item strong { - max-width: 200px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; From b16893c4d2f5349b94a0e5a7d4ef951ca13622da Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 23:02:04 +0200 Subject: [PATCH 020/873] feat(breadcrumb): collapse items if the list is too big --- apps/client/src/widgets/Breadcrumb.css | 1 + apps/client/src/widgets/Breadcrumb.tsx | 67 +++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 810d547975..7a78982b21 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -50,5 +50,6 @@ white-space: nowrap; overflow: hidden; display: block; + max-width: 300px; } } diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 1c4f6a4dc0..940f624058 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -10,22 +10,47 @@ import ActionButton from "./react/ActionButton"; import { useMemo } from "preact/hooks"; import froca from "../services/froca"; +const COLLAPSE_THRESHOLD = 5; +const INITIAL_ITEMS = 2; +const FINAL_ITEMS = 2; + export default function Breadcrumb() { const { note, noteContext } = useNoteContext(); const notePath = buildNotePaths(noteContext?.notePathArray); return (
- {notePath.map((item, index) => ( + {notePath.length > COLLAPSE_THRESHOLD ? ( + <> + {notePath.slice(0, INITIAL_ITEMS).map((item, index) => ( {index === 0 && notePath.length > 1 - ? - : + ? + : } - {(index < notePath.length - 1 || note?.hasChildren()) && - } + - ))} + ))} + + {notePath.slice(-FINAL_ITEMS).map((item, index) => ( + + + + + ))} + + ) : ( + notePath.map((item, index) => ( + + {index === 0 && notePath.length > 1 + ? + : + } + {(index < notePath.length - 1 || note?.hasChildren()) && + } + + )) + )}
) } @@ -95,6 +120,36 @@ function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNoteP ) } +function BreadcrumbCollapsed({ items, noteContext }: { items: string[], noteContext: NoteContext | undefined }) { + return ( + } + noSelectButtonStyle + buttonClassName="icon-action" + hideToggleArrow + dropdownOptions={{ popperConfig: { strategy: "fixed" } }} + > + + + ) +} + function buildNotePaths(notePathArray: string[] | undefined) { if (!notePathArray) return []; From ef3cbcac6db395681a3579ea5390dc977ad36162 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 23:09:00 +0200 Subject: [PATCH 021/873] refactor(breadcrumb): fix eslint issues --- apps/client/src/widgets/Breadcrumb.tsx | 98 +++++++++++++------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 940f624058..29d5cc7f0e 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -1,14 +1,16 @@ -import { Fragment } from "preact/jsx-runtime"; import "./Breadcrumb.css"; -import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty, useStaticTooltip } from "./react/hooks"; -import NoteLink from "./react/NoteLink"; -import Dropdown from "./react/Dropdown"; -import Icon from "./react/Icon"; -import { FormListItem } from "./react/FormList"; -import NoteContext from "../components/note_context"; -import ActionButton from "./react/ActionButton"; + import { useMemo } from "preact/hooks"; +import { Fragment } from "preact/jsx-runtime"; + +import NoteContext from "../components/note_context"; import froca from "../services/froca"; +import ActionButton from "./react/ActionButton"; +import Dropdown from "./react/Dropdown"; +import { FormListItem } from "./react/FormList"; +import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty } from "./react/hooks"; +import Icon from "./react/Icon"; +import NoteLink from "./react/NoteLink"; const COLLAPSE_THRESHOLD = 5; const INITIAL_ITEMS = 2; @@ -21,38 +23,38 @@ export default function Breadcrumb() { return (
{notePath.length > COLLAPSE_THRESHOLD ? ( - <> - {notePath.slice(0, INITIAL_ITEMS).map((item, index) => ( - - {index === 0 && notePath.length > 1 - ? - : - } - - - ))} - - {notePath.slice(-FINAL_ITEMS).map((item, index) => ( - - - - - ))} - + <> + {notePath.slice(0, INITIAL_ITEMS).map((item, index) => ( + + {index === 0 && notePath.length > 1 + ? + : + } + + + ))} + + {notePath.slice(-FINAL_ITEMS).map((item, index) => ( + + + + + ))} + ) : ( - notePath.map((item, index) => ( - - {index === 0 && notePath.length > 1 - ? - : - } - {(index < notePath.length - 1 || note?.hasChildren()) && - } - - )) + notePath.map((item, index) => ( + + {index === 0 && notePath.length > 1 + ? + : + } + {(index < notePath.length - 1 || note?.hasChildren()) && + } + + )) )}
- ) + ); } function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined }) { @@ -66,7 +68,7 @@ function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined text={title ?? ""} onClick={() => noteContext?.setNote("root")} /> - ) + ); } function BreadcrumbItem({ notePath, activeNotePath }: { notePath: string, activeNotePath: string }) { @@ -78,7 +80,7 @@ function BreadcrumbItem({ notePath, activeNotePath }: { notePath: string, active title={isRootNote && activeNotePath !== "root" ? "" : undefined} showNoteIcon={isRootNote} /> - ) + ); } function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) { @@ -92,7 +94,7 @@ function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePa > - ) + ); } function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) { @@ -104,20 +106,20 @@ function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNoteP return ( - ) + ); } function BreadcrumbCollapsed({ items, noteContext }: { items: string[], noteContext: NoteContext | undefined }) { @@ -143,11 +145,11 @@ function BreadcrumbCollapsed({ items, noteContext }: { items: string[], noteCont > {note.title} - + ; })} - ) + ); } function buildNotePaths(notePathArray: string[] | undefined) { From d15b5f8cbc34532b06dee4318878db63cdc606a1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 23:13:58 +0200 Subject: [PATCH 022/873] style(next): basic styling of ribbon as a floating toolbar --- .../src/stylesheets/theme-next/ribbon.css | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next/ribbon.css b/apps/client/src/stylesheets/theme-next/ribbon.css index 3718602cf1..3101339be4 100644 --- a/apps/client/src/stylesheets/theme-next/ribbon.css +++ b/apps/client/src/stylesheets/theme-next/ribbon.css @@ -164,7 +164,7 @@ ul.editability-dropdown li.dropdown-item > div { background: var(--cmd-button-hover-background-color); } -/* +/* * Note info */ @@ -177,4 +177,19 @@ ul.editability-dropdown li.dropdown-item > div { /* Narrow width layout */ .note-info-widget { container: info-section / inline-size; -} \ No newline at end of file +} + +/* + * Styling as a floating toolbar + */ +.ribbon-container { + background: var(--card-background-color); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); + margin: 10px; + border-radius: 6px; + padding: 5px 0; + + .ribbon-body { + border-bottom: 0; + } +} From fcf51ec6da9d2cd974bcbca21ef6926aef33e69f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 23:14:09 +0200 Subject: [PATCH 023/873] chore(eslint): apply to .tsx as well --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 3a2254d69b..8d9119044c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -63,7 +63,7 @@ const mainConfig = [ } }, { - files: ["**/*.{js,ts,mjs,cjs}"], + files: ["**/*.{js,ts,mjs,cjs,tsx}"], languageOptions: { parser: tsParser From 05679f7a8db8ea960a105f2727092a92246fd2a5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 23:33:55 +0200 Subject: [PATCH 024/873] feat(ribbon): prototype sticky ribbon --- apps/client/src/layouts/desktop_layout.tsx | 2 +- apps/client/src/stylesheets/theme-next/ribbon.css | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 0347acf1e4..7464844dc2 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -130,7 +130,6 @@ export default class DesktopLayout { .child() .child() ) - .child() .child(new WatchedFileUpdateStatusWidget()) .child() .child( @@ -150,6 +149,7 @@ export default class DesktopLayout { .child() .child() ) + .child() .child() .child() .child() diff --git a/apps/client/src/stylesheets/theme-next/ribbon.css b/apps/client/src/stylesheets/theme-next/ribbon.css index 3101339be4..a518e05dc2 100644 --- a/apps/client/src/stylesheets/theme-next/ribbon.css +++ b/apps/client/src/stylesheets/theme-next/ribbon.css @@ -183,13 +183,10 @@ ul.editability-dropdown li.dropdown-item > div { * Styling as a floating toolbar */ .ribbon-container { - background: var(--card-background-color); - box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); - margin: 10px; - border-radius: 6px; - padding: 5px 0; - - .ribbon-body { - border-bottom: 0; - } + position: sticky; + top: 0; + left: 0; + right: 0; + background: var(--main-background-color); + z-index: 997; } From 608ab539338c7fc74ba09db41f7d9c72b7caa7ec Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 23:41:17 +0200 Subject: [PATCH 025/873] chore(ribbon): reduce note title padding --- apps/client/src/layouts/desktop_layout.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 7464844dc2..725e050ba7 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -139,7 +139,6 @@ export default class DesktopLayout { .child(new FlexContainer("row") .class("title-row") .css("height", "50px") - .css("margin", "1em") .css("min-height", "50px") .css("align-items", "center") .cssBlock(".title-row > * { margin: 5px; }") From 5973e5ca26fe350d411bdc7801e68aa9be1c9d6c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 07:45:38 +0200 Subject: [PATCH 026/873] chore(ribbon): remove label for the root entirely --- apps/client/src/widgets/Breadcrumb.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 29d5cc7f0e..0c08cf160b 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -26,7 +26,7 @@ export default function Breadcrumb() { <> {notePath.slice(0, INITIAL_ITEMS).map((item, index) => ( - {index === 0 && notePath.length > 1 + {index === 0 ? : } @@ -44,7 +44,7 @@ export default function Breadcrumb() { ) : ( notePath.map((item, index) => ( - {index === 0 && notePath.length > 1 + {index === 0 ? : } From 6fac947d9c76bc16669e5d858f9d1d1890f0e896 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 07:50:21 +0200 Subject: [PATCH 027/873] chore(ribbon): address requested changes --- apps/client/src/widgets/Breadcrumb.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 0c08cf160b..24d2ba9d4f 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -99,8 +99,8 @@ function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePa function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) { const notePathComponents = notePath.split("/"); - const notePathPrefix = notePathComponents.join("/"); // last item was removed already. - const parentNoteId = notePathComponents.length > 1 ? notePathComponents.pop() : "root"; + const notePathPrefix = notePathComponents.join("/"); + const parentNoteId = notePathComponents.at(-1); const childNotes = useChildNotes(parentNoteId); return ( From 7377e4e34d4475a4daa57414bf0084607ce24598 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 07:58:59 +0200 Subject: [PATCH 028/873] chore(ribbon): improve paddings slightly --- apps/client/src/layouts/desktop_layout.tsx | 1 + apps/client/src/widgets/Breadcrumb.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 725e050ba7..8cef9fe2ad 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -122,6 +122,7 @@ export default class DesktopLayout { .css("height", "30px") .css("min-height", "30px") .css("align-items", "center") + .css("padding", "10px") .cssBlock(".breadcrumb-row > * { margin: 5px; }") .child() .child() diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 7a78982b21..9739f33db1 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -4,8 +4,8 @@ .component.breadcrumb { contain: none; - margin: 0 10px; display: flex; + margin: 0; align-items: center; font-size: 0.9em; gap: 0.25em; From 6b059a9a75d5401dacc56388ea38a19dd29e33e3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 08:01:52 +0200 Subject: [PATCH 029/873] feat(ribbon): context menu for root item --- apps/client/src/widgets/Breadcrumb.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 24d2ba9d4f..5af9caabe5 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -11,6 +11,7 @@ import { FormListItem } from "./react/FormList"; import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty } from "./react/hooks"; import Icon from "./react/Icon"; import NoteLink from "./react/NoteLink"; +import link_context_menu from "../menus/link_context_menu"; const COLLAPSE_THRESHOLD = 5; const INITIAL_ITEMS = 2; @@ -67,6 +68,10 @@ function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined icon={note.getIcon()} text={title ?? ""} onClick={() => noteContext?.setNote("root")} + onContextMenu={(e) => { + e.preventDefault(); + link_context_menu.openContextMenu(note.noteId, e); + }} /> ); } From 0805e077a19d8b2261fdafbd9c8c82afd3d95263 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 08:18:27 +0200 Subject: [PATCH 030/873] feat(ribbon): basic implementation for scroll pinning --- .../src/widgets/containers/content_header.css | 18 +++++++++ .../src/widgets/containers/content_header.ts | 39 +++++++++++++++++-- apps/client/src/widgets/ribbon/style.css | 12 +++++- 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 apps/client/src/widgets/containers/content_header.css diff --git a/apps/client/src/widgets/containers/content_header.css b/apps/client/src/widgets/containers/content_header.css new file mode 100644 index 0000000000..322c88da9c --- /dev/null +++ b/apps/client/src/widgets/containers/content_header.css @@ -0,0 +1,18 @@ +.content-header-widget { + position: relative; + transition: position 0.3s ease, box-shadow 0.3s ease, z-index 0.3s ease; + z-index: 8; +} + +.content-header-widget.floating { + position: sticky; + top: 0; + z-index: 11; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + background-color: var(--main-background-color, #fff); +} + +/* Ensure content inside doesn't get affected by the floating state */ +.content-header-widget > * { + transition: inherit; +} diff --git a/apps/client/src/widgets/containers/content_header.ts b/apps/client/src/widgets/containers/content_header.ts index ac001d40a8..96b9ba5b88 100644 --- a/apps/client/src/widgets/containers/content_header.ts +++ b/apps/client/src/widgets/containers/content_header.ts @@ -2,15 +2,19 @@ import { EventData } from "../../components/app_context"; import BasicWidget from "../basic_widget"; import Container from "./container"; import NoteContext from "../../components/note_context"; +import "./content_header.css"; export default class ContentHeader extends Container { - + noteContext?: NoteContext; thisElement?: HTMLElement; parentElement?: HTMLElement; resizeObserver: ResizeObserver; currentHeight: number = 0; currentSafeMargin: number = NaN; + previousScrollTop: number = 0; + isFloating: boolean = false; + scrollThreshold: number = 10; // pixels before triggering float constructor() { super(); @@ -35,7 +39,36 @@ export default class ContentHeader extends Container { this.thisElement = this.$widget.get(0)!; this.resizeObserver.observe(this.thisElement); - this.parentElement.addEventListener("scroll", this.updateSafeMargin.bind(this)); + this.parentElement.addEventListener("scroll", this.updateScrollState.bind(this), { passive: true }); + } + + updateScrollState() { + const currentScrollTop = this.parentElement!.scrollTop; + const isScrollingUp = currentScrollTop < this.previousScrollTop; + const hasMovedEnough = Math.abs(currentScrollTop - this.previousScrollTop) > this.scrollThreshold; + + if (hasMovedEnough) { + this.setFloating(isScrollingUp); + this.previousScrollTop = currentScrollTop; + } + + this.updateSafeMargin(); + } + + setFloating(shouldFloat: boolean) { + if (shouldFloat !== this.isFloating) { + this.isFloating = shouldFloat; + + if (shouldFloat) { + this.$widget.addClass("floating"); + // Set CSS variable so ribbon can position itself below the floating header + this.parentElement!.style.setProperty("--content-header-height", `${this.currentHeight}px`); + } else { + this.$widget.removeClass("floating"); + // Reset CSS variable when header is not floating + this.parentElement!.style.removeProperty("--content-header-height"); + } + } } updateSafeMargin() { @@ -60,4 +93,4 @@ export default class ContentHeader extends Container { } } -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 290d1b30e9..a14d5bc967 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -1,5 +1,15 @@ .ribbon-container { margin-bottom: 5px; + position: relative; + transition: position 0.3s ease, z-index 0.3s ease, top 0.3s ease; + z-index: 8; +} + +/* When content header is floating, ribbon sticks below it */ +.scrolling-container:has(.content-header-widget.floating) .ribbon-container { + position: sticky; + top: var(--content-header-height, 100px); + z-index: 10; } .ribbon-top-row { @@ -404,4 +414,4 @@ body[dir=rtl] .attribute-list-editor { background-color: transparent !important; pointer-events: none; /* makes it unclickable */ } -/* #endregion */ \ No newline at end of file +/* #endregion */ From 474228b63068c4c524e729a62b3f06d6d419815e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 08:22:51 +0200 Subject: [PATCH 031/873] style(client): remove bottom border & box-shadow for content header --- apps/client/src/widgets/containers/content_header.css | 1 - apps/client/src/widgets/react/InfoBar.css | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/client/src/widgets/containers/content_header.css b/apps/client/src/widgets/containers/content_header.css index 322c88da9c..97b2c965cc 100644 --- a/apps/client/src/widgets/containers/content_header.css +++ b/apps/client/src/widgets/containers/content_header.css @@ -8,7 +8,6 @@ position: sticky; top: 0; z-index: 11; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); background-color: var(--main-background-color, #fff); } diff --git a/apps/client/src/widgets/react/InfoBar.css b/apps/client/src/widgets/react/InfoBar.css index cf9774e0b4..4a7a859dbd 100644 --- a/apps/client/src/widgets/react/InfoBar.css +++ b/apps/client/src/widgets/react/InfoBar.css @@ -17,7 +17,6 @@ .info-bar-subtle { color: var(--muted-text-color); background: var(--main-background-color); - border-bottom: 1px solid var(--main-border-color); margin-block: 0; padding-inline: 22px; -} \ No newline at end of file +} From 7fc3d413e5a973c50fae9198084a227a762e6c72 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 09:13:18 +0200 Subject: [PATCH 032/873] fix(client): 1px scroll in full-height note --- apps/client/src/stylesheets/style.css | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index f2f5546296..ce76ad286e 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -24,8 +24,8 @@ --bs-body-font-family: var(--main-font-family) !important; --bs-body-font-weight: var(--main-font-weight) !important; --bs-body-color: var(--main-text-color) !important; - --bs-body-bg: var(--main-background-color) !important; - --ck-mention-list-max-height: 500px; + --bs-body-bg: var(--main-background-color) !important; + --ck-mention-list-max-height: 500px; --tn-modal-max-height: 90vh; --tree-item-light-theme-max-color-lightness: 50; @@ -471,7 +471,7 @@ body.mobile .dropdown .dropdown-submenu > span { padding-inline-start: 12px; } -.dropdown-menu kbd { +.dropdown-menu kbd { color: var(--muted-text-color); border: none; background-color: transparent; @@ -487,7 +487,7 @@ body.mobile .dropdown .dropdown-submenu > span { border: 1px solid transparent !important; } -/* This is a workaround for Firefox not supporting break-before / break-after: avoid on columns. +/* This is a workaround for Firefox not supporting break-before / break-after: avoid on columns. * It usually wraps a menu item followed by a separator / header and another menu item. */ .dropdown-no-break { break-inside: avoid; @@ -1591,7 +1591,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { inset-inline-start: 0; inset-inline-end: 0; margin: 0 !important; - max-height: 85vh; + max-height: 85vh; display: flex; } @@ -2093,7 +2093,7 @@ body.zen .note-split.type-text .scrolling-container { body.zen:not(.backdrop-effects-disabled) .note-split.type-text .scrolling-container { --padding-top: 50px; /* Should be enough to cover the title row */ - + padding-top: var(--padding-top); scroll-padding-top: var(--padding-top); } @@ -2365,7 +2365,7 @@ footer.webview-footer button { margin-bottom: 0; } -.admonition::before { +.admonition::before { color: var(--accent-color); font-family: boxicons !important; position: absolute; @@ -2391,7 +2391,7 @@ footer.webview-footer button { .ck-content ul.todo-list li:has(> span.todo-list__label input[type="checkbox"]:checked) > span.todo-list__label span.todo-list__label__description { text-decoration: line-through; - opacity: 0.6; + opacity: 0.6; } .chat-options-container { @@ -2524,6 +2524,7 @@ iframe.print-iframe { position: relative; flex-grow: 1; width: 100%; + overflow: hidden; } /* Calendar collection */ @@ -2538,7 +2539,7 @@ iframe.print-iframe { body.mobile { .split-note-container-widget { flex-direction: column !important; - + .note-split { width: 100%; } @@ -2553,4 +2554,4 @@ iframe.print-iframe { opacity: 0.4; } } -} \ No newline at end of file +} From d5d2815bdfd89c97c4d5b684342b7e33ced0ac48 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 09:20:31 +0200 Subject: [PATCH 033/873] fix(client/floating_buttons): clipped by ribbon --- apps/client/src/widgets/FloatingButtons.css | 2 +- apps/client/src/widgets/ribbon/Ribbon.tsx | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/FloatingButtons.css b/apps/client/src/widgets/FloatingButtons.css index b61df46daf..dbed6bdf7d 100644 --- a/apps/client/src/widgets/FloatingButtons.css +++ b/apps/client/src/widgets/FloatingButtons.css @@ -6,7 +6,7 @@ .floating-buttons-children, .show-floating-buttons { position: absolute; - top: var(--floating-buttons-vert-offset, 14px); + top: calc(var(--floating-buttons-vert-offset, 14px) + var(--ribbon-height, 0)); inset-inline-end: var(--floating-buttons-horiz-offset, 10px); display: flex; flex-direction: row; diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index 60b2e2667f..55ade760ce 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks"; -import { useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks"; +import { useElementSize, useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks"; import "./style.css"; import { Indexed, numberObjectsInPlace } from "../../services/utils"; @@ -42,6 +42,16 @@ export default function Ribbon() { refresh(); }, [ note, noteType, isReadOnlyTemporarilyDisabled ]); + // Manage height. + const containerRef = useRef(null); + const size = useElementSize(containerRef); + useEffect(() => { + if (!containerRef.current || !size) return; + const parentEl = containerRef.current.closest(".note-split"); + if (!parentEl) return; + parentEl.style.setProperty("--ribbon-height", `${size.height}px`); + }, [ size ]); + // Automatically activate the first ribbon tab that needs to be activated whenever a note changes. useEffect(() => { if (!computedTabs) return; @@ -65,6 +75,7 @@ export default function Ribbon() { return (
From 5770222304e486171d45249e7335f15a2feff84d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 11:08:28 +0200 Subject: [PATCH 034/873] fix(client/floating_buttons): clipped by floating content header --- apps/client/src/widgets/FloatingButtons.css | 3 ++- apps/client/src/widgets/containers/content_header.ts | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/FloatingButtons.css b/apps/client/src/widgets/FloatingButtons.css index dbed6bdf7d..46ed6dd802 100644 --- a/apps/client/src/widgets/FloatingButtons.css +++ b/apps/client/src/widgets/FloatingButtons.css @@ -6,11 +6,12 @@ .floating-buttons-children, .show-floating-buttons { position: absolute; - top: calc(var(--floating-buttons-vert-offset, 14px) + var(--ribbon-height, 0)); + top: calc(var(--floating-buttons-vert-offset, 14px) + var(--ribbon-height, 0px) + var(--content-header-height, 0px)); inset-inline-end: var(--floating-buttons-horiz-offset, 10px); display: flex; flex-direction: row; z-index: 100; + transition: top 0.3s ease; } .note-split.rtl .floating-buttons-children, diff --git a/apps/client/src/widgets/containers/content_header.ts b/apps/client/src/widgets/containers/content_header.ts index 96b9ba5b88..0bfdd8b3e7 100644 --- a/apps/client/src/widgets/containers/content_header.ts +++ b/apps/client/src/widgets/containers/content_header.ts @@ -59,14 +59,13 @@ export default class ContentHeader extends Container { if (shouldFloat !== this.isFloating) { this.isFloating = shouldFloat; + const parentEl = this.parentElement?.closest(".note-split"); if (shouldFloat) { this.$widget.addClass("floating"); - // Set CSS variable so ribbon can position itself below the floating header - this.parentElement!.style.setProperty("--content-header-height", `${this.currentHeight}px`); + parentEl!.style.setProperty("--content-header-height", `${this.currentHeight}px`); } else { this.$widget.removeClass("floating"); - // Reset CSS variable when header is not floating - this.parentElement!.style.removeProperty("--content-header-height"); + parentEl!.style.removeProperty("--content-header-height"); } } } From bfcf85e0d2c18d7a22ef0e2ed1a2b6ee04ba06e3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 11:26:15 +0200 Subject: [PATCH 035/873] fix(client/layout): content header height not properly resized when switching notes --- apps/client/src/components/app_context.ts | 4 ---- apps/client/src/widgets/FloatingButtons.tsx | 10 ++-------- .../src/widgets/containers/content_header.ts | 17 +++++------------ 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index 5cd4eecbe0..bd6f0473a3 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -498,10 +498,6 @@ type EventMappings = { noteIds: string[]; }; refreshData: { ntxId: string | null | undefined }; - contentSafeMarginChanged: { - top: number; - noteContext: NoteContext; - } }; export type EventListener = { diff --git a/apps/client/src/widgets/FloatingButtons.tsx b/apps/client/src/widgets/FloatingButtons.tsx index f38039afd6..85b6d58178 100644 --- a/apps/client/src/widgets/FloatingButtons.tsx +++ b/apps/client/src/widgets/FloatingButtons.tsx @@ -48,12 +48,6 @@ export default function FloatingButtons({ items }: FloatingButtonsProps) { const [ visible, setVisible ] = useState(true); useEffect(() => setVisible(true), [ note ]); - useTriliumEvent("contentSafeMarginChanged", (e) => { - if (e.noteContext === noteContext) { - setTop(e.top); - } - }); - return (
@@ -93,9 +87,9 @@ function CloseFloatingButton({ setVisible }: { setVisible(visible: boolean): voi className="close-floating-buttons-button" icon="bx bx-chevrons-right" text={t("hide_floating_buttons_button.button_title")} - onClick={() => setVisible(false)} + onClick={() => setVisible(false)} noIconActionClass />
); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/containers/content_header.ts b/apps/client/src/widgets/containers/content_header.ts index 0bfdd8b3e7..c4b38bbfd7 100644 --- a/apps/client/src/widgets/containers/content_header.ts +++ b/apps/client/src/widgets/containers/content_header.ts @@ -59,27 +59,20 @@ export default class ContentHeader extends Container { if (shouldFloat !== this.isFloating) { this.isFloating = shouldFloat; - const parentEl = this.parentElement?.closest(".note-split"); if (shouldFloat) { this.$widget.addClass("floating"); - parentEl!.style.setProperty("--content-header-height", `${this.currentHeight}px`); } else { this.$widget.removeClass("floating"); - parentEl!.style.removeProperty("--content-header-height"); } } } updateSafeMargin() { - const newSafeMargin = Math.max(this.currentHeight - this.parentElement!.scrollTop, 0); - - if (newSafeMargin !== this.currentSafeMargin) { - this.currentSafeMargin = newSafeMargin; - - this.triggerEvent("contentSafeMarginChanged", { - top: newSafeMargin, - noteContext: this.noteContext! - }); + const parentEl = this.parentElement?.closest(".note-split"); + if (this.isFloating) { + parentEl!.style.setProperty("--content-header-height", `${this.currentHeight}px`); + } else { + parentEl!.style.removeProperty("--content-header-height"); } } From 895e9b8bf02eb0f8cd1766f1b33329c31b5b5484 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 11:30:18 +0200 Subject: [PATCH 036/873] chore(client/layout): reduce transitions --- apps/client/src/widgets/containers/content_header.css | 6 ------ apps/client/src/widgets/ribbon/style.css | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/client/src/widgets/containers/content_header.css b/apps/client/src/widgets/containers/content_header.css index 97b2c965cc..060abde126 100644 --- a/apps/client/src/widgets/containers/content_header.css +++ b/apps/client/src/widgets/containers/content_header.css @@ -1,6 +1,5 @@ .content-header-widget { position: relative; - transition: position 0.3s ease, box-shadow 0.3s ease, z-index 0.3s ease; z-index: 8; } @@ -10,8 +9,3 @@ z-index: 11; background-color: var(--main-background-color, #fff); } - -/* Ensure content inside doesn't get affected by the floating state */ -.content-header-widget > * { - transition: inherit; -} diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index a14d5bc967..95c8791eb9 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -1,7 +1,7 @@ .ribbon-container { margin-bottom: 5px; position: relative; - transition: position 0.3s ease, z-index 0.3s ease, top 0.3s ease; + transition: top 0.2s ease; z-index: 8; } From 8df5a010c92fb5992e5b8459991489c6f5221264 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 11:36:00 +0200 Subject: [PATCH 037/873] fix(ribbon): note buttons cut off --- apps/client/src/widgets/ribbon/style.css | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 95c8791eb9..752a268633 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -81,12 +81,9 @@ display: flex; border-bottom: 1px solid var(--main-border-color); margin-inline-end: 5px; -} - -.ribbon-button-container > * { - position: relative; - top: -3px; - margin-inline-start: 10px; + align-items: center; + height: 36px; + gap: 10px; } .ribbon-body { @@ -396,6 +393,8 @@ body[dir=rtl] .attribute-list-editor { .note-actions { width: 35px; height: 35px; + display: flex; + align-items: center; } .note-actions .dropdown-menu { From fb6c82740cc24c44651581fcc0904cfee4ef327f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 11:38:04 +0200 Subject: [PATCH 038/873] chore(ribbon): remove top transition completely --- apps/client/src/widgets/ribbon/style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 752a268633..bfe964a2f6 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -1,7 +1,6 @@ .ribbon-container { margin-bottom: 5px; position: relative; - transition: top 0.2s ease; z-index: 8; } From 3514e3d0577edc1cdc98278c596016cca7229ef1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 11:46:16 +0200 Subject: [PATCH 039/873] fix(floating_buttons): wrong position when at the top of the note --- apps/client/src/widgets/containers/content_header.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/containers/content_header.ts b/apps/client/src/widgets/containers/content_header.ts index c4b38bbfd7..17ec4651f1 100644 --- a/apps/client/src/widgets/containers/content_header.ts +++ b/apps/client/src/widgets/containers/content_header.ts @@ -69,7 +69,7 @@ export default class ContentHeader extends Container { updateSafeMargin() { const parentEl = this.parentElement?.closest(".note-split"); - if (this.isFloating) { + if (this.isFloating || this.parentElement!.scrollTop === 0) { parentEl!.style.setProperty("--content-header-height", `${this.currentHeight}px`); } else { parentEl!.style.removeProperty("--content-header-height"); From 19980807f25e318072c462259b94bea50c4bb960 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 12:16:14 +0200 Subject: [PATCH 040/873] style(ribbon): fix some alignment issues & decrease button size --- apps/client/src/widgets/ribbon/style.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index bfe964a2f6..07619c89db 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -33,12 +33,14 @@ max-width: max-content; flex-grow: 10; user-select: none; + display: flex; + align-items: center; + font-size: 0.9em; } .ribbon-tab-title .bx { - font-size: 150%; + font-size: 125%; position: relative; - top: 3px; } .ribbon-tab-title.active { From 72b0d03546d6834740a59156252801b459871b32 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 12:23:05 +0200 Subject: [PATCH 041/873] chore(layout): remove some title margins --- apps/client/src/layouts/desktop_layout.tsx | 1 - apps/client/src/widgets/note_icon.css | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 8cef9fe2ad..41a6bb68fc 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -142,7 +142,6 @@ export default class DesktopLayout { .css("height", "50px") .css("min-height", "50px") .css("align-items", "center") - .cssBlock(".title-row > * { margin: 5px; }") .child() .child() ) diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index a3be50dc66..2163de8e57 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -1,5 +1,5 @@ .note-icon-widget { - padding-inline-start: 7px; + padding-inline-start: 10px; margin-inline-end: 0; width: 50px; height: 50px; @@ -13,7 +13,7 @@ cursor: pointer; color: var(--muted-text-color); } - + .note-icon-widget button.note-icon:disabled { cursor: default; opacity: .75; @@ -68,4 +68,4 @@ border: 1px dashed var(--muted-text-color); width: 1em; height: 1em; -} \ No newline at end of file +} From 658b699b717b1c74d287ca10a29aaffba0c59799 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 12:24:28 +0200 Subject: [PATCH 042/873] fix(collections/geomap): fake floating buttons mispositioned --- apps/client/src/widgets/collections/presentation/index.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/index.css b/apps/client/src/widgets/collections/presentation/index.css index 5aafffd9f0..ac3d628c39 100644 --- a/apps/client/src/widgets/collections/presentation/index.css +++ b/apps/client/src/widgets/collections/presentation/index.css @@ -2,9 +2,13 @@ position: absolute; top: 1em; right: 1em; + + .floating-buttons-children { + top: 0; + } } .presentation-container { width: 100%; height: 100%; -} \ No newline at end of file +} From 4d752219387e06f1f52f0e7337f8baff9a4d3e30 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Dec 2025 12:41:54 +0200 Subject: [PATCH 043/873] chore(breadcrumbs): address requested changes --- apps/client/src/widgets/Breadcrumb.tsx | 16 ++++++---------- .../src/widgets/containers/content_header.ts | 3 +-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 5af9caabe5..c3ec935048 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -38,7 +38,7 @@ export default function Breadcrumb() { {notePath.slice(-FINAL_ITEMS).map((item, index) => ( - + ))} @@ -47,7 +47,7 @@ export default function Breadcrumb() { {index === 0 ? - : + : } {(index < notePath.length - 1 || note?.hasChildren()) && } @@ -76,14 +76,11 @@ function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined ); } -function BreadcrumbItem({ notePath, activeNotePath }: { notePath: string, activeNotePath: string }) { - const isRootNote = (notePath === "root"); +function BreadcrumbItem({ notePath }: { notePath: string }) { return ( ); } @@ -104,14 +101,13 @@ function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePa function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) { const notePathComponents = notePath.split("/"); - const notePathPrefix = notePathComponents.join("/"); const parentNoteId = notePathComponents.at(-1); const childNotes = useChildNotes(parentNoteId); return ( -
- ) -} \ No newline at end of file + ); +} From b39a6bcc97cfd7672da295dbf19c2d6494430339 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:17:39 +0200 Subject: [PATCH 128/873] feat(widgets): prevent clicks in toggle from dismissing menu --- apps/client/src/widgets/react/FormList.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 3f81472414..95a1052621 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -139,8 +139,12 @@ export function FormListToggleableItem({ title, currentValue, onChange, ...props onChange(newValue: boolean): void; }) { return ( - - + e.stopPropagation()}> + ); } From f18ac3a9234a912eb2657d9593193fcb6dcd0c55 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:20:23 +0200 Subject: [PATCH 129/873] feat(note_actions): integrate bookmark into new layout --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 54 +++++++++++-------- .../client/src/widgets/ribbon/NoteActions.tsx | 14 ++++- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 11a8432334..dc14284f3a 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -21,6 +21,9 @@ import Modal from "../react/Modal"; import { CodeMimeTypesList } from "../type_widgets/options/code_notes"; import { ContentLanguagesList } from "../type_widgets/options/i18n"; import { LocaleSelector } from "../type_widgets/options/components/LocaleSelector"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; + +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export default function BasicPropertiesTab({ note }: TabContext) { return ( @@ -28,7 +31,7 @@ export default function BasicPropertiesTab({ note }: TabContext) { - + {!isNewLayout && } @@ -191,18 +194,7 @@ function EditabilitySelect({ note }: { note?: FNote | null }) { } function BookmarkSwitch({ note }: { note?: FNote | null }) { - const [ isBookmarked, setIsBookmarked ] = useState(false); - const refreshState = useCallback(() => { - const isBookmarked = note && !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks"); - setIsBookmarked(!!isBookmarked); - }, [ note ]); - - useEffect(() => refreshState(), [ note ]); - useTriliumEvent("entitiesReloaded", ({ loadResults }) => { - if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { - refreshState(); - } - }); + const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); return (
@@ -210,18 +202,36 @@ function BookmarkSwitch({ note }: { note?: FNote | null }) { switchOnName={t("bookmark_switch.bookmark")} switchOnTooltip={t("bookmark_switch.bookmark_this_note")} switchOffName={t("bookmark_switch.bookmark")} switchOffTooltip={t("bookmark_switch.remove_bookmark")} currentValue={isBookmarked} - onChange={async (shouldBookmark) => { - if (!note) return; - const resp = await server.put(`notes/${note.noteId}/toggle-in-parent/_lbBookmarks/${shouldBookmark}`); - - if (!resp.success && "message" in resp) { - toast.showError(resp.message); - } - }} + onChange={setIsBookmarked} disabled={["root", "_hidden"].includes(note?.noteId ?? "")} />
- ) + ); +} + +export function useNoteBookmarkState(note: FNote | null | undefined) { + const [ isBookmarked, setIsBookmarked ] = useState(false); + const refreshState = useCallback(() => { + const isBookmarked = note && !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks"); + setIsBookmarked(!!isBookmarked); + }, [ note ]); + + const changeHandler = useCallback(async (shouldBookmark: boolean) => { + if (!note) return; + const resp = await server.put(`notes/${note.noteId}/toggle-in-parent/_lbBookmarks/${shouldBookmark}`); + + if (!resp.success && "message" in resp) { + toast.showError(resp.message); + } + }, [ note ]); + + useEffect(() => refreshState(), [ refreshState ]); + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { + if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { + refreshState(); + } + }); + return [ isBookmarked, changeHandler ] as const; } function TemplateSwitch({ note }: { note?: FNote | null }) { diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 12655262ef..ee5c949d7a 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -13,10 +13,11 @@ import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/u import ws from "../../services/ws"; import ActionButton from "../react/ActionButton"; import Dropdown from "../react/Dropdown"; -import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList"; +import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; +import { useNoteBookmarkState } from "./BasicPropertiesTab"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -79,6 +80,9 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not {isElectron && } + {isNewLayout && } + + parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} /> @@ -112,6 +116,14 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not ); } +function NoteBasicProperties({ note }: { note: FNote }) { + const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); + + return <> + + ; +} + function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { return ( From e0f7d65f77440188866b9ea68edc7312544c3c5c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:24:31 +0200 Subject: [PATCH 130/873] feat(widgets): toggle from label --- apps/client/src/widgets/react/FormList.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 95a1052621..92c021c559 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -139,7 +139,10 @@ export function FormListToggleableItem({ title, currentValue, onChange, ...props onChange(newValue: boolean): void; }) { return ( - e.stopPropagation()}> + { + e.stopPropagation(); + onChange(!currentValue); + }}> Date: Wed, 10 Dec 2025 18:33:29 +0200 Subject: [PATCH 131/873] feat(widgets/toggle): disable transitions on first render --- apps/client/src/widgets/react/FormToggle.css | 10 +++++++++- apps/client/src/widgets/react/FormToggle.tsx | 13 ++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/react/FormToggle.css b/apps/client/src/widgets/react/FormToggle.css index 9418391430..bfe5bdc614 100644 --- a/apps/client/src/widgets/react/FormToggle.css +++ b/apps/client/src/widgets/react/FormToggle.css @@ -24,6 +24,14 @@ border-radius: 24px; background-color: var(--switch-off-track-background); transition: background 200ms ease-in; + + &.disable-transitions { + transition: none !important; + + &:after { + transition: none !important; + } + } } .switch-widget .switch-button.on { @@ -103,4 +111,4 @@ body[dir=rtl] .switch-widget .switch-button.on:after { .switch-widget .switch-help-button:hover { color: var(--main-text-color); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx index ba7499f367..39be5cd70e 100644 --- a/apps/client/src/widgets/react/FormToggle.tsx +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -1,5 +1,7 @@ +import clsx from "clsx"; import "./FormToggle.css"; import HelpButton from "./HelpButton"; +import { useEffect, useState } from "preact/hooks"; interface FormToggleProps { currentValue: boolean | null; @@ -13,13 +15,22 @@ interface FormToggleProps { } export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled }: FormToggleProps) { + const [ disableTransition, setDisableTransition ] = useState(true); + + useEffect(() => { + const timeout = setTimeout(() => { + setDisableTransition(false); + }, 100); + return () => clearTimeout(timeout); + }, []); + return (
{ currentValue ? switchOffName : switchOnName }
); @@ -247,7 +247,7 @@ function TemplateSwitch({ note }: { note?: FNote | null }) { currentValue={isTemplate} onChange={setIsTemplate} />
- ) + ); } function SharedSwitch({ note }: { note?: FNote | null }) { diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 8cf1ffbebc..321ccda894 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -14,7 +14,7 @@ import ws from "../../services/ws"; import ActionButton from "../react/ActionButton"; import Dropdown from "../react/Dropdown"; import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; -import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteProperty, useTriliumOption } from "../react/hooks"; +import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import { useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; @@ -119,10 +119,12 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not function NoteBasicProperties({ note }: { note: FNote }) { const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); const [ isShared, switchShareState ] = useShareState(note); + const [ isTemplate, setIsTemplate ] = useNoteLabelBoolean(note, "template"); return <> + ; } From cfbd2bf53ac899789d4a189a3c69c5e662ef601e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:58:46 +0200 Subject: [PATCH 135/873] feat(note_actions): integrate editability menu into new layout --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 4 ++-- .../client/src/widgets/ribbon/NoteActions.tsx | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 0f186998b9..94aa6b3143 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -30,7 +30,7 @@ export default function BasicPropertiesTab({ note }: TabContext) {
- + {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } @@ -190,7 +190,7 @@ function EditabilitySelect({ note }: { note?: FNote | null }) { }} />
- ) + ); } function BookmarkSwitch({ note }: { note?: FNote | null }) { diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 321ccda894..22e913b16d 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -125,9 +125,28 @@ function NoteBasicProperties({ note }: { note: FNote }) { + ; } +function EditabilityDropdown({ note }: { note: FNote }) { + const [ readOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); + const [ autoReadOnlyDisabled, setAutoReadOnlyDisabled ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled"); + + function setState(readOnly: boolean, autoReadOnlyDisabled: boolean) { + setReadOnly(readOnly); + setAutoReadOnlyDisabled(autoReadOnlyDisabled); + } + + return ( + + setState(false, false)} description={t("editability_select.note_is_editable")}>{t("editability_select.auto")} + setState(true, false)} description={t("editability_select.note_is_read_only")}>{t("editability_select.read_only")} + setState(false, true)} description={t("editability_select.note_is_always_editable")}>{t("editability_select.always_editable")} + + ); +} + function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { return ( From 01978dabf0d932a2315808774ad53b73c270cbad Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 19:05:33 +0200 Subject: [PATCH 136/873] fix(breadcrumb_badges): doesn't refresh when switching editability --- apps/client/src/widgets/react/hooks.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 38f0a1967f..65448eb924 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -845,6 +845,8 @@ export function useGlobalShortcut(keyboardShortcut: string | null | undefined, h */ export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: NoteContext | undefined) { const [ isReadOnly, setIsReadOnly ] = useState(undefined); + const [ readOnlyAttr ] = useNoteLabelBoolean(note, "readOnly"); + const [ autoReadOnlyDisabledAttr ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled"); const enableEditing = useCallback((enabled = true) => { if (noteContext?.viewScope) { @@ -859,7 +861,7 @@ export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: N setIsReadOnly(readOnly); }); } - }, [ note, noteContext, noteContext?.viewScope ]); + }, [ note, noteContext, noteContext?.viewScope, readOnlyAttr, autoReadOnlyDisabledAttr ]); useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => { if (noteContext?.ntxId === eventNoteContext.ntxId) { From efb2f9a048ab6608ff2bdf9abdaee6c8dc483ce0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 20:20:21 +0200 Subject: [PATCH 137/873] chore(note_actions): reintroduce disabled logic for toggles --- apps/client/src/widgets/react/FormList.tsx | 4 +++- .../client/src/widgets/ribbon/NoteActions.tsx | 21 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 92c021c559..8be2836053 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -141,7 +141,9 @@ export function FormListToggleableItem({ title, currentValue, onChange, ...props return ( { e.stopPropagation(); - onChange(!currentValue); + if (!props.disabled) { + onChange(!currentValue); + } }}> - - - + + + ; } From 483327c8087e9e71a59cfd9f8889be8c23a43ac4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 20:30:55 +0200 Subject: [PATCH 138/873] fix(widgets/toggle): double event triggering when in menu --- apps/client/src/widgets/react/FormList.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 8be2836053..f1374712c1 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -146,9 +146,10 @@ export function FormListToggleableItem({ title, currentValue, onChange, ...props } }}> {}} /> ); From 36b1182565b065efcf231e269ae9ef19307791d6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 20:32:58 +0200 Subject: [PATCH 139/873] feat(widgets/toggle): disable if going too fast --- apps/client/src/widgets/react/FormList.tsx | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index f1374712c1..fac69efdef 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -133,18 +133,25 @@ export function FormListItem({ className, icon, value, title, active, disabled, ); } -export function FormListToggleableItem({ title, currentValue, onChange, ...props }: Omit & { +export function FormListToggleableItem({ title, currentValue, onChange, disabled, ...props }: Omit & { title: string; currentValue: boolean; - onChange(newValue: boolean): void; + onChange(newValue: boolean): void | Promise; }) { + const isWaiting = useRef(false); + return ( - { - e.stopPropagation(); - if (!props.disabled) { - onChange(!currentValue); - } - }}> + { + e.stopPropagation(); + if (!disabled && !isWaiting.current) { + isWaiting.current = true; + await onChange(!currentValue); + isWaiting.current = false; + } + }}> Date: Wed, 10 Dec 2025 20:46:58 +0200 Subject: [PATCH 140/873] fix(note_actions): editability context menu is too narrow --- apps/client/src/stylesheets/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 871b84bdc1..55398be8c3 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1315,7 +1315,8 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu { top: 0; inset-inline-start: calc(100% - 2px); /* -2px, otherwise there's a small gap between menu and submenu where the hover can disappear */ margin-top: -10px; - min-width: 15rem; + min-width: max-content; + max-width: 300px; /* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */ max-height: 600px; overflow: auto; From 77f5770bff5c2e056a48e48cee31a7c7ed67272a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 20:57:15 +0200 Subject: [PATCH 141/873] feat(note_actions): protect note switch --- apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx | 2 +- apps/client/src/widgets/ribbon/NoteActions.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 94aa6b3143..d42617dae1 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -29,7 +29,7 @@ export default function BasicPropertiesTab({ note }: TabContext) { return (
- + {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index d4b5e235a1..282bb6b9c1 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -18,6 +18,7 @@ import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, u import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import { useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; +import protected_session from "../../services/protected_session"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -120,6 +121,7 @@ function NoteBasicProperties({ note }: { note: FNote }) { const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); const [ isShared, switchShareState ] = useShareState(note); const [ isTemplate, setIsTemplate ] = useNoteLabelBoolean(note, "template"); + const isProtected = useNoteProperty(note, "isProtected"); return <> + protected_session.protectNote(note.noteId, shouldProtect, false)} + /> ; } @@ -154,7 +161,7 @@ function EditabilityDropdown({ note }: { note: FNote }) { } return ( - + setState(false, false)} description={t("editability_select.note_is_editable")}>{t("editability_select.auto")} setState(true, false)} description={t("editability_select.note_is_read_only")}>{t("editability_select.read_only")} setState(false, true)} description={t("editability_select.note_is_always_editable")}>{t("editability_select.always_editable")} From 6f85b7cc0966fa77a7f59542358607702b7062bb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 21:54:17 +0200 Subject: [PATCH 142/873] feat(note_actions): integrate note type --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 142 +++++++++--------- .../client/src/widgets/ribbon/NoteActions.tsx | 18 ++- 2 files changed, 91 insertions(+), 69 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index d42617dae1..9c6f656d44 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; +import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/hooks"; import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; @@ -28,7 +28,7 @@ const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export default function BasicPropertiesTab({ note }: TabContext) { return (
- + {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } @@ -40,18 +40,42 @@ export default function BasicPropertiesTab({ note }: TabContext) { } function NoteTypeWidget({ note }: { note?: FNote | null }) { - const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); - const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes"); - const mimeTypes = useMemo(() => { - mime_types.loadMimeTypes(); - return mime_types.getMimeTypes().filter(mimeType => mimeType.enabled) - }, [ codeNotesMimeTypes ]); const notSelectableNoteTypes = useMemo(() => NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type), []); const currentNoteType = useNoteProperty(note, "type") ?? undefined; const currentNoteMime = useNoteProperty(note, "mime"); const [ modalShown, setModalShown ] = useState(false); + return ( +
+ {t("basic_properties.note_type")}:   + {findTypeTitle(currentNoteType, currentNoteMime)}} + disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")} + > + + + + setModalShown(false)} + size="xl" scrollable + > + + +
+ ); +} + +export function NoteTypeDropdownContent({ currentNoteType, currentNoteMime, note, setModalShown }: { currentNoteType?: NoteType, currentNoteMime?: string | null, note?: FNote | null, setModalShown: Dispatch> }) { + const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes"); + const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); + const mimeTypes = useMemo(() => { + mime_types.loadMimeTypes(); + return mime_types.getMimeTypes().filter(mimeType => mimeType.enabled); + }, [ codeNotesMimeTypes ]); const changeNoteType = useCallback(async (type: NoteType, mime?: string) => { if (!note || (type === currentNoteType && mime === currentNoteMime)) { return; @@ -71,70 +95,54 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) { }, [ note, currentNoteType, currentNoteMime ]); return ( -
- {t("basic_properties.note_type")}:   - {findTypeTitle(currentNoteType, currentNoteMime)}} - disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")} - > - {noteTypes.map(({ isNew, isBeta, type, mime, title }) => { - const badges: FormListBadge[] = []; - if (isNew) { - badges.push({ - className: "new-note-type-badge", - text: t("note_types.new-feature") - }); - } - if (isBeta) { - badges.push({ - text: t("note_types.beta-feature") - }); - } + <> + {noteTypes.map(({ isNew, isBeta, type, mime, title }) => { + const badges: FormListBadge[] = []; + if (isNew) { + badges.push({ + className: "new-note-type-badge", + text: t("note_types.new-feature") + }); + } + if (isBeta) { + badges.push({ + text: t("note_types.beta-feature") + }); + } - const checked = (type === currentNoteType); - if (type !== "code") { - return ( + const checked = (type === currentNoteType); + if (type !== "code") { + return ( + changeNoteType(type, mime)} + >{title} + ); + } else { + return ( + <> + changeNoteType(type, mime)} - >{title} - ); - } else { - return ( - <> - - - {title} - - - ) - } - })} + disabled + > + {title} + + + ); + } + })} - {mimeTypes.map(({ title, mime }) => ( - changeNoteType("code", mime)}> - {title} - - ))} + {mimeTypes.map(({ title, mime }) => ( + changeNoteType("code", mime)}> + {title} + + ))} - - setModalShown(true)}>{t("basic_properties.configure_code_notes")} - - - setModalShown(false)} - size="xl" scrollable - > - - -
+ + setModalShown(true)}>{t("basic_properties.configure_code_notes")} + ) } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 282bb6b9c1..515535ba83 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -1,5 +1,5 @@ import { ConvertToAttachmentResponse } from "@triliumnext/commons"; -import { useContext } from "preact/hooks"; +import { useContext, useState } from "preact/hooks"; import appContext, { CommandNames } from "../../components/app_context"; import NoteContext from "../../components/note_context"; @@ -17,7 +17,7 @@ import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListTogglea import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; -import { useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; +import { NoteTypeDropdownContent, useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; import protected_session from "../../services/protected_session"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -148,6 +148,8 @@ function NoteBasicProperties({ note }: { note: FNote }) { title={t("protect_note.toggle-on")} currentValue={!!isProtected} onChange={shouldProtect => protected_session.protectNote(note.noteId, shouldProtect, false)} /> + + ; } @@ -169,6 +171,18 @@ function EditabilityDropdown({ note }: { note: FNote }) { ); } +function NoteTypeDropdown({ note }: { note: FNote }) { + const currentNoteType = useNoteProperty(note, "type") ?? undefined; + const currentNoteMime = useNoteProperty(note, "mime"); + const [ modalShown, setModalShown ] = useState(false); + + return ( + + + + ); +} + function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { return ( From 8d8ff25bae18cac4650957ccd0fc02fbe7b26f28 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 22:18:53 +0200 Subject: [PATCH 143/873] feat(note_actions): reintroduce help pages --- apps/client/src/stylesheets/style.css | 6 ++--- apps/client/src/stylesheets/theme-dark.css | 4 ++-- apps/client/src/stylesheets/theme-light.css | 4 ++-- .../src/stylesheets/theme-next-dark.css | 12 +++++----- .../src/stylesheets/theme-next-light.css | 10 ++++---- apps/client/src/widgets/react/FormList.css | 7 +++++- apps/client/src/widgets/react/FormList.tsx | 23 +++++++++++++++---- apps/client/src/widgets/react/FormToggle.tsx | 5 +++- .../client/src/widgets/ribbon/NoteActions.tsx | 2 ++ 9 files changed, 49 insertions(+), 24 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 55398be8c3..0a1971f118 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -423,16 +423,16 @@ body.desktop .tabulator-popup-container, pointer-events: none; } -.dropdown-menu .disabled .disabled-tooltip { +.dropdown-menu .disabled .contextual-help { pointer-events: all; margin-inline-start: 8px; font-size: 0.75rem; - color: var(--disabled-tooltip-icon-color); + color: var(--contextual-help-icon-color); cursor: help; opacity: 0.75; } -.dropdown-menu .disabled .disabled-tooltip:hover { +.dropdown-menu .disabled .contextual-help:hover { opacity: 1; } diff --git a/apps/client/src/stylesheets/theme-dark.css b/apps/client/src/stylesheets/theme-dark.css index 690d49ecda..d4702d6c51 100644 --- a/apps/client/src/stylesheets/theme-dark.css +++ b/apps/client/src/stylesheets/theme-dark.css @@ -19,7 +19,7 @@ --dropdown-border-color: #555; --dropdown-shadow-opacity: 0.4; --dropdown-item-icon-destructive-color: #de6e5b; - --disabled-tooltip-icon-color: #7fd2ef; + --contextual-help-icon-color: #7fd2ef; --accented-background-color: #555; --more-accented-background-color: #777; @@ -114,4 +114,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before { .use-note-color { --custom-color: var(--dark-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-light.css b/apps/client/src/stylesheets/theme-light.css index 0c14a2d926..777ccbd619 100644 --- a/apps/client/src/stylesheets/theme-light.css +++ b/apps/client/src/stylesheets/theme-light.css @@ -23,7 +23,7 @@ html { --dropdown-border-color: #ccc; --dropdown-shadow-opacity: 0.2; --dropdown-item-icon-destructive-color: #ec5138; - --disabled-tooltip-icon-color: #004382; + --contextual-help-icon-color: #004382; --accented-background-color: #f5f5f5; --more-accented-background-color: #ddd; @@ -98,4 +98,4 @@ html { .use-note-color { --custom-color: var(--light-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 8358de09bf..b4c3def742 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -6,7 +6,7 @@ */ :root { - /* + /* * ⚠️ NOTICE: This theme is currently in the beta stage of development. * The names and purposes of these CSS variables are subject to frequent changes. */ @@ -22,7 +22,7 @@ --dropdown-border-color: #404040; --dropdown-shadow-opacity: 0.6; --dropdown-item-icon-destructive-color: #de6e5b; - --disabled-tooltip-icon-color: #7fd2ef; + --contextual-help-icon-color: #7fd2ef; --accented-background-color: #555; @@ -182,7 +182,7 @@ --tab-close-button-hover-background: #a45353; --tab-close-button-hover-color: white; - + --active-tab-background-color: #ffffff1c; --active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-icon-color: #a9a9a9; @@ -201,7 +201,7 @@ --promoted-attribute-card-background-color: #ffffff21; --promoted-attribute-card-shadow: none; - + --floating-button-shadow-color: #00000080; --floating-button-background-color: #494949d2; --floating-button-color: var(--button-text-color); @@ -226,7 +226,7 @@ --scrollbar-border-color: unset; /* Deprecated */ --selection-background-color: #3399FF70; - + --link-color: lightskyblue; --mermaid-theme: dark; @@ -320,4 +320,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before { .use-note-color { --custom-color: var(--dark-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 60e7d55b2b..620672499d 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -6,7 +6,7 @@ */ :root { - /* + /* * ⚠️ NOTICE: This theme is currently in the beta stage of development. * The names and purposes of these CSS variables are subject to frequent changes. */ @@ -22,7 +22,7 @@ --dropdown-border-color: #ccc; --dropdown-shadow-opacity: 0.2; --dropdown-item-icon-destructive-color: #ec5138; - --disabled-tooltip-icon-color: #004382; + --contextual-help-icon-color: #004382; --accented-background-color: #f5f5f5; @@ -138,7 +138,7 @@ /* Deprecated: now local variables in #launcher, with the values dependent on the current layout. */ --launcher-pane-background-color: unset; --launcher-pane-text-color: unset; - + --launcher-pane-vert-background-color: #e8e8e8; --launcher-pane-vert-text-color: #000000bd; --launcher-pane-vert-button-hover-color: black; @@ -174,7 +174,7 @@ --tab-close-button-hover-background: #c95a5a; --tab-close-button-hover-color: white; - + --active-tab-background-color: white; --active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-icon-color: gray; @@ -291,4 +291,4 @@ --modal-background-color: hsl(var(--custom-color-hue), 56%, 96%); --modal-border-color: hsl(var(--custom-color-hue), 33%, 41%); --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/react/FormList.css b/apps/client/src/widgets/react/FormList.css index 33922e7c66..643b5132a8 100644 --- a/apps/client/src/widgets/react/FormList.css +++ b/apps/client/src/widgets/react/FormList.css @@ -17,7 +17,12 @@ --switch-thumb-width: 12px; --switch-thumb-height: var(--switch-thumb-width); - .switch-name { + .contextual-help { + margin-inline-start: 0.25em; + cursor: pointer; + } + + .switch-spacer { flex-grow: 1; } } diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index fac69efdef..dd5948cb3b 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -5,7 +5,7 @@ import { useEffect, useMemo, useRef, useState, type CSSProperties } from "preact import "./FormList.css"; import { CommandNames } from "../../components/app_context"; import { useStaticTooltip } from "./hooks"; -import { handleRightToLeftPlacement, isMobile } from "../../services/utils"; +import { handleRightToLeftPlacement, isMobile, openInAppHelpFromUrl } from "../../services/utils"; import clsx from "clsx"; import FormToggle from "./FormToggle"; @@ -95,12 +95,13 @@ interface FormListItemOpts { description?: string; className?: string; rtl?: boolean; + postContent?: ComponentChildren; } const TOOLTIP_CONFIG: Partial = { placement: handleRightToLeftPlacement("right"), fallbackPlacements: [ handleRightToLeftPlacement("right") ] -} +}; export function FormListItem({ className, icon, value, title, active, disabled, checked, container, onClick, selected, rtl, triggerCommand, description, ...contentProps }: FormListItemOpts) { const itemRef = useRef(null); @@ -133,9 +134,10 @@ export function FormListItem({ className, icon, value, title, active, disabled, ); } -export function FormListToggleableItem({ title, currentValue, onChange, disabled, ...props }: Omit & { +export function FormListToggleableItem({ title, currentValue, onChange, disabled, helpPage, ...props }: Omit & { title: string; currentValue: boolean; + helpPage?: string; onChange(newValue: boolean): void | Promise; }) { const isWaiting = useRef(false); @@ -145,6 +147,10 @@ export function FormListToggleableItem({ title, currentValue, onChange, disabled {...props} disabled={disabled} onClick={async (e) => { + if ((e.target as HTMLElement | null)?.classList.contains("contextual-help")) { + return; + } + e.stopPropagation(); if (!disabled && !isWaiting.current) { isWaiting.current = true; @@ -157,6 +163,15 @@ export function FormListToggleableItem({ title, currentValue, onChange, disabled switchOffName={title} currentValue={currentValue} onChange={() => {}} + afterName={<> + {helpPage && ( + openInAppHelpFromUrl(helpPage)} + /> + )} + + } /> ); @@ -169,7 +184,7 @@ function FormListContent({ children, badges, description, disabled, disabledTool {text} ))} {disabled && disabledTooltip && ( - + )} {description &&
{description}
} ; diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx index 39be5cd70e..e08c1e3c2e 100644 --- a/apps/client/src/widgets/react/FormToggle.tsx +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -2,6 +2,7 @@ import clsx from "clsx"; import "./FormToggle.css"; import HelpButton from "./HelpButton"; import { useEffect, useState } from "preact/hooks"; +import { ComponentChildren } from "preact"; interface FormToggleProps { currentValue: boolean | null; @@ -12,9 +13,10 @@ interface FormToggleProps { switchOffTooltip?: string; helpPage?: string; disabled?: boolean; + afterName?: ComponentChildren; } -export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled }: FormToggleProps) { +export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled, afterName }: FormToggleProps) { const [ disableTransition, setDisableTransition ] = useState(true); useEffect(() => { @@ -27,6 +29,7 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc return (
{ currentValue ? switchOffName : switchOnName } + { afterName }
); } -export function NoteLanguageSelector({ note }: { note?: FNote | null }) { +export function NoteLanguageSelector({ note, extraChildren }: { note?: FNote | null, extraChildren?: ComponentChildren }) { const [ modalShown, setModalShown ] = useState(false); const [ languages ] = useTriliumOption("languages"); const DEFAULT_LOCALE = { @@ -351,12 +352,13 @@ export function NoteLanguageSelector({ note }: { note?: FNote | null }) { locales={locales} defaultLocale={DEFAULT_LOCALE} currentValue={currentNoteLanguage ?? ""} onChange={setCurrentNoteLanguage} - extraChildren={( + extraChildren={<> + {extraChildren} setModalShown(true)} icon="bx bx-cog" >{t("note_language.configure-languages")} - )} + } /> {createPortal( , From 58bc5dc66a2ba81b656fba6c61e265d014c117ea Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 23:07:35 +0200 Subject: [PATCH 152/873] chore(ribbon): hide basic properties from the ribbon on new layout --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 50 +++++++++---------- .../src/widgets/ribbon/RibbonDefinition.ts | 43 ++++++++-------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 8ea9d6f7d0..04e8334ee2 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -1,41 +1,39 @@ +import { NoteType, ToggleInParentResponse } from "@triliumnext/commons"; +import { ComponentChildren } from "preact"; +import { createPortal } from "preact/compat"; import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/hooks"; -import Dropdown from "../react/Dropdown"; -import { NOTE_TYPES } from "../../services/note_types"; -import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; -import { getAvailableLocales, t } from "../../services/i18n"; -import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks"; -import mime_types from "../../services/mime_types"; -import { Locale, LOCALES, NoteType, ToggleInParentResponse } from "@triliumnext/commons"; -import server from "../../services/server"; -import dialog from "../../services/dialog"; -import FormToggle from "../react/FormToggle"; + import FNote from "../../entities/fnote"; -import protected_session from "../../services/protected_session"; -import FormDropdownList from "../react/FormDropdownList"; -import toast from "../../services/toast"; import branches from "../../services/branches"; +import dialog from "../../services/dialog"; +import { getAvailableLocales, t } from "../../services/i18n"; +import mime_types from "../../services/mime_types"; +import { NOTE_TYPES } from "../../services/note_types"; +import protected_session from "../../services/protected_session"; +import server from "../../services/server"; import sync from "../../services/sync"; +import toast from "../../services/toast"; +import Dropdown from "../react/Dropdown"; +import FormDropdownList from "../react/FormDropdownList"; +import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; +import FormToggle from "../react/FormToggle"; import HelpButton from "../react/HelpButton"; -import { TabContext } from "./ribbon-interface"; +import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks"; import Modal from "../react/Modal"; import { CodeMimeTypesList } from "../type_widgets/options/code_notes"; -import { ContentLanguagesList } from "../type_widgets/options/i18n"; import { LocaleSelector } from "../type_widgets/options/components/LocaleSelector"; -import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; -import { createPortal } from "preact/compat"; -import { ComponentChildren } from "preact"; - -const isNewLayout = isExperimentalFeatureEnabled("new-layout"); +import { ContentLanguagesList } from "../type_widgets/options/i18n"; +import { TabContext } from "./ribbon-interface"; export default function BasicPropertiesTab({ note }: TabContext) { return (
- {!isNewLayout && } - {!isNewLayout && } - {!isNewLayout && } - {!isNewLayout && } - {!isNewLayout && } - {!isNewLayout && } + + + + + +
); diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index ab351637d9..a76e971401 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -1,22 +1,24 @@ -import ScriptTab from "./ScriptTab"; -import EditedNotesTab from "./EditedNotesTab"; -import NotePropertiesTab from "./NotePropertiesTab"; -import NoteInfoTab from "./NoteInfoTab"; -import SimilarNotesTab from "./SimilarNotesTab"; -import FilePropertiesTab from "./FilePropertiesTab"; -import ImagePropertiesTab from "./ImagePropertiesTab"; -import NotePathsTab from "./NotePathsTab"; -import NoteMapTab from "./NoteMapTab"; -import OwnedAttributesTab from "./OwnedAttributesTab"; -import InheritedAttributesTab from "./InheritedAttributesTab"; -import CollectionPropertiesTab from "./CollectionPropertiesTab"; -import SearchDefinitionTab from "./SearchDefinitionTab"; -import BasicPropertiesTab from "./BasicPropertiesTab"; -import FormattingToolbar from "./FormattingToolbar"; -import options from "../../services/options"; -import { t } from "../../services/i18n"; -import { TabConfiguration } from "./ribbon-interface"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; +import { t } from "../../services/i18n"; +import options from "../../services/options"; +import BasicPropertiesTab from "./BasicPropertiesTab"; +import CollectionPropertiesTab from "./CollectionPropertiesTab"; +import EditedNotesTab from "./EditedNotesTab"; +import FilePropertiesTab from "./FilePropertiesTab"; +import FormattingToolbar from "./FormattingToolbar"; +import ImagePropertiesTab from "./ImagePropertiesTab"; +import InheritedAttributesTab from "./InheritedAttributesTab"; +import NoteInfoTab from "./NoteInfoTab"; +import NoteMapTab from "./NoteMapTab"; +import NotePathsTab from "./NotePathsTab"; +import NotePropertiesTab from "./NotePropertiesTab"; +import OwnedAttributesTab from "./OwnedAttributesTab"; +import { TabConfiguration } from "./ribbon-interface"; +import ScriptTab from "./ScriptTab"; +import SearchDefinitionTab from "./SearchDefinitionTab"; +import SimilarNotesTab from "./SimilarNotesTab"; + +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ { @@ -28,7 +30,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ toggleCommand: "toggleRibbonTabClassicEditor", content: FormattingToolbar, activate: ({ note }) => !options.is("editedNotesOpenInRibbon") || !note?.hasOwnedLabel("dateNote"), - stayInDom: !isExperimentalFeatureEnabled("new-layout"), + stayInDom: !isNewLayout, avoidInNewLayout: true }, { @@ -85,11 +87,10 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ activate: true, }, { - // BasicProperties title: t("basic_properties.basic_properties"), icon: "bx bx-slider", content: BasicPropertiesTab, - show: ({note}) => !note?.isLaunchBarConfig(), + show: ({note}) => !isNewLayout && !note?.isLaunchBarConfig(), toggleCommand: "toggleRibbonTabBasicProperties" }, { From 6e8e10323f4f5b0e5897b6741fff015075c8c9f5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 23:19:17 +0200 Subject: [PATCH 153/873] chore(client): address requested changes --- apps/client/src/services/experimental_features.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index 9295aa8c7b..5f7e5d0109 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -28,15 +28,13 @@ export function getEnabledExperimentalFeatureIds() { } export async function toggleExperimentalFeature(featureId: ExperimentalFeatureId, enable: boolean) { - let features = Array.from(getEnabledFeatures()); + const features = new Set(getEnabledFeatures()); if (enable) { - if (!features.includes(featureId)) { - features.push(featureId); - } + features.add(featureId); } else { - features = features.filter(f => f !== featureId); + features.delete(featureId); } - await options.save("experimentalFeatures", JSON.stringify(features)); + await options.save("experimentalFeatures", JSON.stringify(Array.from(features))); } function getEnabledFeatures() { From 1ab89d0db024e436c9dc80f55b2aeacbd41be727 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 23:36:47 +0200 Subject: [PATCH 154/873] fix(status_bar): language selector not updating properly --- apps/client/src/widgets/NoteStatusBar.tsx | 22 ++++++++++++------- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 2 +- .../options/components/LocaleSelector.tsx | 8 +++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/client/src/widgets/NoteStatusBar.tsx b/apps/client/src/widgets/NoteStatusBar.tsx index 9255fb8fd3..3b686ff846 100644 --- a/apps/client/src/widgets/NoteStatusBar.tsx +++ b/apps/client/src/widgets/NoteStatusBar.tsx @@ -1,19 +1,25 @@ +import "./NoteStatusBar.css"; + import { t } from "../services/i18n"; import { openInAppHelpFromUrl } from "../services/utils"; -import "./NoteStatusBar.css"; import { FormListItem } from "./react/FormList"; - +import { useNoteContext } from "./react/hooks"; import { NoteLanguageSelector } from "./ribbon/BasicPropertiesTab"; export default function NoteStatusBar() { + const { note } = useNoteContext(); + return (
- openInAppHelpFromUrl("veGu4faJErEM")} - icon="bx bx-help-circle" - >{t("note_language.help-on-languages")} - )} /> + openInAppHelpFromUrl("veGu4faJErEM")} + icon="bx bx-help-circle" + >{t("note_language.help-on-languages")} + )} + />
); } diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 04e8334ee2..479994ca39 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -330,7 +330,7 @@ function NoteLanguageSwitch({ note }: { note?: FNote | null }) { ); } -export function NoteLanguageSelector({ note, extraChildren }: { note?: FNote | null, extraChildren?: ComponentChildren }) { +export function NoteLanguageSelector({ note, extraChildren }: { note: FNote | null | undefined, extraChildren?: ComponentChildren }) { const [ modalShown, setModalShown ] = useState(false); const [ languages ] = useTriliumOption("languages"); const DEFAULT_LOCALE = { diff --git a/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx b/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx index 5628cc7980..b9b1d8758d 100644 --- a/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx +++ b/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx @@ -1,8 +1,9 @@ import { Locale } from "@triliumnext/commons"; +import { ComponentChildren } from "preact"; +import { useMemo } from "preact/hooks"; + import Dropdown from "../../../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../../../react/FormList"; -import { ComponentChildren } from "preact"; -import { useMemo, useState } from "preact/hooks"; export function LocaleSelector({ id, locales, currentValue, onChange, defaultLocale, extraChildren }: { id?: string; @@ -12,7 +13,7 @@ export function LocaleSelector({ id, locales, currentValue, onChange, defaultLoc defaultLocale?: Locale, extraChildren?: ComponentChildren }) { - const [ activeLocale, setActiveLocale ] = useState(defaultLocale?.id === currentValue ? defaultLocale : locales.find(l => l.id === currentValue)); + const activeLocale = defaultLocale?.id === currentValue ? defaultLocale : locales.find(l => l.id === currentValue); const processedLocales = useMemo(() => { const leftToRightLanguages = locales.filter((l) => !l.rtl); @@ -48,7 +49,6 @@ export function LocaleSelector({ id, locales, currentValue, onChange, defaultLoc rtl={locale.rtl} checked={locale.id === currentValue} onClick={() => { - setActiveLocale(locale); onChange(locale.id); }} >{locale.name} From 25e5bf0b86fab0244e9cafa523f2929e82ee8155 Mon Sep 17 00:00:00 2001 From: green Date: Tue, 9 Dec 2025 16:23:54 +0100 Subject: [PATCH 155/873] Translated using Weblate (Japanese) Currently translated at 100.0% (1648 of 1648 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- apps/client/src/translations/ja/translation.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 098db894e5..c539b80362 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -1779,7 +1779,7 @@ "placeholder": "ここにノートの内容を入力...", "auto-detect-language": "自動検出", "keeps-crashing": "編集コンポーネントがクラッシュし続けます。Trilium を再起動してください。問題が解決しない場合は、バグレポートの作成をご検討ください。", - "editor_crashed_title": "テキストエディタがクラッシュしました", + "editor_crashed_title": "テキストエディターがクラッシュしました", "editor_crashed_content": "コンテンツは正常に復元されましたが、最近の変更の一部が保存されていない可能性があります。", "editor_crashed_details_button": "詳細を見る...", "editor_crashed_details_intro": "このエラーが何度も発生する場合は、以下の情報を貼り付けて GitHub に報告することを検討してください。", @@ -2116,5 +2116,9 @@ "unknown_http_error_title": "サーバーとの通信エラー", "unknown_http_error_content": "ステータスコード: {{statusCode}}\nURL: {{method}} {{url}}\nメッセージ: {{message}}", "traefik_blocks_requests": "Traefik リバース プロキシを使用している場合、サーバーとの通信に影響する重大な変更が導入されました。" + }, + "tab_history_navigation_buttons": { + "go-back": "前のノートに戻る", + "go-forward": "次のノートに進む" } } From b020365af49457848577701b8d0a8497211405c2 Mon Sep 17 00:00:00 2001 From: Abdulmajeed Alaskar Date: Tue, 9 Dec 2025 20:20:18 +0100 Subject: [PATCH 156/873] Translated using Weblate (Arabic) Currently translated at 29.3% (34 of 116 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ar/ --- docs/README-ar.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/README-ar.md b/docs/README-ar.md index 1cb51bac3a..134fc8c068 100644 --- a/docs/README-ar.md +++ b/docs/README-ar.md @@ -29,14 +29,14 @@ script)](./README-ZH_TW.md) | [English](../README.md) | [French](./README-fr.md) [Spanish](./README-es.md) -Trilium Notes is a free and open-source, cross-platform hierarchical note taking -application with focus on building large personal knowledge bases. +تريليوم هو برنامج مجاني مفتوح المصدر، يمكن استخدامه في أكثر من جهاز بنفس الوقت، +مبني على كتابة الملاحظات بالتفرعات الشجرية مع التركيز على بناء قاعدة بيانات +معرفية ضخمة. Trilium Screenshot ## ⬇️ تنزيل -- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) – - stable version, recommended for most users. +- النسخة الأخيرة - نسخة مستقرة، محبذة لأكثر المستخدمين. - [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – unstable development version, updated daily with the latest features and fixes. From f5440576b59244805061df9248f51c8de98dc895 Mon Sep 17 00:00:00 2001 From: Abdulmajeed Alaskar Date: Tue, 9 Dec 2025 20:20:51 +0100 Subject: [PATCH 157/873] Translated using Weblate (Arabic) Currently translated at 53.9% (82 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ar/ --- apps/website/src/translations/ar/translation.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/website/src/translations/ar/translation.json b/apps/website/src/translations/ar/translation.json index 024d96f840..c3234bb998 100644 --- a/apps/website/src/translations/ar/translation.json +++ b/apps/website/src/translations/ar/translation.json @@ -13,7 +13,9 @@ "get-started": { "architecture": "المعمارية:", "older_releases": "انظر الى الاصدارات القديمة", - "title": "ابدأ الان" + "title": "ابدأ الان", + "desktop_title": "قم بتحميل تطبيق سطح المكتب (v{{version}})", + "server_title": "إعداد خادم للدخول من أكثر من جهاز" }, "hero_section": { "github": "GitHub", From 242c63dfb41e67029390866bb8cadbfd883aa322 Mon Sep 17 00:00:00 2001 From: pythaac Date: Tue, 9 Dec 2025 17:53:48 +0100 Subject: [PATCH 158/873] Translated using Weblate (Korean) Currently translated at 65.1% (99 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/ --- apps/website/src/translations/ko/translation.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/website/src/translations/ko/translation.json b/apps/website/src/translations/ko/translation.json index c3ec3f5744..5b9c0bf32b 100644 --- a/apps/website/src/translations/ko/translation.json +++ b/apps/website/src/translations/ko/translation.json @@ -119,6 +119,11 @@ "calendar_description": "캘린더를 사용하여 개인 또는 업무 관련 일정을 관리하세요. 하루 또는 며칠 단위로 일정을 관리할 수 있으며, 주별, 월별, 연도별 보기를 통해 일정을 한눈에 확인할 수 있습니다. 또 간편한 상호작용을 통해 일정을 추가하거나 끌어다 놓을 수 있습니다.", "table_title": "테이블", "table_description": "텍스트, 숫자, 체크박스, 날짜 및 시간, 링크, 색상 등 다양한 열 유형을 제공하고 관계 기능을 지원하는 표 형식으로 노트에 내용을 표시하고 편집할 수 있습니다. 필요에 따라 표 안에 트리 계층 구조의 노트를 표시할 수도 있습니다.", - "board_title": "칸반 보드" + "board_title": "칸반 보드", + "board_description": "칸반 보드에 작업이나 프로젝트 상태를 정리하세요. 새 항목과 열을 쉽게 만들 수 있고, 보드 위로 드래그하여 상태를 간단히 변경할 수 있습니다.", + "geomap_title": "지리 지도", + "geomap_description": "커스터마이징이 가능한 마커로 휴가를 계획하거나 관심 지점을 지도에 직접 표시하세요. 기록된 GPX 트랙을 표시하여 여정을 추적할 수도 있습니다.", + "presentation_title": "프레젠테이션", + "presentation_description": "슬라이드에 정보를 정리하고, 매끄러운 전환 효과와 함께 전체 화면으로 발표하세요. 슬라이드를 PDF로 내보내 간편하게 공유할 수도 있습니다." } } From 77e3cc40218539e1956da4682d75697812805cbf Mon Sep 17 00:00:00 2001 From: "Francis C." Date: Wed, 10 Dec 2025 07:42:31 +0100 Subject: [PATCH 159/873] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (1658 of 1658 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/ --- .../src/translations/cn/translation.json | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index 88d6910acf..63f39312cf 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -1577,7 +1577,9 @@ "printing_pdf": "正在导出为PDF…" }, "note_title": { - "placeholder": "请输入笔记标题..." + "placeholder": "请输入笔记标题...", + "created_on": "建立于 {{date}}", + "last_modified": "最后修改于 {{date}}" }, "search_result": { "no_notes_found": "没有找到符合搜索条件的笔记。", @@ -2116,5 +2118,21 @@ "unknown_http_error_title": "与服务器通讯错误", "unknown_http_error_content": "状态码: {{statusCode}}\n地址: {{method}} {{url}}\n信息: {{message}}", "traefik_blocks_requests": "如果您使用 Traefik 反向代理,它引入了一项影响与服务器的通信重大更改。" + }, + "experimental_features": { + "title": "实验选项", + "disclaimer": "这些选项处于实验阶段,可能导致系统不稳定。请谨慎使用。", + "new_layout_name": "新布局", + "new_layout_description": "尝试全新布局,呈现更现代的外观并提升易用性。后续版本将进行重大调整。" + }, + "tab_history_navigation_buttons": { + "go-back": "返回前一笔记", + "go-forward": "前往下一笔记" + }, + "breadcrumb_badges": { + "read_only_explicit": "只读", + "read_only_auto": "自动只读", + "shared_publicly": "公开共享", + "shared_locally": "本地共享" } } From 1db54cba3e2c19467e4ff196928ba6e1aad5ebf7 Mon Sep 17 00:00:00 2001 From: green Date: Wed, 10 Dec 2025 04:43:39 +0100 Subject: [PATCH 160/873] Translated using Weblate (Japanese) Currently translated at 100.0% (1658 of 1658 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- apps/client/src/translations/ja/translation.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index c539b80362..264d482dc1 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -1233,7 +1233,9 @@ "none_yet": "アクションを上のリストからクリックして追加。" }, "note_title": { - "placeholder": "ここにノートのタイトルを入力..." + "placeholder": "ここにノートのタイトルを入力...", + "created_on": "{{date}} に作成", + "last_modified": "{{date}} に最終更新" }, "search_result": { "no_notes_found": "指定された検索パラメータに該当するノートは見つかりませんでした。", @@ -2120,5 +2122,17 @@ "tab_history_navigation_buttons": { "go-back": "前のノートに戻る", "go-forward": "次のノートに進む" + }, + "experimental_features": { + "title": "実験オプション", + "disclaimer": "これらのオプションは試験的なもので、動作が不安定になる可能性があります。注意してご使用ください。", + "new_layout_name": "新しいレイアウト", + "new_layout_description": "よりモダンな外観と使いやすさが向上した新しいレイアウトをお試しください。今後のリリースで大幅な変更が加えられる可能性があります。" + }, + "breadcrumb_badges": { + "read_only_explicit": "読み取り専用", + "read_only_auto": "自動的に読み取り専用", + "shared_publicly": "公開で共有", + "shared_locally": "ローカルで共有" } } From e404e7629968b481a084ddfa9b7fa446fcd864e8 Mon Sep 17 00:00:00 2001 From: "Francis C." Date: Wed, 10 Dec 2025 07:28:08 +0100 Subject: [PATCH 161/873] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (116 of 116 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/zh_Hant/ --- docs/README-ZH_TW.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/README-ZH_TW.md b/docs/README-ZH_TW.md index 5ddcf47d74..b189198b7c 100644 --- a/docs/README-ZH_TW.md +++ b/docs/README-ZH_TW.md @@ -70,15 +70,13 @@ Trilium Notes 是一款免費且開源、跨平台的階層式筆記應用程式 * 直接[整合 OpenID 與 TOTP](https://docs.triliumnotes.org/user-guide/setup/server/mfa) 以實現更安全的登入 * 與自架的同步伺服器進行[同步](https://docs.triliumnotes.org/user-guide/setup/synchronization) - * there are [3rd party services for hosting synchronisation - server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) + * 有[第三方服務可託管同步伺服器](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) * 將筆記[分享](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing)(公開發布)到網際網路 * 以每則筆記為粒度的強大[筆記加密](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes) * 手繪/示意圖:基於 [Excalidraw](https://excalidraw.com/)(筆記類型為「canvas」) -* [Relation - maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and - [note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map) - for visualizing notes and their relations +* [關係圖](https://docs.triliumnotes.org/user-guide/note-types/relation-map) 與 + [筆記/連結圖](https://docs.triliumnotes.org/user-guide/note-types/note-map) + 用於視覺化呈現筆記及其關聯性 * 心智圖:基於 [Mind Elixir](https://docs.mind-elixir.com/) * 具有定位釘與 GPX 軌跡的[地圖](https://docs.triliumnotes.org/user-guide/collections/geomap) @@ -95,8 +93,8 @@ Trilium Notes 是一款免費且開源、跨平台的階層式筆記應用程式 * 用於快速保存網頁內容的 [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) * 可自訂的 UI(側邊欄按鈕、使用者自訂小工具等) -* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), - along with a Grafana Dashboard. +* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics),以及 + Grafana 儀表板。 ✨ 想要更多 Trilium Notes 的主題、腳本、外掛與資源,亦可參考以下第三方資源 / 社群: From 884578ea9515cbc4b81a461cff1b1b6e8239e1d4 Mon Sep 17 00:00:00 2001 From: "Francis C." Date: Wed, 10 Dec 2025 07:39:56 +0100 Subject: [PATCH 162/873] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (116 of 116 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/zh_Hans/ --- docs/README-ZH_CN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README-ZH_CN.md b/docs/README-ZH_CN.md index 33b270eefa..682a30e3d0 100644 --- a/docs/README-ZH_CN.md +++ b/docs/README-ZH_CN.md @@ -95,8 +95,8 @@ Trilium Notes 是一款免费且开源、跨平台的阶层式笔记应用程序 * 用于快速保存网页内容的 [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) * 可自定义的 UI(侧边栏按钮、用户自定义小组件等) -* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), - along with a Grafana Dashboard. +* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics),以及 + Grafana 仪表板。 ✨ 想要更多 TriliumNext 的主题、脚本、外挂与资源,亦可参考以下第三方资源/社群: From 9897efe4afd740217de8c007850003a1c48ab6fe Mon Sep 17 00:00:00 2001 From: Tomas Adamek Date: Wed, 10 Dec 2025 16:17:41 +0100 Subject: [PATCH 163/873] Translated using Weblate (Czech) Currently translated at 5.3% (88 of 1658 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/cs/ --- apps/client/src/translations/cs/translation.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/cs/translation.json b/apps/client/src/translations/cs/translation.json index e2bf0bd0da..b5cb420326 100644 --- a/apps/client/src/translations/cs/translation.json +++ b/apps/client/src/translations/cs/translation.json @@ -108,6 +108,11 @@ "cloned_note_prefix_title": "Klonovaná poznámka se zobrazí ve stromu poznámek s danou předponou", "clone_to_selected_note": "Klonovat vybranou poznámku", "no_path_to_clone_to": "Žádná cest pro klonování.", - "note_cloned": "Poznámka: „{{clonedTitle}}“ bylo naklonováno do „{{targetTitle}}“" + "note_cloned": "Poznámka „{{clonedTitle}}“ bylo naklonována do „{{targetTitle}}“" + }, + "zpetne_odkazy": { + "backlink_one": "{{count}} zpětný odkaz", + "backlink_few": "{{count}} zpětné odkazy", + "backlink_other": "{{count}} zpětných odkazů" } } From 1711384eaa0a5ddf0ff4271634e349f35ed29d5a Mon Sep 17 00:00:00 2001 From: Tomas Adamek Date: Wed, 10 Dec 2025 16:23:09 +0100 Subject: [PATCH 164/873] Translated using Weblate (Czech) Currently translated at 35.3% (41 of 116 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/cs/ --- docs/README-cs.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/README-cs.md b/docs/README-cs.md index afe43e4870..aa93e2bd04 100644 --- a/docs/README-cs.md +++ b/docs/README-cs.md @@ -94,8 +94,8 @@ Naše dokumenatce je dostupná ve vícero formátech: bezpečnější přihlášení * [Synchronizace](https://docs.triliumnotes.org/user-guide/setup/synchronization) s vlastním synchronizačním serverem - * there are [3rd party services for hosting synchronisation - server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) + * existují [služby třetích stran pro hostování synchronizačních + serverů](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) * [Sdílení](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) (zveřejňování) poznámek na veřejném internetu * Silné [šifrování @@ -103,10 +103,10 @@ Naše dokumenatce je dostupná ve vícero formátech: s granularitou na úrovni jednotlivých poznámek * Náčrt diagramů na základě [Excalidraw](https://excalidraw.com/) (poznámka typu „plátno“) -* [Relation - maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and - [note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map) - for visualizing notes and their relations +* [Mapy vazeb](https://docs.triliumnotes.org/user-guide/note-types/relation-map) + a [Poznámka/mapa + odkazů](https://docs.triliumnotes.org/user-guide/note-types/note-map) pro + vizualizaci poznámek a jejich vazeb * Myšlenkové mapy založené na [Mind Elixir](https://docs.mind-elixir.com/) * [Geo mapy](https://docs.triliumnotes.org/user-guide/collections/geomap) s lokalizačními značkami a trasami GPX From 5d198819815cf5b3b4bc41387d6faa0d68b21f6b Mon Sep 17 00:00:00 2001 From: "Francis C." Date: Wed, 10 Dec 2025 07:38:07 +0100 Subject: [PATCH 165/873] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (1658 of 1658 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/ --- .../src/translations/tw/translation.json | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/apps/client/src/translations/tw/translation.json b/apps/client/src/translations/tw/translation.json index e9f88c0297..286144632c 100644 --- a/apps/client/src/translations/tw/translation.json +++ b/apps/client/src/translations/tw/translation.json @@ -205,7 +205,8 @@ "info": { "modalTitle": "資訊消息", "closeButton": "關閉", - "okButton": "確定" + "okButton": "確定", + "copy_to_clipboard": "複製到剪貼簿" }, "jump_to_note": { "search_button": "全文搜尋", @@ -986,7 +987,12 @@ "editable_text": { "placeholder": "在這裡輸入您的筆記內容…", "auto-detect-language": "自動檢測", - "keeps-crashing": "編輯元件持續發生崩潰。請嘗試重新啟動 Trilium。若問題仍存在,請考慮提交錯誤報告。" + "keeps-crashing": "編輯元件持續發生崩潰。請嘗試重新啟動 Trilium。若問題仍存在,請考慮提交錯誤報告。", + "editor_crashed_title": "文字編輯器崩潰", + "editor_crashed_content": "您的內容已成功恢復,但最近的幾項變更可能未被儲存。", + "editor_crashed_details_button": "檢視更多資訊⋯", + "editor_crashed_details_intro": "若您多次遇到此錯誤,請考慮在 GitHub 回報以下資訊。", + "editor_crashed_details_title": "技術資訊" }, "empty": { "open_note_instruction": "透過在下面的輸入框中輸入筆記標題或在樹中選擇筆記來打開筆記。", @@ -1531,7 +1537,9 @@ "printing_pdf": "正在匯出為 PDF…" }, "note_title": { - "placeholder": "請輸入筆記標題..." + "placeholder": "請輸入筆記標題...", + "created_on": "建立於 {{date}}", + "last_modified": "最後修改於 {{date}}" }, "search_result": { "no_notes_found": "沒有找到符合搜尋條件的筆記。", @@ -2106,5 +2114,26 @@ }, "popup-editor": { "maximize": "切換至完整編輯器" + }, + "experimental_features": { + "title": "實驗性選項", + "disclaimer": "這些選項屬實驗性質,可能導致系統不穩定。請謹慎使用。", + "new_layout_name": "新版面配置", + "new_layout_description": "體驗全新版面配置,呈現更現代的外觀與更佳的使用體驗。在未來版本將進行大幅調整。" + }, + "server": { + "unknown_http_error_title": "與伺服器通訊錯誤", + "unknown_http_error_content": "狀態碼:{{statusCode}}\n網址:{{method}} {{url}}\n訊息:{{message}}", + "traefik_blocks_requests": "若您正在使用 Traefik 反向代理,該代理已引入一項重大變更影響與伺服器的通訊。" + }, + "tab_history_navigation_buttons": { + "go-back": "返回前一筆記", + "go-forward": "前往下一筆記" + }, + "breadcrumb_badges": { + "read_only_explicit": "唯讀", + "read_only_auto": "自動唯讀", + "shared_publicly": "公開分享", + "shared_locally": "本地分享" } } From 2e44397c8838861c0fbb699d8f2496b6883b55f7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 00:18:56 +0200 Subject: [PATCH 166/873] feat(breadcrumb/note_info): get basic dropdown --- apps/client/src/widgets/BreadcrumbBadges.css | 8 ++++++ apps/client/src/widgets/BreadcrumbBadges.tsx | 28 ++++++++++++++++++- .../client/src/widgets/ribbon/NoteInfoTab.tsx | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index 7d6405159d..e3cd4f750c 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -68,4 +68,12 @@ padding: 0; } } + + .dropdown-note-info-badge { + ul { + list-style-type: none; + padding: 0.5em; + margin: 0; + } + } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 0e3cf986f2..32c2fd1874 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -17,10 +17,36 @@ export default function BreadcrumbBadges() { +
); } +function NoteInfoBadge() { + const { note } = useNoteContext(); + + return (note && + +
    + {note.type} {note.mime && ({note.mime})}} /> + {note.noteId}} /> +
+
+ ); +} + +function NoteInfoValue({ text, value }: { text: string; value: ComponentChildren }) { + return ( +
  • + {text}{": "} + {value} +
  • + ); +} + function ReadOnlyBadge() { const { note, noteContext } = useNoteContext(); const { isReadOnly, enableEditing } = useIsNoteReadOnly(note, noteContext); @@ -83,7 +109,7 @@ function BacklinksBadge() { } interface BadgeProps { - text: string; + text?: string; icon?: string; className: string; tooltip?: string; diff --git a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx index 8884128673..739b7012ab 100644 --- a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx +++ b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx @@ -13,7 +13,7 @@ import FNote from "../../entities/fnote"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); -export default function NoteInfoTab({ note }: TabContext) { +export default function NoteInfoTab({ note }: { note: FNote | null | undefined }) { const { isLoading, metadata, noteSizeResponse, subtreeSizeResponse, requestSizeInfo } = useNoteMetadata(note); return ( From e5696713de672abd97ea185034b9ac66f2053f58 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 00:23:32 +0200 Subject: [PATCH 167/873] feat(breadcrumb/note_info): modification/creation date --- apps/client/src/widgets/BreadcrumbBadges.tsx | 5 +++++ apps/client/src/widgets/NoteTitleDetails.tsx | 18 ++---------------- apps/client/src/widgets/ribbon/NoteInfoTab.tsx | 1 - 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 32c2fd1874..5bf68eff03 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -5,10 +5,12 @@ import { ComponentChildren, MouseEventHandler } from "preact"; import { useRef } from "preact/hooks"; import { t } from "../services/i18n"; +import { formatDateTime } from "../utils/formatters"; import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "./react/Dropdown"; import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; +import { useNoteMetadata } from "./ribbon/NoteInfoTab"; import { useShareInfo } from "./shared_info"; export default function BreadcrumbBadges() { @@ -24,6 +26,7 @@ export default function BreadcrumbBadges() { function NoteInfoBadge() { const { note } = useNoteContext(); + const { isLoading, metadata, noteSizeResponse, subtreeSizeResponse, requestSizeInfo } = useNoteMetadata(note); return (note &&
      + + {note.type} {note.mime && ({note.mime})}} /> {note.noteId}} />
    diff --git a/apps/client/src/widgets/NoteTitleDetails.tsx b/apps/client/src/widgets/NoteTitleDetails.tsx index 58f4da0f79..c9992578fa 100644 --- a/apps/client/src/widgets/NoteTitleDetails.tsx +++ b/apps/client/src/widgets/NoteTitleDetails.tsx @@ -9,26 +9,12 @@ import { useRef } from "preact/hooks"; export default function NoteTitleDetails() { const { note, noteContext } = useNoteContext(); - const { metadata } = useNoteMetadata(note); const isHiddenNote = note?.noteId.startsWith("_"); const isDefaultView = noteContext?.viewScope?.viewMode === "default"; - const items: ComponentChild[] = [ - (isDefaultView && !isHiddenNote && metadata?.dateCreated && - ), - (isDefaultView && !isHiddenNote && metadata?.dateModified && - ) - ].filter(item => !!item); + const items: ComponentChild[] = [].filter(item => !!item); - return ( + return items.length && (
    {joinElements(items, " • ")}
    diff --git a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx index 739b7012ab..d5af837998 100644 --- a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx +++ b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; -import { TabContext } from "./ribbon-interface"; import { MetadataResponse, NoteSizeResponse, SubtreeSizeResponse } from "@triliumnext/commons"; import server from "../../services/server"; import Button from "../react/Button"; From 4b74ad5577a767bac939f62fdfaa2163df00f67b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 00:34:25 +0200 Subject: [PATCH 168/873] feat(breadcrumb/note_info): note size --- apps/client/src/widgets/BreadcrumbBadges.tsx | 14 +++++-- .../client/src/widgets/ribbon/NoteInfoTab.tsx | 42 +++++++++++-------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 5bf68eff03..610796c7e3 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -10,7 +10,7 @@ import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "./react/Dropdown"; import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; -import { useNoteMetadata } from "./ribbon/NoteInfoTab"; +import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab"; import { useShareInfo } from "./shared_info"; export default function BreadcrumbBadges() { @@ -26,18 +26,20 @@ export default function BreadcrumbBadges() { function NoteInfoBadge() { const { note } = useNoteContext(); - const { isLoading, metadata, noteSizeResponse, subtreeSizeResponse, requestSizeInfo } = useNoteMetadata(note); + const { metadata, ...sizeProps } = useNoteMetadata(note); return (note &&
      {note.type} {note.mime && ({note.mime})}} /> {note.noteId}} /> + } />
    ); @@ -161,8 +163,14 @@ function BadgeWithDropdown({ children, tooltip, className, dropdownOptions, ...p hideToggleArrow title={tooltip} titlePosition="bottom" - dropdownOptions={{ popperConfig: { placement: "bottom", strategy: "fixed" } }} {...dropdownOptions} + dropdownOptions={{ + ...dropdownOptions?.dropdownOptions, + popperConfig: { + ...dropdownOptions?.dropdownOptions?.popperConfig, + placement: "bottom", strategy: "fixed" + } + }} >{children} ); } diff --git a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx index d5af837998..8180f74a6f 100644 --- a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx +++ b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx @@ -13,7 +13,7 @@ import FNote from "../../entities/fnote"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export default function NoteInfoTab({ note }: { note: FNote | null | undefined }) { - const { isLoading, metadata, noteSizeResponse, subtreeSizeResponse, requestSizeInfo } = useNoteMetadata(note); + const { metadata, ...sizeProps } = useNoteMetadata(note); return (
    @@ -41,23 +41,7 @@ export default function NoteInfoTab({ note }: { note: FNote | null | undefined }
    {t("note_info_widget.note_size")}: - {!isLoading && !noteSizeResponse && !subtreeSizeResponse && ( -
    @@ -66,6 +50,28 @@ export default function NoteInfoTab({ note }: { note: FNote | null | undefined } ); } +export function NoteSizeWidget({ isLoading, noteSizeResponse, subtreeSizeResponse, requestSizeInfo }: Omit, "metadata">) { + return <> + {!isLoading && !noteSizeResponse && !subtreeSizeResponse && ( +
    ); } -function NoteInfoBadge() { - const { note } = useNoteContext(); +export function NoteInfoBadge({ note }: { note: FNote | null | undefined }) { const { metadata, ...sizeProps } = useNoteMetadata(note); return (note && @@ -156,7 +155,7 @@ function BadgeWithDropdown({ children, tooltip, className, dropdownOptions, ...p }) { return ( } noDropdownListStyle noSelectButtonStyle From 68591fb51161ff114fc8748b9de5377453d2c596 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 16:48:49 +0200 Subject: [PATCH 178/873] feat(note_info): hide ribbon on new layout --- apps/client/src/widgets/ribbon/RibbonDefinition.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index a76e971401..e2779e287b 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -132,7 +132,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ { title: t("note_info_widget.title"), icon: "bx bx-info-circle", - show: ({ note }) => !!note, + show: ({ note }) => !isNewLayout && !!note, content: NoteInfoTab, toggleCommand: "toggleRibbonTabNoteInfo" } From a51820f5df7b83242e2eed912f8488d21116fdc5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 16:57:04 +0200 Subject: [PATCH 179/873] chore(note_info): address requested changes --- apps/client/src/widgets/BreadcrumbBadges.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 51c346749c..9f8b0de93b 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -34,8 +34,8 @@ export function NoteInfoBadge({ note }: { note: FNote | null | undefined }) { dropdownOptions={{ dropdownOptions: { autoClose: "outside" } }} >
      - - + + {note.type} {note.mime && ({note.mime})}} /> {note.noteId}} /> } /> From 1eee471018a493418e459e94a1a01c9d74bdf11c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 17:20:28 +0200 Subject: [PATCH 180/873] fix(breadcrumb_badges): temporarily editable showing up always in popup editor --- apps/client/src/widgets/BreadcrumbBadges.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 9f8b0de93b..f1c6dd60ea 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -57,7 +57,7 @@ function ReadOnlyBadge() { const { note, noteContext } = useNoteContext(); const { isReadOnly, enableEditing } = useIsNoteReadOnly(note, noteContext); const isExplicitReadOnly = note?.isLabelTruthy("readOnly"); - const isTemporarilyEditable = noteContext?.viewScope?.readOnlyTemporarilyDisabled; + const isTemporarilyEditable = noteContext?.ntxId !== "_popup-editor" && noteContext?.viewScope?.readOnlyTemporarilyDisabled; if (isTemporarilyEditable) { return Date: Thu, 11 Dec 2025 17:34:04 +0200 Subject: [PATCH 181/873] feat(breadcrumb_badges): integrate note properties tab --- .../src/translations/en/translation.json | 4 +++- apps/client/src/widgets/BreadcrumbBadges.css | 1 + apps/client/src/widgets/BreadcrumbBadges.tsx | 18 +++++++++++++++++- .../src/widgets/ribbon/RibbonDefinition.ts | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 9880e99b56..d92e21d266 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2146,6 +2146,8 @@ "backlinks_one": "{{count}} backlink", "backlinks_other": "{{count}} backlinks", "backlinks_description_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.", - "backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks." + "backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks.", + "clipped_note": "Web clip", + "clipped_note_description": "This note was originally taken from {{url}}.\n\nClick to navigate to the source webpage." } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index a8fcd6657f..ae8b4cad11 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -33,6 +33,7 @@ &.temporarily-editable-badge { --color: #4fa52b; } &.read-only-badge { --color: #e33f3b; } &.share-badge { --color: #3b82f6; } + &.clipped-note-badge { --color: #57a2a5; } &.backlinks-badge { color: var(--badge-text-color); } a { diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index f1c6dd60ea..42aaea065a 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -8,7 +8,7 @@ import { t } from "../services/i18n"; import { formatDateTime } from "../utils/formatters"; import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "./react/Dropdown"; -import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks"; +import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab"; import { useShareInfo } from "./shared_info"; @@ -20,6 +20,7 @@ export default function BreadcrumbBadges() { +
    ); } @@ -114,6 +115,21 @@ function BacklinksBadge() { ); } +function ClippedNoteBadge() { + const { note } = useNoteContext(); + const [ pageUrl ] = useNoteLabel(note, "pageUrl"); + + return (pageUrl && + + ); +} + interface BadgeProps { text?: string; icon?: string; diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index e2779e287b..2845476397 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -67,7 +67,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ title: t("note_properties.info"), icon: "bx bx-info-square", content: NotePropertiesTab, - show: ({ note }) => !!note?.getLabelValue("pageUrl"), + show: ({ note }) => !isNewLayout && !!note?.getLabelValue("pageUrl"), activate: true }, { From a9b453c27a636a6ad794a036587c3d03ae98f454 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 17:43:00 +0200 Subject: [PATCH 182/873] feat(breadcrumb_badges): integrate query/script tab --- .../src/translations/en/translation.json | 6 ++++- apps/client/src/widgets/BreadcrumbBadges.css | 1 + apps/client/src/widgets/BreadcrumbBadges.tsx | 22 ++++++++++++++++++- .../src/widgets/ribbon/RibbonDefinition.ts | 2 +- packages/commons/src/lib/attribute_names.ts | 1 + 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index d92e21d266..1159a8bf81 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2148,6 +2148,10 @@ "backlinks_description_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.", "backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks.", "clipped_note": "Web clip", - "clipped_note_description": "This note was originally taken from {{url}}.\n\nClick to navigate to the source webpage." + "clipped_note_description": "This note was originally taken from {{url}}.\n\nClick to navigate to the source webpage.", + "execute_script": "Run script", + "execute_script_description": "This note is a script note. Click to execute the script.", + "execute_sql": "Run SQL", + "execute_sql_description": "This note is a SQL note. Click to execute the SQL query." } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index ae8b4cad11..758678b01f 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -35,6 +35,7 @@ &.share-badge { --color: #3b82f6; } &.clipped-note-badge { --color: #57a2a5; } &.backlinks-badge { color: var(--badge-text-color); } + &.execute-badge { --color: #f59e0b; } a { color: inherit !important; diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 42aaea065a..a7ccbae9f6 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -8,7 +8,7 @@ import { t } from "../services/i18n"; import { formatDateTime } from "../utils/formatters"; import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "./react/Dropdown"; -import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useStaticTooltip } from "./react/hooks"; +import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab"; import { useShareInfo } from "./shared_info"; @@ -21,6 +21,7 @@ export default function BreadcrumbBadges() { +
    ); } @@ -130,6 +131,25 @@ function ClippedNoteBadge() { ); } +function ExecuteBadge() { + const { note, parentComponent } = useNoteContext(); + const isScript = note?.isTriliumScript(); + const isSql = note?.isTriliumSqlite(); + const isExecutable = isScript || isSql; + const [ executeDescription ] = useNoteLabel(note, "executeDescription"); + const [ executeButton ] = useNoteLabelBoolean(note, "executeButton"); + + return (note && isExecutable && (executeDescription || executeButton) && + parentComponent.triggerCommand("runActiveNote")} + /> + ); +} + interface BadgeProps { text?: string; icon?: string; diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index 2845476397..766f07df79 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -38,7 +38,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ icon: "bx bx-play", content: ScriptTab, activate: true, - show: ({ note }) => note && + show: ({ note }) => note && !isNewLayout && (note.isTriliumScript() || note.isTriliumSqlite()) && (note.hasLabel("executeDescription") || note.hasLabel("executeButton")) }, diff --git a/packages/commons/src/lib/attribute_names.ts b/packages/commons/src/lib/attribute_names.ts index 0295af0b2c..ebbe221237 100644 --- a/packages/commons/src/lib/attribute_names.ts +++ b/packages/commons/src/lib/attribute_names.ts @@ -5,6 +5,7 @@ type Labels = { color: string; iconClass: string; workspaceIconClass: string; + executeButton: boolean; executeDescription: string; executeTitle: string; limit: string; // should be probably be number From 0856d3dbdfd89fb04a995ea185b3ac89980c035b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 18:00:49 +0200 Subject: [PATCH 183/873] fix(layout): note title padding on full-height note --- apps/client/src/widgets/note_title.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 8769c74aee..174c7d7865 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -75,7 +75,8 @@ body.experimental-feature-new-layout { } } - .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)) { + .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)), + .note-split.type-book { .title-row, .title-details { width: 100%; @@ -84,7 +85,7 @@ body.experimental-feature-new-layout { } .title-row { - margin-top: 0; + padding: 0; } .title-details { From 0eed72b88843b3a7aad1c64d69cd52ebb56ffb50 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 18:53:48 +0200 Subject: [PATCH 184/873] feat(note_bars): view type switcher --- apps/client/src/widgets/NoteTitleDetails.tsx | 45 ++------------- .../note_bars/CollectionProperties.tsx | 29 ++++++++++ apps/client/src/widgets/note_title.css | 3 +- .../ribbon/CollectionPropertiesTab.tsx | 55 +++++++++++-------- 4 files changed, 67 insertions(+), 65 deletions(-) create mode 100644 apps/client/src/widgets/note_bars/CollectionProperties.tsx diff --git a/apps/client/src/widgets/NoteTitleDetails.tsx b/apps/client/src/widgets/NoteTitleDetails.tsx index c9992578fa..60618ecb55 100644 --- a/apps/client/src/widgets/NoteTitleDetails.tsx +++ b/apps/client/src/widgets/NoteTitleDetails.tsx @@ -1,46 +1,13 @@ -import { type ComponentChild } from "preact"; - -import { formatDateTime } from "../utils/formatters"; -import { useNoteContext, useStaticTooltip } from "./react/hooks"; -import { joinElements } from "./react/react_utils"; -import { useNoteMetadata } from "./ribbon/NoteInfoTab"; -import { Trans } from "react-i18next"; -import { useRef } from "preact/hooks"; +import CollectionProperties from "./note_bars/CollectionProperties"; +import { useNoteContext, useNoteProperty } from "./react/hooks"; export default function NoteTitleDetails() { - const { note, noteContext } = useNoteContext(); - const isHiddenNote = note?.noteId.startsWith("_"); - const isDefaultView = noteContext?.viewScope?.viewMode === "default"; + const { note } = useNoteContext(); + const noteType = useNoteProperty(note, "type"); - const items: ComponentChild[] = [].filter(item => !!item); - - return items.length && ( + return (
    - {joinElements(items, " • ")} + {note && noteType === "book" && }
    ); } - -function TextWithValue({ i18nKey, value, valueTooltip }: { - i18nKey: string; - value: string; - valueTooltip: string; -}) { - const listItemRef = useRef(null); - useStaticTooltip(listItemRef, { - selector: "span.value", - title: valueTooltip, - popperConfig: { placement: "bottom" } - }); - - return ( -
  • - {value} as React.ReactElement - }} - /> -
  • - ); -} diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx new file mode 100644 index 0000000000..8436f3af6f --- /dev/null +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -0,0 +1,29 @@ +import FNote from "../../entities/fnote"; +import { ViewTypeOptions } from "../collections/interface"; +import Dropdown from "../react/Dropdown"; +import { FormListItem } from "../react/FormList"; +import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; + +export default function CollectionProperties({ note }: { note: FNote }) { + return ( + + ); +} + +function ViewTypeSwitcher({ note }: { note: FNote }) { + const [ viewType, setViewType ] = useViewType(note); + + return ( + + {Object.entries(VIEW_TYPE_MAPPINGS).map(([ key, label ]) => ( + setViewType(key)} + checked={viewType === key} + >{label} + ))} + + ); +} diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 174c7d7865..668695cd97 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -89,8 +89,7 @@ body.experimental-feature-new-layout { } .title-details { - margin-bottom: 0.2em; - opacity: 0.65; + padding-bottom: 0.2em; font-size: 0.8em; } } diff --git a/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx b/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx index 374633c49a..7df0708796 100644 --- a/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx @@ -12,34 +12,41 @@ import FormCheckbox from "../react/FormCheckbox"; import FormTextBox from "../react/FormTextBox"; import { ComponentChildren } from "preact"; import { ViewTypeOptions } from "../collections/interface"; -import { FormDropdownDivider, FormListItem } from "../react/FormList"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; -const VIEW_TYPE_MAPPINGS: Record = { - grid: t("book_properties.grid"), - list: t("book_properties.list"), - calendar: t("book_properties.calendar"), - table: t("book_properties.table"), - geoMap: t("book_properties.geo-map"), - board: t("book_properties.board"), - presentation: t("book_properties.presentation") +export const VIEW_TYPE_MAPPINGS: Record = { + grid: t("book_properties.grid"), + list: t("book_properties.list"), + calendar: t("book_properties.calendar"), + table: t("book_properties.table"), + geoMap: t("book_properties.geo-map"), + board: t("book_properties.board"), + presentation: t("book_properties.presentation") }; -export default function CollectionPropertiesTab({ note }: TabContext) { - const [ viewType, setViewType ] = useNoteLabel(note, "viewType"); - const defaultViewType = (note?.type === "search" ? "list" : "grid"); - const viewTypeWithDefault = (viewType ?? defaultViewType) as ViewTypeOptions; - const properties = bookPropertiesConfig[viewTypeWithDefault].properties; +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); - return ( -
    - {note && ( - <> - - - - )} -
    - ); +export default function CollectionPropertiesTab({ note }: TabContext) { + const [viewType, setViewType] = useViewType(note); + const properties = bookPropertiesConfig[viewType].properties; + + return ( +
    + {note && ( + <> + {!isNewLayout && } + + + )} +
    + ); +} + +export function useViewType(note: FNote | null | undefined) { + const [ viewType, setViewType ] = useNoteLabel(note, "viewType"); + const defaultViewType = (note?.type === "search" ? "list" : "grid"); + const viewTypeWithDefault = (viewType ?? defaultViewType) as ViewTypeOptions; + return [ viewTypeWithDefault, setViewType ] as const; } function CollectionTypeSwitcher({ viewType, setViewType }: { viewType: string, setViewType: (newValue: string) => void }) { From b540111fa459a9acc3eabeac8a63833869914d43 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 18:59:28 +0200 Subject: [PATCH 185/873] feat(note_bars): add icons to view type switcher --- .../note_bars/CollectionProperties.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 8436f3af6f..ac16eb1e52 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -2,8 +2,19 @@ import FNote from "../../entities/fnote"; import { ViewTypeOptions } from "../collections/interface"; import Dropdown from "../react/Dropdown"; import { FormListItem } from "../react/FormList"; +import Icon from "../react/Icon"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; +const ICON_MAPPINGS: Record = { + grid: "bx bxs-grid", + list: "bx bx-list-ul", + calendar: "bx bx-calendar", + table: "bx bx-table", + geoMap: "bx bx-map-alt", + board: "bx bx-columns", + presentation: "bx bx-rectangle" +}; + export default function CollectionProperties({ note }: { note: FNote }) { return ( @@ -15,13 +26,18 @@ function ViewTypeSwitcher({ note }: { note: FNote }) { return ( +   + {VIEW_TYPE_MAPPINGS[viewType]} + } > {Object.entries(VIEW_TYPE_MAPPINGS).map(([ key, label ]) => ( setViewType(key)} - checked={viewType === key} + selected={viewType === key} + disabled={viewType === key} + icon={ICON_MAPPINGS[key as ViewTypeOptions]} >{label} ))} From fec5ee9335b8b797a12ef6152f7d0d2281b820e0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 19:17:23 +0200 Subject: [PATCH 186/873] feat(note_bars/collection): integrate show archived notes --- .../note_bars/CollectionProperties.tsx | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index ac16eb1e52..8cbbc540bf 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -1,9 +1,12 @@ +import { t } from "i18next"; import FNote from "../../entities/fnote"; import { ViewTypeOptions } from "../collections/interface"; import Dropdown from "../react/Dropdown"; -import { FormListItem } from "../react/FormList"; +import { FormListItem, FormListToggleableItem } from "../react/FormList"; import Icon from "../react/Icon"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; +import { BookProperty, CheckBoxProperty } from "../ribbon/collection-properties-config"; +import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks"; const ICON_MAPPINGS: Record = { grid: "bx bxs-grid", @@ -17,7 +20,10 @@ const ICON_MAPPINGS: Record = { export default function CollectionProperties({ note }: { note: FNote }) { return ( - + <> + + + ); } @@ -43,3 +49,36 @@ function ViewTypeSwitcher({ note }: { note: FNote }) { ); } + +function ViewOptions({ note }: { note: FNote }) { + return ( + + + + ); +} + +function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) { + switch (property.type) { + case "checkbox": + return ; + } +} + +function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) { + const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel); + return ( + + ); +} From 0de67b6a69a00607ec461fb0510de0fc5593df74 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 19:29:27 +0200 Subject: [PATCH 187/873] feat(note_bars/collection): support button properties --- .../note_bars/CollectionProperties.tsx | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 8cbbc540bf..0de1e363a6 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -2,11 +2,13 @@ import { t } from "i18next"; import FNote from "../../entities/fnote"; import { ViewTypeOptions } from "../collections/interface"; import Dropdown from "../react/Dropdown"; -import { FormListItem, FormListToggleableItem } from "../react/FormList"; +import { FormDropdownDivider, FormListItem, FormListToggleableItem } from "../react/FormList"; import Icon from "../react/Icon"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; -import { BookProperty, CheckBoxProperty } from "../ribbon/collection-properties-config"; +import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty } from "../ribbon/collection-properties-config"; import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks"; +import { useContext } from "preact/hooks"; +import { ParentComponent } from "../react/react_utils"; const ICON_MAPPINGS: Record = { grid: "bx bxs-grid", @@ -19,17 +21,17 @@ const ICON_MAPPINGS: Record = { }; export default function CollectionProperties({ note }: { note: FNote }) { + const [ viewType, setViewType ] = useViewType(note); + return ( <> - - + + ); } -function ViewTypeSwitcher({ note }: { note: FNote }) { - const [ viewType, setViewType ] = useViewType(note); - +function ViewTypeSwitcher({ note, viewType, setViewType }: { note: FNote, viewType: ViewTypeOptions, setViewType: (newValue: ViewTypeOptions) => void }) { return ( @@ -40,7 +42,7 @@ function ViewTypeSwitcher({ note }: { note: FNote }) { {Object.entries(VIEW_TYPE_MAPPINGS).map(([ key, label ]) => ( setViewType(key)} + onClick={() => setViewType(key as ViewTypeOptions)} selected={viewType === key} disabled={viewType === key} icon={ICON_MAPPINGS[key as ViewTypeOptions]} @@ -50,7 +52,9 @@ function ViewTypeSwitcher({ note }: { note: FNote }) { ); } -function ViewOptions({ note }: { note: FNote }) { +function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOptions }) { + const properties = bookPropertiesConfig[viewType].properties; + return ( + + {properties.length > 0 && } + {properties.map(property => ( + + ))} ); } function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) { switch (property.type) { + case "button": + return ; case "checkbox": return ; } } +function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) { + const parentComponent = useContext(ParentComponent); + + return ( + { + if (!parentComponent) return; + property.onClick({ + note, + triggerCommand: parentComponent.triggerCommand.bind(parentComponent) + }); + }} + >{property.label} + ); +} + function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) { const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel); return ( @@ -82,3 +111,4 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check /> ); } + From a1513a35673d6a34a3dc2df21d24b0e6c74d3945 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 19:34:22 +0200 Subject: [PATCH 188/873] feat(note_bars/collection): support split button properties --- .../widgets/note_bars/CollectionProperties.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 0de1e363a6..830ef67c2e 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -2,10 +2,10 @@ import { t } from "i18next"; import FNote from "../../entities/fnote"; import { ViewTypeOptions } from "../collections/interface"; import Dropdown from "../react/Dropdown"; -import { FormDropdownDivider, FormListItem, FormListToggleableItem } from "../react/FormList"; +import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; import Icon from "../react/Icon"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; -import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty } from "../ribbon/collection-properties-config"; +import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks"; import { useContext } from "preact/hooks"; import { ParentComponent } from "../react/react_utils"; @@ -78,6 +78,8 @@ function ViewProperty({ note, property }: { note: FNote, property: BookProperty switch (property.type) { case "button": return ; + case "split-button": + return ; case "checkbox": return ; } @@ -101,6 +103,17 @@ function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonP ); } +function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) { + const parentComponent = useContext(ParentComponent); + const ItemsComponent = property.items; + + return (parentComponent && + + + + ); +} + function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) { const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel); return ( From 1a9fb34a6e946ef4acd023c7d2bdba2ba7f1df7f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 19:37:04 +0200 Subject: [PATCH 189/873] feat(note_bars/collection): support dropdown menu click action --- .../src/widgets/note_bars/CollectionProperties.tsx | 10 +++++++++- apps/client/src/widgets/react/FormList.tsx | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 830ef67c2e..bee74b5135 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -106,9 +106,17 @@ function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonP function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) { const parentComponent = useContext(ParentComponent); const ItemsComponent = property.items; + const clickContext = parentComponent && { + note, + triggerCommand: parentComponent.triggerCommand.bind(parentComponent) + }; return (parentComponent && - + clickContext && property.onClick(clickContext)} + > ); diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index dd5948cb3b..42f43a044c 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -206,10 +206,11 @@ export function FormDropdownDivider() { return
    ; } -export function FormDropdownSubmenu({ icon, title, children, dropStart }: { +export function FormDropdownSubmenu({ icon, title, children, dropStart, onDropdownToggleClicked }: { icon: string, title: ComponentChildren, children: ComponentChildren, + onDropdownToggleClicked?: () => void, dropStart?: boolean }) { const [ openOnMobile, setOpenOnMobile ] = useState(false); @@ -224,6 +225,10 @@ export function FormDropdownSubmenu({ icon, title, children, dropStart }: { if (isMobile()) { setOpenOnMobile(!openOnMobile); } + + if (onDropdownToggleClicked) { + onDropdownToggleClicked(); + } }} > {" "} From 9f4757af5b0179e049ac49681823f58434af6320 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 19:39:07 +0200 Subject: [PATCH 190/873] chore(note_bars/collection): put archived notes at the end --- .../src/widgets/note_bars/CollectionProperties.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index bee74b5135..5f53f56851 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -6,7 +6,7 @@ import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListTogglea import Icon from "../react/Icon"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; -import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks"; +import { useNoteLabelBoolean } from "../react/hooks"; import { useContext } from "preact/hooks"; import { ParentComponent } from "../react/react_utils"; @@ -60,16 +60,16 @@ function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOption buttonClassName="bx bx-cog icon-action" hideToggleArrow > + {properties.map(property => ( + + ))} + {properties.length > 0 && } + - - {properties.length > 0 && } - {properties.map(property => ( - - ))} ); } From e766b8241809a4ea1ea0aceea313e15f4799fdda Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 19:44:22 +0200 Subject: [PATCH 191/873] feat(note_bars/collection): add icon to checkboxes --- apps/client/src/widgets/note_bars/CollectionProperties.tsx | 2 ++ .../src/widgets/ribbon/collection-properties-config.tsx | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 5f53f56851..7042e80d35 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -67,6 +67,7 @@ function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOption @@ -126,6 +127,7 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel); return ( + bindToLabel: FilterLabelsByType; + icon?: string; } export interface ButtonProperty { @@ -107,11 +108,13 @@ export const bookPropertiesConfig: Record = { properties: [ { label: t("book_properties_config.hide-weekends"), + icon: "bx bx-calendar-week", type: "checkbox", bindToLabel: "calendar:hideWeekends" }, { label: t("book_properties_config.display-week-numbers"), + icon: "bx bx-hash", type: "checkbox", bindToLabel: "calendar:weekNumbers" } @@ -147,6 +150,7 @@ export const bookPropertiesConfig: Record = { }, { label: t("book_properties_config.show-scale"), + icon: "bx bx-ruler", type: "checkbox", bindToLabel: "map:scale" } From 00df3c3d1fde68c9dcde4a95b89605da0eee2011 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 19:51:40 +0200 Subject: [PATCH 192/873] feat(note_bars/collection): support number fields --- .../note_bars/CollectionProperties.tsx | 30 +++++++++++++++++-- apps/client/src/widgets/note_title.css | 9 ++++++ .../ribbon/CollectionPropertiesTab.tsx | 2 +- .../ribbon/collection-properties-config.tsx | 2 ++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 7042e80d35..e8b1c82bc6 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -5,10 +5,11 @@ import Dropdown from "../react/Dropdown"; import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; import Icon from "../react/Icon"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; -import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; -import { useNoteLabelBoolean } from "../react/hooks"; +import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; +import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks"; import { useContext } from "preact/hooks"; import { ParentComponent } from "../react/react_utils"; +import FormTextBox from "../react/FormTextBox"; const ICON_MAPPINGS: Record = { grid: "bx bxs-grid", @@ -83,6 +84,8 @@ function ViewProperty({ note, property }: { note: FNote, property: BookProperty return ; case "checkbox": return ; + case "number": + return ; } } @@ -123,6 +126,29 @@ function SplitButtonPropertyView({ note, property }: { note: FNote, property: Sp ); } +function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) { + //@ts-expect-error Interop with text box which takes in string values even for numbers. + const [ value, setValue ] = useNoteLabel(note, property.bindToLabel); + const disabled = property.disabled?.(note); + + return ( + e.stopPropagation()} + > + {property.label} + + + ); +} + function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) { const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel); return ( diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 668695cd97..0d9912b339 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -37,6 +37,15 @@ body.experimental-feature-new-layout { padding-inline-start: 24px; } + .title-details { + .dropdown-menu { + input.form-control { + padding: 2px 8px; + margin-left: 1em; + } + } + } + .title-row { margin-left: 12px; diff --git a/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx b/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx index 7df0708796..4af8247a3f 100644 --- a/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx @@ -155,7 +155,7 @@ function NumberPropertyView({ note, property }: { note: FNote, property: NumberP diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.tsx b/apps/client/src/widgets/ribbon/collection-properties-config.tsx index e8db421d38..9dddae75a6 100644 --- a/apps/client/src/widgets/ribbon/collection-properties-config.tsx +++ b/apps/client/src/widgets/ribbon/collection-properties-config.tsx @@ -41,6 +41,7 @@ export interface NumberProperty { bindToLabel: FilterLabelsByType; width?: number; min?: number; + icon?: string; disabled?: (note: FNote) => boolean; } @@ -160,6 +161,7 @@ export const bookPropertiesConfig: Record = { properties: [ { label: t("book_properties_config.max-nesting-depth"), + icon: "bx bx-subdirectory-right", type: "number", bindToLabel: "maxNestingDepth", width: 65, From 53b7d93efbc4dab83a5647af47136a53eaf8561c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 20:09:25 +0200 Subject: [PATCH 193/873] feat(note_bars/collection): support comboboxes --- .../src/translations/en/translation.json | 2 +- .../note_bars/CollectionProperties.tsx | 44 ++++++++++++++++++- .../ribbon/collection-properties-config.tsx | 5 ++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 1159a8bf81..2e63ef7772 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2031,7 +2031,7 @@ "book_properties_config": { "hide-weekends": "Hide weekends", "display-week-numbers": "Display week numbers", - "map-style": "Map style:", + "map-style": "Map style", "max-nesting-depth": "Max nesting depth:", "raster": "Raster", "vector_light": "Vector (Light)", diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index e8b1c82bc6..142ed6ca61 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -5,11 +5,12 @@ import Dropdown from "../react/Dropdown"; import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; import Icon from "../react/Icon"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; -import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; -import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks"; +import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; +import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault } from "../react/hooks"; import { useContext } from "preact/hooks"; import { ParentComponent } from "../react/react_utils"; import FormTextBox from "../react/FormTextBox"; +import { Fragment } from "preact/jsx-runtime"; const ICON_MAPPINGS: Record = { grid: "bx bxs-grid", @@ -86,6 +87,8 @@ function ViewProperty({ note, property }: { note: FNote, property: BookProperty return ; case "number": return ; + case "combobox": + return ; } } @@ -149,6 +152,43 @@ function NumberPropertyView({ note, property }: { note: FNote, property: NumberP ); } +function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) { + const [ value, setValue ] = useNoteLabelWithDefault(note, property.bindToLabel, property.defaultValue ?? ""); + + function renderItem(option: ComboBoxItem) { + return ( + setValue(option.value)} + > + {option.label} + + ); + } + + return ( + + {(property.options).map((option, index) => { + if ("items" in option) { + return ( + + {option.title} + {option.items.map(renderItem)} + {index < property.options.length - 1 && } + + ); + } else { + return renderItem(option); + } + })} + + ) +} + function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) { const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel); return ( diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.tsx b/apps/client/src/widgets/ribbon/collection-properties-config.tsx index 9dddae75a6..1f79217e9c 100644 --- a/apps/client/src/widgets/ribbon/collection-properties-config.tsx +++ b/apps/client/src/widgets/ribbon/collection-properties-config.tsx @@ -45,7 +45,7 @@ export interface NumberProperty { disabled?: (note: FNote) => boolean; } -interface ComboBoxItem { +export interface ComboBoxItem { value: string; label: string; } @@ -58,6 +58,7 @@ interface ComboBoxGroup { export interface ComboBoxProperty { type: "combobox", label: string; + icon?: string; bindToLabel: FilterLabelsByType; /** * The default value is used when the label is not set. @@ -125,6 +126,7 @@ export const bookPropertiesConfig: Record = { properties: [ { label: t("book_properties_config.map-style"), + icon: "bx bx-palette", type: "combobox", bindToLabel: "map:style", defaultValue: DEFAULT_MAP_LAYER_NAME, @@ -177,6 +179,7 @@ export const bookPropertiesConfig: Record = { { label: "Theme", type: "combobox", + icon: "bx bx-palette", bindToLabel: "presentation:theme", defaultValue: DEFAULT_THEME, options: getPresentationThemes().map(theme => ({ From 8ab9e304044beddf7aba0243bd45e99f2dde2d51 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 20:13:04 +0200 Subject: [PATCH 194/873] chore(note_bars/collection): disable ribbon tab --- .../widgets/note_bars/CollectionProperties.tsx | 15 ++++++++------- .../client/src/widgets/ribbon/RibbonDefinition.ts | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 142ed6ca61..ab74b43243 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -1,16 +1,17 @@ import { t } from "i18next"; +import { useContext } from "preact/hooks"; +import { Fragment } from "preact/jsx-runtime"; + import FNote from "../../entities/fnote"; import { ViewTypeOptions } from "../collections/interface"; import Dropdown from "../react/Dropdown"; import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; -import Icon from "../react/Icon"; -import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; -import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; -import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault } from "../react/hooks"; -import { useContext } from "preact/hooks"; -import { ParentComponent } from "../react/react_utils"; import FormTextBox from "../react/FormTextBox"; -import { Fragment } from "preact/jsx-runtime"; +import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault } from "../react/hooks"; +import Icon from "../react/Icon"; +import { ParentComponent } from "../react/react_utils"; +import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; +import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; const ICON_MAPPINGS: Record = { grid: "bx bxs-grid", diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index 766f07df79..d29cd181fd 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -60,7 +60,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ title: t("book_properties.book_properties"), icon: "bx bx-book", content: CollectionPropertiesTab, - show: ({ note }) => note?.type === "book" || note?.type === "search", + show: ({ note }) => !isNewLayout && note?.type === "book" || note?.type === "search", toggleCommand: "toggleRibbonTabBookProperties" }, { From c76ff2d371dc0ca93ecb71eae153be94492ce19a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 20:19:06 +0200 Subject: [PATCH 195/873] feat(note_bars/collection): add a help button --- .../widgets/FloatingButtonsDefinitions.tsx | 3 ++- .../note_bars/CollectionProperties.tsx | 22 ++++++++++++++++--- apps/client/src/widgets/note_title.css | 6 +++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 1c7a904a9d..171ea2fa80 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -300,8 +300,9 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB function InAppHelpButton({ note }: FloatingButtonContext) { const helpUrl = getHelpUrlForNote(note); + const isEnabled = !!helpUrl && (!isNewLayout || (note?.type !== "book")); - return !!helpUrl && ( + return isEnabled && ( = { grid: "bx bxs-grid", @@ -28,13 +31,15 @@ export default function CollectionProperties({ note }: { note: FNote }) { return ( <> - + +
    + ); } -function ViewTypeSwitcher({ note, viewType, setViewType }: { note: FNote, viewType: ViewTypeOptions, setViewType: (newValue: ViewTypeOptions) => void }) { +function ViewTypeSwitcher({ viewType, setViewType }: { viewType: ViewTypeOptions, setViewType: (newValue: ViewTypeOptions) => void }) { return ( @@ -187,7 +192,7 @@ function ComboBoxPropertyView({ note, property }: { note: FNote, property: Combo } })} - ) + ); } function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) { @@ -202,3 +207,14 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check ); } +function HelpButton({ note }: { note: FNote }) { + const helpUrl = getHelpUrlForNote(note); + + return (helpUrl && ( + openInAppHelpFromUrl(helpUrl))} + text={t("help-button.title")} + /> + )); +} diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 0d9912b339..79734e9288 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -38,12 +38,18 @@ body.experimental-feature-new-layout { } .title-details { + padding-inline-end: 16px; + .dropdown-menu { input.form-control { padding: 2px 8px; margin-left: 1em; } } + + .spacer { + flex-grow: 1; + } } .title-row { From e9ac69b8e5892d52ba1d8d0f439355e96f166af6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 20:33:52 +0200 Subject: [PATCH 196/873] chore(note_bars/collection): address change request --- apps/client/src/widgets/note_bars/CollectionProperties.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 17e4242246..bf05a131e7 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -69,7 +69,7 @@ function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOption hideToggleArrow > {properties.map(property => ( - + ))} {properties.length > 0 && } From bd9fe14a6cd301c4c59dcb5df0e280cb7ae4789b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 21:08:36 +0200 Subject: [PATCH 197/873] chore(layout): remove title extra spacing for now --- apps/client/src/widgets/note_title.css | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 79734e9288..6a6ae98084 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -53,8 +53,6 @@ body.experimental-feature-new-layout { } .title-row { - margin-left: 12px; - .note-icon-widget { padding: 0; width: 41px; @@ -79,17 +77,6 @@ body.experimental-feature-new-layout { } } - .note-split.view-mode-default { - .title-row { - padding-top: 2em; - box-sizing: content-box; - } - - .title-details { - padding-bottom: 2em; - } - } - .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)), .note-split.type-book { .title-row, @@ -99,10 +86,6 @@ body.experimental-feature-new-layout { padding-inline-start: 15px; } - .title-row { - padding: 0; - } - .title-details { padding-bottom: 0.2em; font-size: 0.8em; From 726d6aad6522ce566b9c01d7ab3355216dacd5ff Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 22:01:22 +0200 Subject: [PATCH 198/873] feat(layout): integrate note map --- .../src/components/root_command_executor.ts | 16 ++++++++++++++++ apps/client/src/services/link.ts | 2 +- apps/client/src/services/utils.ts | 2 +- apps/client/src/translations/en/translation.json | 3 ++- apps/client/src/widgets/NoteDetail.tsx | 4 +++- apps/client/src/widgets/ribbon/NoteActions.tsx | 1 + apps/client/src/widgets/ribbon/Ribbon.tsx | 2 +- .../src/widgets/ribbon/RibbonDefinition.ts | 2 +- apps/client/src/widgets/type_widgets/NoteMap.tsx | 7 +++++-- 9 files changed, 31 insertions(+), 8 deletions(-) diff --git a/apps/client/src/components/root_command_executor.ts b/apps/client/src/components/root_command_executor.ts index 4a1c987f76..dc614f5558 100644 --- a/apps/client/src/components/root_command_executor.ts +++ b/apps/client/src/components/root_command_executor.ts @@ -193,6 +193,22 @@ export default class RootCommandExecutor extends Component { appContext.triggerEvent("zenModeChanged", { isEnabled }); } + async toggleRibbonTabNoteMapCommand() { + const { isExperimentalFeatureEnabled } = await import("../services/experimental_features.js"); + const isNewLayout = isExperimentalFeatureEnabled("new-layout"); + if (!isNewLayout) return; + + const activeContext = appContext.tabManager.getActiveContext(); + if (!activeContext) return; + + const subContexts = activeContext.getSubContexts(); + appContext.triggerCommand("openNewNoteSplit", { + ntxId: subContexts[subContexts.length - 1].ntxId, + notePath: activeContext.notePath, + viewScope: { viewMode: "note-map" } + }); + } + firstTabCommand() { this.#goToTab(1); } diff --git a/apps/client/src/services/link.ts b/apps/client/src/services/link.ts index 79bc18c2ac..4ff3a39cff 100644 --- a/apps/client/src/services/link.ts +++ b/apps/client/src/services/link.ts @@ -27,7 +27,7 @@ async function getLinkIcon(noteId: string, viewMode: ViewMode | undefined) { return icon; } -export type ViewMode = "default" | "source" | "attachments" | "contextual-help"; +export type ViewMode = "default" | "source" | "attachments" | "contextual-help" | "note-map"; export interface ViewScope { /** diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 2c999690d4..aa4f0cebcc 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -460,7 +460,7 @@ export async function openInAppHelpFromUrl(inAppHelpPage: string) { notePath: targetNote, hoistedNoteId: "_help", viewScope - }) + }); } else { // There is already a help window open, make sure it opens on the right note. helpSubcontext.setNote(targetNote, { viewScope }); diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 2e63ef7772..7f49dea52b 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -694,7 +694,8 @@ "convert_into_attachment_failed": "Converting note '{{title}}' failed.", "convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.", "convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?", - "print_pdf": "Export as PDF..." + "print_pdf": "Export as PDF...", + "note_map": "Note map" }, "onclick_button": { "no_click_handler": "Button widget '{{componentId}}' has no defined click handler" diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index 272385d258..a5bc629162 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -299,8 +299,10 @@ async function getWidgetType(note: FNote | null | undefined, noteContext: NoteCo if (noteContext?.viewScope?.viewMode === "source") { resultingType = "readOnlyCode"; - } else if (noteContext?.viewScope && noteContext.viewScope.viewMode === "attachments") { + } else if (noteContext.viewScope?.viewMode === "attachments") { resultingType = noteContext.viewScope.attachmentId ? "attachmentDetail" : "attachmentList"; + } else if (noteContext.viewScope?.viewMode === "note-map") { + resultingType = "noteMap"; } else if (type === "text" && (await noteContext?.isReadOnly())) { resultingType = "readOnlyText"; } else if ((type === "code" || type === "mermaid") && (await noteContext?.isReadOnly())) { diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 85ce630139..3d1f8b4cc0 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -116,6 +116,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not + {isNewLayout && } {glob.isDev && <> diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index 3afd39b56b..aff2c148f5 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -67,7 +67,7 @@ export default function Ribbon({ children }: { children?: preact.ComponentChildr useTriliumEvents(eventsToListenTo, useCallback((e, toggleCommand) => { if (!computedTabs) return; const correspondingTab = computedTabs.find(tab => tab.toggleCommand === toggleCommand); - if (correspondingTab) { + if (correspondingTab?.shouldShow) { if (activeTabIndex !== correspondingTab.index) { setActiveTabIndex(correspondingTab.index); } else { diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index d29cd181fd..0be6b9147e 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -119,7 +119,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ title: t("note_map.title"), icon: "bx bxs-network-chart", content: NoteMapTab, - show: true, + show: !isNewLayout, toggleCommand: "toggleRibbonTabNoteMap" }, { diff --git a/apps/client/src/widgets/type_widgets/NoteMap.tsx b/apps/client/src/widgets/type_widgets/NoteMap.tsx index aca9f68d18..3f33fe586d 100644 --- a/apps/client/src/widgets/type_widgets/NoteMap.tsx +++ b/apps/client/src/widgets/type_widgets/NoteMap.tsx @@ -3,12 +3,15 @@ import NoteMapEl from "../note_map/NoteMap"; import { useRef } from "preact/hooks"; import "./NoteMap.css"; -export default function NoteMap({ note }: TypeWidgetProps) { +export default function NoteMap({ note, noteContext }: TypeWidgetProps) { const containerRef = useRef(null); return (
    - +
    ); } From 24cd5006d5064ac9702b0a13d4f1874272022e83 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 22:14:08 +0200 Subject: [PATCH 199/873] chore(note_map): open in reusable split --- .../src/components/root_command_executor.ts | 12 ++----- apps/client/src/services/utils.ts | 36 ++++++++++++------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/apps/client/src/components/root_command_executor.ts b/apps/client/src/components/root_command_executor.ts index dc614f5558..918809cd1e 100644 --- a/apps/client/src/components/root_command_executor.ts +++ b/apps/client/src/components/root_command_executor.ts @@ -6,7 +6,7 @@ import openService from "../services/open.js"; import protectedSessionService from "../services/protected_session.js"; import options from "../services/options.js"; import froca from "../services/froca.js"; -import utils from "../services/utils.js"; +import utils, { openInReusableSplit } from "../services/utils.js"; import toastService from "../services/toast.js"; import noteCreateService from "../services/note_create.js"; @@ -199,14 +199,8 @@ export default class RootCommandExecutor extends Component { if (!isNewLayout) return; const activeContext = appContext.tabManager.getActiveContext(); - if (!activeContext) return; - - const subContexts = activeContext.getSubContexts(); - appContext.triggerCommand("openNewNoteSplit", { - ntxId: subContexts[subContexts.length - 1].ntxId, - notePath: activeContext.notePath, - viewScope: { viewMode: "note-map" } - }); + if (!activeContext?.notePath) return; + openInReusableSplit(activeContext.notePath, "note-map"); } firstTabCommand() { diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index aa4f0cebcc..8c2a12c6ac 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -1,5 +1,5 @@ import { dayjs } from "@triliumnext/commons"; -import type { ViewScope } from "./link.js"; +import type { ViewMode, ViewScope } from "./link.js"; import FNote from "../entities/fnote"; import { snapdom } from "@zumer/snapdom"; @@ -439,7 +439,20 @@ async function openInAppHelp($button: JQuery) { * @param inAppHelpPage the ID of the help note (excluding the `_help_` prefix). * @returns a promise that resolves once the help has been opened. */ -export async function openInAppHelpFromUrl(inAppHelpPage: string) { +export function openInAppHelpFromUrl(inAppHelpPage: string) { + return openInReusableSplit(`_help_${inAppHelpPage}`, "contextual-help"); +} + +/** + * Similar to opening a new note in a split, but re-uses an existing split if there is already one open with the same view mode. + * + * @param targetNoteId the note ID to open in the split. + * @param targetViewMode the view mode of the split to open the note in. + * @param openOpts additional options for opening the note. + */ +export async function openInReusableSplit(targetNoteId: string, targetViewMode: ViewMode, openOpts: { + hoistedNoteId?: string; +} = {}) { // Dynamic import to avoid import issues in tests. const appContext = (await import("../components/app_context.js")).default; const activeContext = appContext.tabManager.getActiveContext(); @@ -447,23 +460,20 @@ export async function openInAppHelpFromUrl(inAppHelpPage: string) { return; } const subContexts = activeContext.getSubContexts(); - const targetNote = `_help_${inAppHelpPage}`; - const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help"); - const viewScope: ViewScope = { - viewMode: "contextual-help", - }; - if (!helpSubcontext) { - // The help is not already open, open a new split with it. + const existingSubcontext = subContexts.find((s) => s.viewScope?.viewMode === targetViewMode); + const viewScope: ViewScope = { viewMode: targetViewMode }; + if (!existingSubcontext) { + // The target split is not already open, open a new split with it. const { ntxId } = subContexts[subContexts.length - 1]; appContext.triggerCommand("openNewNoteSplit", { ntxId, - notePath: targetNote, - hoistedNoteId: "_help", + notePath: targetNoteId, + hoistedNoteId: openOpts.hoistedNoteId, viewScope }); } else { - // There is already a help window open, make sure it opens on the right note. - helpSubcontext.setNote(targetNote, { viewScope }); + // There is already a target split open, make sure it opens on the right note. + existingSubcontext.setNote(targetNoteId, { viewScope }); } } From 61fe27abbe748f0dd610901245be093c4984e8ca Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 22:29:22 +0200 Subject: [PATCH 200/873] feat(layout): extract floating titlebar into its own experimental feature --- apps/client/src/layouts/desktop_layout.tsx | 5 +- .../src/services/experimental_features.ts | 5 ++ .../src/translations/en/translation.json | 4 +- apps/client/src/widgets/note_title.css | 46 +++++++++++++------ 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index d504867500..9c0e02001b 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -79,6 +79,7 @@ export default class DesktopLayout { const fullWidthTabBar = launcherPaneIsHorizontal || (isElectron && !hasNativeTitleBar && isMac); const customTitleBarButtons = !hasNativeTitleBar && !isMac && !isWindows; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); + const isFloatingTitlebar = isExperimentalFeatureEnabled("floating-titlebar"); const titleRow = new FlexContainer("row") .class("title-row") @@ -149,7 +150,7 @@ export default class DesktopLayout { .child() .optChild(isNewLayout, ) ) - .optChild(!isNewLayout, titleRow) + .optChild(!isFloatingTitlebar, titleRow) .optChild(!isNewLayout, ) .optChild(isNewLayout, ) .child(new WatchedFileUpdateStatusWidget()) @@ -157,7 +158,7 @@ export default class DesktopLayout { .child( new ScrollingContainer() .filling() - .optChild(isNewLayout, titleRow) + .optChild(isFloatingTitlebar, titleRow) .optChild(isNewLayout, ) .optChild(!isNewLayout, new ContentHeader() .child() diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index 5f7e5d0109..980bd74d59 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -12,6 +12,11 @@ export const experimentalFeatures = [ id: "new-layout", name: t("experimental_features.new_layout_name"), description: t("experimental_features.new_layout_description"), + }, + { + id: "floating-titlebar", + name: t("experimental_features.floating_titlebar"), + description: t("experimental_features.floating_titlebar_description"), } ] as const satisfies ExperimentalFeature[]; diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 7f49dea52b..136513ef8f 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1102,7 +1102,9 @@ "title": "Experimental Options", "disclaimer": "These options are experimental and may cause instability. Use with caution.", "new_layout_name": "New Layout", - "new_layout_description": "Try out the new layout for a more modern look and improved usability. Subject to heavy change in the upcoming releases." + "new_layout_description": "Try out the new layout for a more modern look and improved usability. Subject to heavy change in the upcoming releases.", + "floating_titlebar": "Floating Titlebar", + "floating_titlebar_description": "The title bar is part of the content and is scrolled along with the note content." }, "fonts": { "theme_defined": "Theme defined", diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 6a6ae98084..2395c9d235 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -30,7 +30,6 @@ body.desktop .note-title-widget input.note-title { } body.experimental-feature-new-layout { - .title-row, .title-details { max-width: var(--max-content-width); padding: 0; @@ -52,14 +51,6 @@ body.experimental-feature-new-layout { } } - .title-row { - .note-icon-widget { - padding: 0; - width: 41px; - } - } - - .note-split.type-code:not(.mime-text-x-sqlite) .title-row, .note-split.type-code:not(.mime-text-x-sqlite) .title-details { background-color: var(--main-background-color); } @@ -79,21 +70,48 @@ body.experimental-feature-new-layout { .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)), .note-split.type-book { - .title-row, .title-details { width: 100%; max-width: unset; padding-inline-start: 15px; - } - - .title-details { padding-bottom: 0.2em; font-size: 0.8em; } } - &.prefers-centered-content .title-row, &.prefers-centered-content .title-details { margin-inline: auto; } } + +body.experimental-feature-floating-titlebar { + .title-row { + max-width: var(--max-content-width); + padding: 0; + padding-inline-start: 24px; + } + + .note-icon-widget { + padding: 0; + width: 41px; + } + + .note-split.type-code:not(.mime-text-x-sqlite) .title-row { + background-color: var(--main-background-color); + } + + .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)), + .note-split.type-book { + .title-row { + width: 100%; + max-width: unset; + padding-inline-start: 15px; + padding-bottom: 0.2em; + font-size: 0.8em; + } + } + + &.prefers-centered-content .title-row { + margin-inline: auto; + } +} From d99d701095ea062549b99a6a976e4445c1d37b8f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 11 Dec 2025 22:38:22 +0200 Subject: [PATCH 201/873] feat(global_menu): add support for all experimental options --- .../src/services/experimental_features.ts | 4 +- .../src/widgets/buttons/global_menu.tsx | 48 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index 980bd74d59..6e2f14e865 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -1,7 +1,7 @@ import { t } from "./i18n"; import options from "./options"; -interface ExperimentalFeature { +export interface ExperimentalFeature { id: string; name: string; description: string; @@ -20,7 +20,7 @@ export const experimentalFeatures = [ } ] as const satisfies ExperimentalFeature[]; -type ExperimentalFeatureId = typeof experimentalFeatures[number]["id"]; +export type ExperimentalFeatureId = typeof experimentalFeatures[number]["id"]; let enabledFeatures: Set | null = null; diff --git a/apps/client/src/widgets/buttons/global_menu.tsx b/apps/client/src/widgets/buttons/global_menu.tsx index 5d7f6d4660..66679dd20e 100644 --- a/apps/client/src/widgets/buttons/global_menu.tsx +++ b/apps/client/src/widgets/buttons/global_menu.tsx @@ -1,17 +1,19 @@ -import Dropdown from "../react/Dropdown"; import "./global_menu.css"; -import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool, useTriliumOptionInt } from "../react/hooks"; -import { useContext, useEffect, useRef, useState } from "preact/hooks"; -import { t } from "../../services/i18n"; -import { FormDropdownDivider, FormDropdownSubmenu, FormListHeader, FormListItem } from "../react/FormList"; -import { CommandNames } from "../../components/app_context"; -import KeyboardShortcut from "../react/KeyboardShortcut"; + import { KeyboardActionNames } from "@triliumnext/commons"; import { ComponentChildren } from "preact"; +import { useContext, useEffect, useRef, useState } from "preact/hooks"; + +import { CommandNames } from "../../components/app_context"; import Component from "../../components/component"; -import { ParentComponent } from "../react/react_utils"; +import { ExperimentalFeature, ExperimentalFeatureId, experimentalFeatures, isExperimentalFeatureEnabled, toggleExperimentalFeature } from "../../services/experimental_features"; +import { t } from "../../services/i18n"; import utils, { dynamicRequire, isElectron, isMobile, reloadFrontendApp } from "../../services/utils"; -import { isExperimentalFeatureEnabled, toggleExperimentalFeature } from "../../services/experimental_features"; +import Dropdown from "../react/Dropdown"; +import { FormDropdownDivider, FormDropdownSubmenu, FormListHeader, FormListItem } from "../react/FormList"; +import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool, useTriliumOptionInt } from "../react/hooks"; +import KeyboardShortcut from "../react/KeyboardShortcut"; +import { ParentComponent } from "../react/react_utils"; interface MenuItemProps { icon: string, @@ -73,7 +75,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: {!isElectron() && } {glob.isDev && }
    - ) + ); } function AdvancedMenu() { @@ -91,7 +93,7 @@ function AdvancedMenu() { {isElectron() && } - ) + ); } function BrowserOnlyOptions() { @@ -102,18 +104,30 @@ function BrowserOnlyOptions() { } function DevelopmentOptions() { - const newLayoutEnabled = isExperimentalFeatureEnabled("new-layout"); - return <> + Development Options + + {experimentalFeatures.map((feature) => ( + + ))} + + ; +} + +function ExperimentalFeatureToggle({ experimentalFeature }: { experimentalFeature: ExperimentalFeature }) { + const featureEnabled = isExperimentalFeatureEnabled(experimentalFeature.id as ExperimentalFeatureId); + + return ( { - await toggleExperimentalFeature("new-layout", !newLayoutEnabled); + await toggleExperimentalFeature(experimentalFeature.id as ExperimentalFeatureId, !featureEnabled); reloadFrontendApp(); }} - >{!newLayoutEnabled ? "Switch to new layout" : "Switch to old layout"} - ; + >{experimentalFeature.name} + ); } function SwitchToOptions() { From 88aad6d351db1a9dfbc2b2fd06a7efe11cb4d6d6 Mon Sep 17 00:00:00 2001 From: noobhjy Date: Thu, 11 Dec 2025 15:03:13 +0100 Subject: [PATCH 202/873] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (1668 of 1668 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/ --- .../src/translations/cn/translation.json | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index 63f39312cf..46de55f44a 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -693,7 +693,8 @@ "convert_into_attachment_successful": "笔记 '{{title}}' 已成功转换为附件。", "convert_into_attachment_prompt": "确定要将笔记 '{{title}}' 转换为父笔记的附件吗?", "print_pdf": "导出为 PDF...", - "open_note_on_server": "在服务器上打开笔记" + "open_note_on_server": "在服务器上打开笔记", + "view_revisions": "笔记修订..." }, "onclick_button": { "no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序" @@ -1578,8 +1579,8 @@ }, "note_title": { "placeholder": "请输入笔记标题...", - "created_on": "建立于 {{date}}", - "last_modified": "最后修改于 {{date}}" + "created_on": "建立于 ", + "last_modified": "最后修改于 " }, "search_result": { "no_notes_found": "没有找到符合搜索条件的笔记。", @@ -1941,8 +1942,9 @@ "unknown_widget": "未知组件:\"{{id}}\"." }, "note_language": { - "not_set": "不设置", - "configure-languages": "设置语言..." + "not_set": "未设置语言", + "configure-languages": "设置语言...", + "help-on-languages": "内容语言帮助..." }, "content_language": { "title": "内容语言", @@ -2133,6 +2135,14 @@ "read_only_explicit": "只读", "read_only_auto": "自动只读", "shared_publicly": "公开共享", - "shared_locally": "本地共享" + "shared_locally": "本地共享", + "read_only_explicit_description": "此笔记已被手动设置为只读。\n点击可临时编辑。", + "read_only_auto_description": "出于性能原因,此笔记已被自动设置为只读模式。此自动限制可以在设置中调整。\n\n点击可临时编辑。", + "read_only_temporarily_disabled": "临时编辑", + "read_only_temporarily_disabled_description": "此笔记当前可编辑,但通常是只读的。一旦你切换到其他笔记,该笔记将恢复为只读模式。\n\n点击以重新启用只读模式。", + "shared_publicly_description": "此笔记已在网上发布,链接为 {{- link}},并且可公开访问。\n\n点击以导航到共享笔记,或右键点击查看更多选项。", + "shared_locally_description": "此笔记仅在本地网络共享,链接为 {{- link}}。\n\n点击以导航到共享笔记,或右键点击查看更多选项。", + "backlinks_other": "{{count}} 个反向链接", + "backlinks_description_other": "此笔记与 {{count}} 个其他笔记相关联。\n\n点击查看反向链接列表。" } } From 78b9c94829acbcd82ba7a5476fafde2b7ea040e0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 00:13:25 +0200 Subject: [PATCH 203/873] chore(layout/status_bar): create empty component --- apps/client/src/layouts/desktop_layout.tsx | 4 +++- apps/client/src/widgets/layout/StatusBar.css | 3 +++ apps/client/src/widgets/layout/StatusBar.tsx | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/layout/StatusBar.css create mode 100644 apps/client/src/widgets/layout/StatusBar.tsx diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 9c0e02001b..8815e113de 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -53,6 +53,7 @@ import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibb import BreadcrumbBadges from "../widgets/BreadcrumbBadges.jsx"; import NoteTitleDetails from "../widgets/NoteTitleDetails.jsx"; import NoteStatusBar from "../widgets/NoteStatusBar.jsx"; +import StatusBar from "../widgets/layout/StatusBar.jsx"; export default class DesktopLayout { @@ -134,6 +135,7 @@ export default class DesktopLayout { .filling() .collapsible() .id("center-pane") + .optChild(isNewLayout, ) .child( new SplitNoteContainer(() => new NoteWrapperWidget() @@ -152,7 +154,6 @@ export default class DesktopLayout { ) .optChild(!isFloatingTitlebar, titleRow) .optChild(!isNewLayout, ) - .optChild(isNewLayout, ) .child(new WatchedFileUpdateStatusWidget()) .child() .child( @@ -183,6 +184,7 @@ export default class DesktopLayout { )) + .optChild(isNewLayout, ) ) ) .child(...this.customWidgets.get("center-pane")) diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css new file mode 100644 index 0000000000..d14e1f2a69 --- /dev/null +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -0,0 +1,3 @@ +.component.status-bar { + contain: none; +} diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx new file mode 100644 index 0000000000..d537c58024 --- /dev/null +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -0,0 +1,9 @@ +import "./StatusBar.css"; + +export default function StatusBar() { + return ( +
    + Status bar goes here. +
    + ); +} From 9ee3c4848556613ee275b17d74c21024fa6a960d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 00:15:58 +0200 Subject: [PATCH 204/873] chore(layout): relocate ribbon on the top temporarily --- apps/client/src/layouts/desktop_layout.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 8815e113de..d2e88740b5 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -154,6 +154,11 @@ export default class DesktopLayout { ) .optChild(!isFloatingTitlebar, titleRow) .optChild(!isNewLayout, ) + .optChild(isNewLayout, ( + + + + )) .child(new WatchedFileUpdateStatusWidget()) .child() .child( @@ -179,11 +184,6 @@ export default class DesktopLayout { ...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC ...this.customWidgets.get("note-detail-pane") ) - .optChild(isNewLayout, ( - - - - )) .optChild(isNewLayout, ) ) ) From 2f44b9dc59cb0d2e4ab87ee4fa5d38be8f2b0532 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 00:21:40 +0200 Subject: [PATCH 205/873] feat(layout/status_bar): integrate breadcrumbs --- apps/client/src/layouts/desktop_layout.tsx | 1 - apps/client/src/widgets/Breadcrumb.css | 128 +++++++++---------- apps/client/src/widgets/layout/StatusBar.css | 2 + apps/client/src/widgets/layout/StatusBar.tsx | 5 +- 4 files changed, 69 insertions(+), 67 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index d2e88740b5..b25e7c70ce 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -143,7 +143,6 @@ export default class DesktopLayout { new FlexContainer("row") .class("breadcrumb-row") .cssBlock(".breadcrumb-row > * { margin: 5px; }") - .child() .optChild(isNewLayout, ) .child() .child() diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 2bb58281fc..ef28d42ee2 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -1,9 +1,7 @@ .breadcrumb-row { position: relative; - height: 30px; min-height: 30px; align-items: center; - padding: 10px; container-type: inline-size; @container (max-width: 700px) { @@ -47,72 +45,72 @@ margin: 0; } } + + .breadcrumb { + display: flex; + margin: 0; + align-items: center; + font-size: 0.9em; + gap: 0.25em; + flex-wrap: nowrap; + overflow: hidden; + max-width: 85%; + + > span, + > span > span { + display: flex; + align-items: center; + min-width: 0; + + a { + color: inherit; + text-decoration: none; + min-width: 0; + max-width: 150px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; + flex-shrink: 2; + } + } + + > span:last-of-type a { + max-width: 300px; + flex-shrink: 1; + } + + ul { + flex-direction: column; + list-style-type: none; + margin: 0; + padding: 0; + } + + .dropdown-item span, + .dropdown-item strong, + .breadcrumb-last-item { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; + max-width: 300px; + } + + .breadcrumb-last-item { + text-decoration: none; + color: unset; + cursor: text; + } + + input { + padding: 0 10px; + width: 200px; + } + } } body.experimental-feature-new-layout .breadcrumb-row { padding-inline-end: 0; } -.component.breadcrumb { - contain: none; - display: flex; - margin: 0; - align-items: center; - font-size: 0.9em; - gap: 0.25em; - flex-wrap: nowrap; - overflow: hidden; - max-width: 85%; - - > span, - > span > span { - display: flex; - align-items: center; - min-width: 0; - - a { - color: inherit; - text-decoration: none; - min-width: 0; - max-width: 150px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - display: block; - flex-shrink: 2; - } - } - - > span:last-of-type a { - max-width: 300px; - flex-shrink: 1; - } - - ul { - flex-direction: column; - list-style-type: none; - margin: 0; - padding: 0; - } - - .dropdown-item span, - .dropdown-item strong, - .breadcrumb-last-item { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - display: block; - max-width: 300px; - } - - .breadcrumb-last-item { - text-decoration: none; - color: unset; - cursor: text; - } - - input { - padding: 0 10px; - width: 200px; - } -} diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index d14e1f2a69..8ece8cc276 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -1,3 +1,5 @@ .component.status-bar { contain: none; + border-top: 1px solid var(--main-border-color); + background-color: var(--left-pane-background-color) } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index d537c58024..e7179defa3 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -1,9 +1,12 @@ +import Breadcrumb from "../Breadcrumb"; import "./StatusBar.css"; export default function StatusBar() { return (
    - Status bar goes here. +
    + +
    ); } From 4e1188484dfaf272595b1e780fbac669b0252fc4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 00:24:15 +0200 Subject: [PATCH 206/873] refactor(layout/status_bar): move breadcrumbs into layout dir --- apps/client/src/layouts/desktop_layout.tsx | 2 +- .../src/widgets/{ => layout}/Breadcrumb.css | 0 .../src/widgets/{ => layout}/Breadcrumb.tsx | 24 +++++++++---------- apps/client/src/widgets/layout/StatusBar.tsx | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) rename apps/client/src/widgets/{ => layout}/Breadcrumb.css (100%) rename apps/client/src/widgets/{ => layout}/Breadcrumb.tsx (93%) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index b25e7c70ce..b18db50040 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -44,7 +44,7 @@ import NoteDetail from "../widgets/NoteDetail.jsx"; import PromotedAttributes from "../widgets/PromotedAttributes.jsx"; import SpacerWidget from "../widgets/launch_bar/SpacerWidget.jsx"; import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx"; -import Breadcrumb from "../widgets/Breadcrumb.jsx"; +import Breadcrumb from "../widgets/layout/Breadcrumb.jsx"; import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx"; import { isExperimentalFeatureEnabled } from "../services/experimental_features.js"; import NoteActions from "../widgets/ribbon/NoteActions.jsx"; diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/layout/Breadcrumb.css similarity index 100% rename from apps/client/src/widgets/Breadcrumb.css rename to apps/client/src/widgets/layout/Breadcrumb.css diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/layout/Breadcrumb.tsx similarity index 93% rename from apps/client/src/widgets/Breadcrumb.tsx rename to apps/client/src/widgets/layout/Breadcrumb.tsx index c218f7cea2..8ef9be8fd7 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/layout/Breadcrumb.tsx @@ -3,18 +3,18 @@ import "./Breadcrumb.css"; import { useMemo, useState } from "preact/hooks"; import { Fragment } from "preact/jsx-runtime"; -import NoteContext from "../components/note_context"; -import froca from "../services/froca"; -import ActionButton from "./react/ActionButton"; -import Dropdown from "./react/Dropdown"; -import { FormListItem } from "./react/FormList"; -import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty } from "./react/hooks"; -import Icon from "./react/Icon"; -import NoteLink from "./react/NoteLink"; -import link_context_menu from "../menus/link_context_menu"; -import { TitleEditor } from "./collections/board"; -import server from "../services/server"; -import { NoteInfoBadge } from "./BreadcrumbBadges"; +import NoteContext from "../../components/note_context"; +import froca from "../../services/froca"; +import ActionButton from "../react/ActionButton"; +import Dropdown from "../react/Dropdown"; +import { FormListItem } from "../react/FormList"; +import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty } from "../react/hooks"; +import Icon from "../react/Icon"; +import NoteLink from "../react/NoteLink"; +import link_context_menu from "../../menus/link_context_menu"; +import { TitleEditor } from "../collections/board"; +import server from "../../services/server"; +import { NoteInfoBadge } from "../BreadcrumbBadges"; const COLLAPSE_THRESHOLD = 5; const INITIAL_ITEMS = 2; diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index e7179defa3..987ad99ffb 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -1,4 +1,4 @@ -import Breadcrumb from "../Breadcrumb"; +import Breadcrumb from "./Breadcrumb"; import "./StatusBar.css"; export default function StatusBar() { From df9554194a43820b286b7c76f3c192060c26fa33 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 00:34:47 +0200 Subject: [PATCH 207/873] feat(layout/status_bar): integrate language selector basically --- apps/client/src/layouts/desktop_layout.tsx | 8 +--- apps/client/src/widgets/NoteStatusBar.css | 13 ------ apps/client/src/widgets/NoteStatusBar.tsx | 25 ------------ apps/client/src/widgets/layout/StatusBar.css | 17 +++++++- apps/client/src/widgets/layout/StatusBar.tsx | 42 ++++++++++++++++++-- 5 files changed, 55 insertions(+), 50 deletions(-) delete mode 100644 apps/client/src/widgets/NoteStatusBar.css delete mode 100644 apps/client/src/widgets/NoteStatusBar.tsx diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index b18db50040..2ad525dca5 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -44,7 +44,6 @@ import NoteDetail from "../widgets/NoteDetail.jsx"; import PromotedAttributes from "../widgets/PromotedAttributes.jsx"; import SpacerWidget from "../widgets/launch_bar/SpacerWidget.jsx"; import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx"; -import Breadcrumb from "../widgets/layout/Breadcrumb.jsx"; import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx"; import { isExperimentalFeatureEnabled } from "../services/experimental_features.js"; import NoteActions from "../widgets/ribbon/NoteActions.jsx"; @@ -52,7 +51,6 @@ import FormattingToolbar from "../widgets/ribbon/FormattingToolbar.jsx"; import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx"; import BreadcrumbBadges from "../widgets/BreadcrumbBadges.jsx"; import NoteTitleDetails from "../widgets/NoteTitleDetails.jsx"; -import NoteStatusBar from "../widgets/NoteStatusBar.jsx"; import StatusBar from "../widgets/layout/StatusBar.jsx"; export default class DesktopLayout { @@ -153,11 +151,7 @@ export default class DesktopLayout { ) .optChild(!isFloatingTitlebar, titleRow) .optChild(!isNewLayout, ) - .optChild(isNewLayout, ( - - - - )) + .optChild(isNewLayout, ) .child(new WatchedFileUpdateStatusWidget()) .child() .child( diff --git a/apps/client/src/widgets/NoteStatusBar.css b/apps/client/src/widgets/NoteStatusBar.css deleted file mode 100644 index c43f13daf4..0000000000 --- a/apps/client/src/widgets/NoteStatusBar.css +++ /dev/null @@ -1,13 +0,0 @@ -.note-status-bar { - display: flex; - align-items: center; - padding-inline: 1em; - - .dropdown { - font-size: 0.85em; - - .dropdown-toggle { - padding: 0.1em 0.25em; - } - } -} diff --git a/apps/client/src/widgets/NoteStatusBar.tsx b/apps/client/src/widgets/NoteStatusBar.tsx deleted file mode 100644 index 3b686ff846..0000000000 --- a/apps/client/src/widgets/NoteStatusBar.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import "./NoteStatusBar.css"; - -import { t } from "../services/i18n"; -import { openInAppHelpFromUrl } from "../services/utils"; -import { FormListItem } from "./react/FormList"; -import { useNoteContext } from "./react/hooks"; -import { NoteLanguageSelector } from "./ribbon/BasicPropertiesTab"; - -export default function NoteStatusBar() { - const { note } = useNoteContext(); - - return ( -
    - openInAppHelpFromUrl("veGu4faJErEM")} - icon="bx bx-help-circle" - >{t("note_language.help-on-languages")} - )} - /> -
    - ); -} diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index 8ece8cc276..ca916e1d14 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -1,5 +1,20 @@ .component.status-bar { contain: none; border-top: 1px solid var(--main-border-color); - background-color: var(--left-pane-background-color) + background-color: var(--left-pane-background-color); + display: flex; + align-items: center; + padding-inline: 0.25em; + + > .breadcrumb-row { + flex-grow: 1; + } + + .dropdown { + font-size: 0.85em; + + .dropdown-toggle { + padding: 0.1em 0.25em; + } + } } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 987ad99ffb..e017728de6 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -1,12 +1,46 @@ -import Breadcrumb from "./Breadcrumb"; import "./StatusBar.css"; +import FNote from "../../entities/fnote"; +import { t } from "../../services/i18n"; +import { openInAppHelpFromUrl } from "../../services/utils"; +import { FormListItem } from "../react/FormList"; +import { useNoteContext } from "../react/hooks"; +import { NoteLanguageSelector } from "../ribbon/BasicPropertiesTab"; +import Breadcrumb from "./Breadcrumb"; + +interface StatusBarContext { + note: FNote; +} + export default function StatusBar() { + const { note } = useNoteContext(); + const context = note && { note } satisfies StatusBarContext; + return (
    -
    - -
    + {context && <> +
    + +
    + +
    + +
    + }
    ); } + +function LanguageSwitcher({ note }: StatusBarContext) { + return ( + openInAppHelpFromUrl("veGu4faJErEM")} + icon="bx bx-help-circle" + >{t("note_language.help-on-languages")} + )} + /> + ); +} From 34025fa64650e4999a334cfb787fd246b6c7e136 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 14:41:10 +0200 Subject: [PATCH 208/873] fix(global_menu): dev menu wrongly positioned on horizontal layout --- apps/client/src/widgets/buttons/global_menu.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/buttons/global_menu.tsx b/apps/client/src/widgets/buttons/global_menu.tsx index 66679dd20e..fcc1cda679 100644 --- a/apps/client/src/widgets/buttons/global_menu.tsx +++ b/apps/client/src/widgets/buttons/global_menu.tsx @@ -104,10 +104,12 @@ function BrowserOnlyOptions() { } function DevelopmentOptions() { + const [ layoutOrientation ] = useTriliumOption("layoutOrientation"); + return <> Development Options - + {experimentalFeatures.map((feature) => ( ))} From 74b6e7bf6321b66ff9291d3a6473be51468ee105 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 17:55:22 +0200 Subject: [PATCH 209/873] fix(breadcrumb): some dropdowns not visible --- apps/client/src/widgets/layout/Breadcrumb.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/layout/Breadcrumb.tsx b/apps/client/src/widgets/layout/Breadcrumb.tsx index 8ef9be8fd7..ed692adeb7 100644 --- a/apps/client/src/widgets/layout/Breadcrumb.tsx +++ b/apps/client/src/widgets/layout/Breadcrumb.tsx @@ -136,7 +136,7 @@ function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePa noSelectButtonStyle buttonClassName="icon-action" hideToggleArrow - dropdownOptions={{ popperConfig: { strategy: "fixed" } }} + dropdownOptions={{ popperConfig: { strategy: "fixed", placement: "top" } }} > From 31c5323fd90db3b2145645baa738670cb46aebcf Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 18:05:10 +0200 Subject: [PATCH 210/873] feat(status_bar/language): compact locale name --- apps/client/src/widgets/layout/StatusBar.tsx | 1 + .../src/widgets/ribbon/BasicPropertiesTab.tsx | 7 ++++++- .../options/components/LocaleSelector.tsx | 16 ++++++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index e017728de6..3cea9e9889 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -41,6 +41,7 @@ function LanguageSwitcher({ note }: StatusBarContext) { icon="bx bx-help-circle" >{t("note_language.help-on-languages")} )} + compact /> ); } diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 479994ca39..dda9a78305 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -330,7 +330,11 @@ function NoteLanguageSwitch({ note }: { note?: FNote | null }) { ); } -export function NoteLanguageSelector({ note, extraChildren }: { note: FNote | null | undefined, extraChildren?: ComponentChildren }) { +export function NoteLanguageSelector({ note, extraChildren, ...restProps }: { + note: FNote | null | undefined, + extraChildren?: ComponentChildren, + compact?: boolean; +}) { const [ modalShown, setModalShown ] = useState(false); const [ languages ] = useTriliumOption("languages"); const DEFAULT_LOCALE = { @@ -357,6 +361,7 @@ export function NoteLanguageSelector({ note, extraChildren }: { note: FNote | nu icon="bx bx-cog" >{t("note_language.configure-languages")} } + {...restProps} /> {createPortal( , diff --git a/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx b/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx index b9b1d8758d..c19fdbfac6 100644 --- a/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx +++ b/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx @@ -5,13 +5,14 @@ import { useMemo } from "preact/hooks"; import Dropdown from "../../../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../../../react/FormList"; -export function LocaleSelector({ id, locales, currentValue, onChange, defaultLocale, extraChildren }: { +export function LocaleSelector({ id, locales, currentValue, onChange, defaultLocale, extraChildren, compact }: { id?: string; locales: Locale[], currentValue: string, onChange: (newLocale: string) => void, defaultLocale?: Locale, - extraChildren?: ComponentChildren + extraChildren?: ComponentChildren, + compact?: boolean; }) { const activeLocale = defaultLocale?.id === currentValue ? defaultLocale : locales.find(l => l.id === currentValue); @@ -42,7 +43,7 @@ export function LocaleSelector({ id, locales, currentValue, onChange, defaultLoc }, [ locales ]); return ( - + {processedLocales.map(locale => { if (typeof locale === "object") { return - ) + ); +} + +function getLocaleName(locale: Locale | null | undefined, compact: boolean | undefined) { + if (!locale) return ""; + if (!compact) return locale.name; + if (!locale.id) return "-"; + return locale.id.toLocaleUpperCase(); } From 4dc773c1a3d9ae093fb84067bcf80a59ad7da5d9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 18:29:40 +0200 Subject: [PATCH 211/873] refactor(status_bar/language): stop reusing UI for greater customisibility --- apps/client/src/widgets/layout/StatusBar.tsx | 47 ++++++++++++-- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 55 ++++++++-------- .../options/components/LocaleSelector.tsx | 64 +++++++++---------- 3 files changed, 99 insertions(+), 67 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 3cea9e9889..deb01da1cd 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -3,10 +3,15 @@ import "./StatusBar.css"; import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; import { openInAppHelpFromUrl } from "../../services/utils"; -import { FormListItem } from "../react/FormList"; +import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; -import { NoteLanguageSelector } from "../ribbon/BasicPropertiesTab"; +import { ContentLanguagesModal, NoteLanguageSelector, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; import Breadcrumb from "./Breadcrumb"; +import { useState } from "preact/hooks"; +import { createPortal } from "preact/compat"; +import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; +import Dropdown from "../react/Dropdown"; +import { Locale } from "@triliumnext/commons"; interface StatusBarContext { note: FNote; @@ -32,16 +37,44 @@ export default function StatusBar() { } function LanguageSwitcher({ note }: StatusBarContext) { + const [ modalShown, setModalShown ] = useState(false); + const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note); + const { activeLocale, processedLocales } = useProcessedLocales(locales, DEFAULT_LOCALE, currentNoteLanguage ?? DEFAULT_LOCALE.id); + return ( - + + {processedLocales.map(locale => { + if (typeof locale === "object") { + return setCurrentNoteLanguage(locale.id)} + >{locale.name} + } else { + return + } + })} + openInAppHelpFromUrl("veGu4faJErEM")} icon="bx bx-help-circle" >{t("note_language.help-on-languages")} + setModalShown(true)} + icon="bx bx-cog" + >{t("note_language.configure-languages")} + + {createPortal( + , + document.body )} - compact - /> + ); } + +export function getLocaleName(locale: Locale | null | undefined) { + if (!locale) return ""; + if (!locale.id) return "-"; + return locale.id.toLocaleUpperCase(); +} diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index dda9a78305..761577949e 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -330,12 +330,32 @@ function NoteLanguageSwitch({ note }: { note?: FNote | null }) { ); } -export function NoteLanguageSelector({ note, extraChildren, ...restProps }: { - note: FNote | null | undefined, - extraChildren?: ComponentChildren, - compact?: boolean; -}) { +export function NoteLanguageSelector({ note }: { note: FNote | null | undefined }) { const [ modalShown, setModalShown ] = useState(false); + const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note); + + return ( + <> + + setModalShown(true)} + icon="bx bx-cog" + >{t("note_language.configure-languages")} + } + /> + {createPortal( + , + document.body + )} + + ); +} + +export function useLanguageSwitcher(note: FNote | null | undefined) { const [ languages ] = useTriliumOption("languages"); const DEFAULT_LOCALE = { id: "", @@ -347,31 +367,10 @@ export function NoteLanguageSelector({ note, extraChildren, ...restProps }: { const filteredLanguages = getAvailableLocales().filter((l) => typeof l !== "object" || enabledLanguages.includes(l.id)); return filteredLanguages; }, [ languages ]); - - return ( - <> - - {extraChildren} - setModalShown(true)} - icon="bx bx-cog" - >{t("note_language.configure-languages")} - } - {...restProps} - /> - {createPortal( - , - document.body - )} - - ); + return { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage }; } -function ContentLanguagesModal({ modalShown, setModalShown }: { modalShown: boolean, setModalShown: (shown: boolean) => void }) { +export function ContentLanguagesModal({ modalShown, setModalShown }: { modalShown: boolean, setModalShown: (shown: boolean) => void }) { return ( void, defaultLocale?: Locale, extraChildren?: ComponentChildren, - compact?: boolean; }) { + const currentValueWithDefault = currentValue ?? defaultLocale?.id ?? ""; + const { activeLocale, processedLocales } = useProcessedLocales(locales, defaultLocale, currentValueWithDefault); + return ( + + {processedLocales.map(locale => { + if (typeof locale === "object") { + return { + onChange(locale.id); + }} + >{locale.name} + } else { + return + } + })} + {extraChildren && ( + <> + + {extraChildren} + + )} + + ); +} + +export function useProcessedLocales(locales: Locale[], defaultLocale: Locale | undefined, currentValue: string) { const activeLocale = defaultLocale?.id === currentValue ? defaultLocale : locales.find(l => l.id === currentValue); const processedLocales = useMemo(() => { @@ -36,35 +63,8 @@ export function LocaleSelector({ id, locales, currentValue, onChange, defaultLoc ]; } - if (extraChildren) { - items.push("---"); - } return items; - }, [ locales ]); + }, [ locales, defaultLocale ]); - return ( - - {processedLocales.map(locale => { - if (typeof locale === "object") { - return { - onChange(locale.id); - }} - >{locale.name} - } else { - return - } - })} - {extraChildren} - - ); -} - -function getLocaleName(locale: Locale | null | undefined, compact: boolean | undefined) { - if (!locale) return ""; - if (!compact) return locale.name; - if (!locale.id) return "-"; - return locale.id.toLocaleUpperCase(); + return { activeLocale, processedLocales }; } From 12be14e6cff8a834da6263d713227859f1f10899 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 18:46:34 +0200 Subject: [PATCH 212/873] feat(status_bar/language): display icon --- apps/client/src/widgets/layout/StatusBar.css | 20 ++++++++++++++++ apps/client/src/widgets/layout/StatusBar.tsx | 25 +++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index ca916e1d14..42f59ed756 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -10,6 +10,26 @@ flex-grow: 1; } + > .actions-row { + padding: 0.1em; + + .status-bar-dropdown-button { + background: transparent; + padding: 0 0.5em !important; + display: flex; + align-items: center; + + &:after { + content: unset; + } + + &:focus, + &:hover { + background: var(--input-background-color); + } + } + } + .dropdown { font-size: 0.85em; diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index deb01da1cd..0013047988 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -10,8 +10,10 @@ import Breadcrumb from "./Breadcrumb"; import { useState } from "preact/hooks"; import { createPortal } from "preact/compat"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; -import Dropdown from "../react/Dropdown"; +import Dropdown, { DropdownProps } from "../react/Dropdown"; import { Locale } from "@triliumnext/commons"; +import clsx from "clsx"; +import Icon from "../react/Icon"; interface StatusBarContext { note: FNote; @@ -36,6 +38,23 @@ export default function StatusBar() { ); } +function StatusBarDropdown({ children, icon, text, buttonClassName, ...dropdownProps }: Omit & { + icon?: string; +}) { + return ( + + {icon && (<> )} + {text} + } + {...dropdownProps} + > + {children} + + ); +} + function LanguageSwitcher({ note }: StatusBarContext) { const [ modalShown, setModalShown ] = useState(false); const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note); @@ -43,7 +62,7 @@ function LanguageSwitcher({ note }: StatusBarContext) { return ( <> - + {processedLocales.map(locale => { if (typeof locale === "object") { return setModalShown(true)} icon="bx bx-cog" >{t("note_language.configure-languages")} - + {createPortal( , document.body From c099634e39803f333283bdce3b2bb8a6c6a13777 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 18:50:48 +0200 Subject: [PATCH 213/873] feat(status_bar/language): improve display of Asian languages --- apps/client/src/widgets/layout/StatusBar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 0013047988..0149d2dd0d 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -95,5 +95,6 @@ function LanguageSwitcher({ note }: StatusBarContext) { export function getLocaleName(locale: Locale | null | undefined) { if (!locale) return ""; if (!locale.id) return "-"; + if (locale.name.length <= 4) return locale.name; // Some locales like Japanese and Chinese look better than their ID. return locale.id.toLocaleUpperCase(); } From 24ed97f65d82cdd636a6a4f3018d589c12ff3f96 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 18:53:54 +0200 Subject: [PATCH 214/873] feat(status_bar/language): improve display of more languages --- apps/client/src/widgets/layout/StatusBar.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 0149d2dd0d..0dc13df192 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -62,7 +62,7 @@ function LanguageSwitcher({ note }: StatusBarContext) { return ( <> - + {getLocaleName(activeLocale)}}> {processedLocales.map(locale => { if (typeof locale === "object") { return Date: Fri, 12 Dec 2025 18:58:54 +0200 Subject: [PATCH 215/873] feat(status_bar/language): add tooltip --- .../client/src/translations/en/translation.json | 3 +++ apps/client/src/widgets/layout/StatusBar.tsx | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 136513ef8f..e8ff99aab5 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2156,5 +2156,8 @@ "execute_script_description": "This note is a script note. Click to execute the script.", "execute_sql": "Run SQL", "execute_sql_description": "This note is a SQL note. Click to execute the SQL query." + }, + "status_bar": { + "language_title": "Change the language of the entire content" } } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 0dc13df192..b941606706 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -38,12 +38,21 @@ export default function StatusBar() { ); } -function StatusBarDropdown({ children, icon, text, buttonClassName, ...dropdownProps }: Omit & { +function StatusBarDropdown({ children, icon, text, buttonClassName, titleOptions, ...dropdownProps }: Omit & { + title: string; icon?: string; }) { return ( {icon && (<> )} {text} @@ -62,7 +71,11 @@ function LanguageSwitcher({ note }: StatusBarContext) { return ( <> - {getLocaleName(activeLocale)}}> + {getLocaleName(activeLocale)}} + > {processedLocales.map(locale => { if (typeof locale === "object") { return Date: Fri, 12 Dec 2025 19:31:00 +0200 Subject: [PATCH 216/873] feat(status_bar): integrate note info badge --- .../src/translations/en/translation.json | 3 +- apps/client/src/widgets/BreadcrumbBadges.css | 24 -------- apps/client/src/widgets/BreadcrumbBadges.tsx | 32 ----------- apps/client/src/widgets/layout/Breadcrumb.tsx | 2 - apps/client/src/widgets/layout/StatusBar.css | 28 ++++++++++ apps/client/src/widgets/layout/StatusBar.tsx | 55 ++++++++++++++++--- 6 files changed, 77 insertions(+), 67 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index e8ff99aab5..d50f1eeda3 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2158,6 +2158,7 @@ "execute_sql_description": "This note is a SQL note. Click to execute the SQL query." }, "status_bar": { - "language_title": "Change the language of the entire content" + "language_title": "Change the language of the entire content", + "note_info_title": "View information about this note such as the creation/modification date or the note size." } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index 758678b01f..582b9c7ba0 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -61,30 +61,6 @@ min-width: 500px; } - &.dropdown-note-info-badge { - .dropdown-menu.show ul { - list-style-type: none; - padding: 0.5em; - margin: 0; - display: table; - - li { - display: table-row; - - > strong { - display: table-cell; - padding: 0.2em 0; - } - - > span { - display: table-cell; - user-select: text; - padding-left: 2em; - } - } - } - } - .breadcrumb-badge { border-radius: 0; } diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index a7ccbae9f6..0ab1d7b850 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -5,14 +5,11 @@ import { ComponentChildren, MouseEventHandler } from "preact"; import { useRef } from "preact/hooks"; import { t } from "../services/i18n"; -import { formatDateTime } from "../utils/formatters"; import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "./react/Dropdown"; import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; -import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab"; import { useShareInfo } from "./shared_info"; -import FNote from "../entities/fnote"; export default function BreadcrumbBadges() { return ( @@ -26,35 +23,6 @@ export default function BreadcrumbBadges() { ); } -export function NoteInfoBadge({ note }: { note: FNote | null | undefined }) { - const { metadata, ...sizeProps } = useNoteMetadata(note); - - return (note && - -
      - - - {note.type} {note.mime && ({note.mime})}} /> - {note.noteId}} /> - } /> -
    -
    - ); -} - -function NoteInfoValue({ text, title, value }: { text: string; title?: string, value: ComponentChildren }) { - return ( -
  • - {text}{": "} - {value} -
  • - ); -} - function ReadOnlyBadge() { const { note, noteContext } = useNoteContext(); const { isReadOnly, enableEditing } = useIsNoteReadOnly(note, noteContext); diff --git a/apps/client/src/widgets/layout/Breadcrumb.tsx b/apps/client/src/widgets/layout/Breadcrumb.tsx index ed692adeb7..cc02657e09 100644 --- a/apps/client/src/widgets/layout/Breadcrumb.tsx +++ b/apps/client/src/widgets/layout/Breadcrumb.tsx @@ -14,7 +14,6 @@ import NoteLink from "../react/NoteLink"; import link_context_menu from "../../menus/link_context_menu"; import { TitleEditor } from "../collections/board"; import server from "../../services/server"; -import { NoteInfoBadge } from "../BreadcrumbBadges"; const COLLAPSE_THRESHOLD = 5; const INITIAL_ITEMS = 2; @@ -122,7 +121,6 @@ function BreadcrumbItem({ index, notePath, noteContext, notePathLength }: { inde if (index === notePathLength - 1) { return <> - ; } diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index 42f59ed756..5b7d642b73 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -12,6 +12,8 @@ > .actions-row { padding: 0.1em; + display: flex; + gap: 0.1em; .status-bar-dropdown-button { background: transparent; @@ -37,4 +39,30 @@ padding: 0.1em 0.25em; } } + + .dropdown-note-info { + width: max-content; + + ul { + list-style-type: none; + padding: 0.5em; + margin: 0; + display: table; + + li { + display: table-row; + + > strong { + display: table-cell; + padding: 0.2em 0; + } + + > span { + display: table-cell; + user-select: text; + padding-left: 2em; + } + } + } + } } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index b941606706..8dc2618648 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -1,19 +1,23 @@ import "./StatusBar.css"; +import { Locale } from "@triliumnext/commons"; +import clsx from "clsx"; +import { createPortal } from "preact/compat"; +import { useState } from "preact/hooks"; + import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; import { openInAppHelpFromUrl } from "../../services/utils"; +import { formatDateTime } from "../../utils/formatters"; +import Dropdown, { DropdownProps } from "../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; -import { ContentLanguagesModal, NoteLanguageSelector, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; -import Breadcrumb from "./Breadcrumb"; -import { useState } from "preact/hooks"; -import { createPortal } from "preact/compat"; -import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; -import Dropdown, { DropdownProps } from "../react/Dropdown"; -import { Locale } from "@triliumnext/commons"; -import clsx from "clsx"; import Icon from "../react/Icon"; +import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; +import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; +import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; +import Breadcrumb from "./Breadcrumb"; + interface StatusBarContext { note: FNote; @@ -31,6 +35,7 @@ export default function StatusBar() {
    +
    } @@ -64,6 +69,7 @@ function StatusBarDropdown({ children, icon, text, buttonClassName, titleOptions ); } +//#region Language Switcher function LanguageSwitcher({ note }: StatusBarContext) { const [ modalShown, setModalShown ] = useState(false); const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note); @@ -113,3 +119,36 @@ export function getLocaleName(locale: Locale | null | undefined) { .replace("_", "-") .toLocaleUpperCase(); } +//#endregion + +//#region Note info +export function NoteInfoBadge({ note }: { note: FNote | null | undefined }) { + const { metadata, ...sizeProps } = useNoteMetadata(note); + + return (note && + +
      + + + {note.type} {note.mime && ({note.mime})}} /> + {note.noteId}} /> + } /> +
    +
    + ); +} + +function NoteInfoValue({ text, title, value }: { text: string; title?: string, value: ComponentChildren }) { + return ( +
  • + {text}{": "} + {value} +
  • + ); +} +//#endregion From d2b32ff5af99d482fd693b2df3996176ae90deca Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 19:47:47 +0200 Subject: [PATCH 217/873] feat(status_bar): relocate to outside split area --- apps/client/src/layouts/desktop_layout.tsx | 4 +++- apps/client/src/widgets/layout/StatusBar.css | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 2ad525dca5..ac4590fecb 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -177,10 +177,10 @@ export default class DesktopLayout { ...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC ...this.customWidgets.get("note-detail-pane") ) - .optChild(isNewLayout, ) ) ) .child(...this.customWidgets.get("center-pane")) + ) .child( new RightPaneContainer() @@ -189,8 +189,10 @@ export default class DesktopLayout { .child(...this.customWidgets.get("right-pane")) ) ) + .optChild(!launcherPaneIsHorizontal && isNewLayout, ) ) ) + .optChild(launcherPaneIsHorizontal && isNewLayout, ) .child() // Desktop-specific dialogs. diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index 5b7d642b73..688d8904d0 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -5,6 +5,7 @@ display: flex; align-items: center; padding-inline: 0.25em; + min-height: 32px; > .breadcrumb-row { flex-grow: 1; From 0545b929e193d044119bb21ee49df666fc1db1e0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 20:17:07 +0200 Subject: [PATCH 218/873] fix(status_bar): react to active note context --- apps/client/src/widgets/layout/Breadcrumb.tsx | 4 +- apps/client/src/widgets/layout/StatusBar.tsx | 12 ++-- apps/client/src/widgets/react/hooks.tsx | 60 ++++++++++++++++++- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/layout/Breadcrumb.tsx b/apps/client/src/widgets/layout/Breadcrumb.tsx index cc02657e09..fe6d59b261 100644 --- a/apps/client/src/widgets/layout/Breadcrumb.tsx +++ b/apps/client/src/widgets/layout/Breadcrumb.tsx @@ -14,13 +14,13 @@ import NoteLink from "../react/NoteLink"; import link_context_menu from "../../menus/link_context_menu"; import { TitleEditor } from "../collections/board"; import server from "../../services/server"; +import FNote from "../../entities/fnote"; const COLLAPSE_THRESHOLD = 5; const INITIAL_ITEMS = 2; const FINAL_ITEMS = 2; -export default function Breadcrumb() { - const { note, noteContext } = useNoteContext(); +export default function Breadcrumb({ note, noteContext }: { note: FNote, noteContext: NoteContext }) { const notePath = buildNotePaths(noteContext?.notePathArray); return ( diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 8dc2618648..459761b7b0 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -2,6 +2,7 @@ import "./StatusBar.css"; import { Locale } from "@triliumnext/commons"; import clsx from "clsx"; +import { type ComponentChildren } from "preact"; import { createPortal } from "preact/compat"; import { useState } from "preact/hooks"; @@ -11,27 +12,28 @@ import { openInAppHelpFromUrl } from "../../services/utils"; import { formatDateTime } from "../../utils/formatters"; import Dropdown, { DropdownProps } from "../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; -import { useNoteContext } from "../react/hooks"; +import { useActiveNoteContext } from "../react/hooks"; import Icon from "../react/Icon"; import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; - +import NoteContext from "../../components/note_context"; interface StatusBarContext { note: FNote; + noteContext: NoteContext; } export default function StatusBar() { - const { note } = useNoteContext(); - const context = note && { note } satisfies StatusBarContext; + const { note, noteContext } = useActiveNoteContext(); + const context = note && noteContext && { note, noteContext } satisfies StatusBarContext; return (
    {context && <>
    - +
    diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 65448eb924..f6f5123f96 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -316,7 +316,7 @@ export function useNoteContext() { useDebugValue(() => `notePath=${notePath}, ntxId=${noteContext?.ntxId}`); return { - note: note, + note, noteId: noteContext?.note?.noteId, notePath: noteContext?.notePath, hoistedNoteId: noteContext?.hoistedNoteId, @@ -327,7 +327,65 @@ export function useNoteContext() { parentComponent, isReadOnlyTemporarilyDisabled }; +} +/** + * Similar to {@link useNoteContext}, but instead of using the note context from the split container that the component is part of, it uses the active note context instead + * (the note currently focused by the user). + */ +export function useActiveNoteContext() { + const [ noteContext, setNoteContext ] = useState(appContext.tabManager.getActiveContext() ?? undefined); + const [ notePath, setNotePath ] = useState(); + const [ note, setNote ] = useState(); + const [ , setViewScope ] = useState(); + const [ isReadOnlyTemporarilyDisabled, setIsReadOnlyTemporarilyDisabled ] = useState(noteContext?.viewScope?.isReadOnly); + const [ refreshCounter, setRefreshCounter ] = useState(0); + + useEffect(() => { + if (!noteContext) { + setNoteContext(appContext.tabManager.getActiveContext() ?? undefined); + } + }, [ noteContext ]); + + useEffect(() => { + setNote(noteContext?.note); + }, [ notePath ]); + + useTriliumEvents([ "setNoteContext", "activeContextChanged", "noteSwitchedAndActivated", "noteSwitched" ], ({}) => { + const noteContext = appContext.tabManager.getActiveContext() ?? undefined; + setNoteContext(noteContext); + setNotePath(noteContext?.notePath); + setViewScope(noteContext?.viewScope); + }); + useTriliumEvent("frocaReloaded", () => { + setNote(noteContext?.note); + }); + useTriliumEvent("noteTypeMimeChanged", ({ noteId }) => { + if (noteId === note?.noteId) { + setRefreshCounter(refreshCounter + 1); + } + }); + useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => { + if (eventNoteContext.ntxId === noteContext?.ntxId) { + setIsReadOnlyTemporarilyDisabled(eventNoteContext?.viewScope?.readOnlyTemporarilyDisabled); + } + }); + + const parentComponent = useContext(ParentComponent) as ReactWrappedWidget; + useDebugValue(() => `notePath=${notePath}, ntxId=${noteContext?.ntxId}`); + + return { + note, + noteId: noteContext?.note?.noteId, + notePath: noteContext?.notePath, + hoistedNoteId: noteContext?.hoistedNoteId, + ntxId: noteContext?.ntxId, + viewScope: noteContext?.viewScope, + componentId: parentComponent.componentId, + noteContext, + parentComponent, + isReadOnlyTemporarilyDisabled + }; } /** From 0162b9d4410865f161634a7a1104573a02f95c74 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 20:18:50 +0200 Subject: [PATCH 219/873] fix(status_bar): language selector appearing for non-text notes --- apps/client/src/widgets/layout/StatusBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 459761b7b0..03ad753c88 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -79,7 +79,7 @@ function LanguageSwitcher({ note }: StatusBarContext) { return ( <> - {getLocaleName(activeLocale)}} @@ -104,7 +104,7 @@ function LanguageSwitcher({ note }: StatusBarContext) { onClick={() => setModalShown(true)} icon="bx bx-cog" >{t("note_language.configure-languages")} - + } {createPortal( , document.body From 28e9abc8bb8b5601bf11fb5102d3c9c88a60b121 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 20:19:37 +0200 Subject: [PATCH 220/873] chore(status_bar): re-order icons to avoid layout shifting --- apps/client/src/widgets/layout/StatusBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 03ad753c88..39c4c01f29 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -37,8 +37,8 @@ export default function StatusBar() {
    - +
    }
    From 2b195155ed9e93bf432e4920531c33f09790fdb3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 20:21:55 +0200 Subject: [PATCH 221/873] fix(note_details): appearing in options --- apps/client/src/widgets/NoteTitleDetails.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/NoteTitleDetails.tsx b/apps/client/src/widgets/NoteTitleDetails.tsx index 60618ecb55..bbd00364c2 100644 --- a/apps/client/src/widgets/NoteTitleDetails.tsx +++ b/apps/client/src/widgets/NoteTitleDetails.tsx @@ -3,11 +3,12 @@ import { useNoteContext, useNoteProperty } from "./react/hooks"; export default function NoteTitleDetails() { const { note } = useNoteContext(); + const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_"); const noteType = useNoteProperty(note, "type"); return (
    - {note && noteType === "book" && } + {note && !isHiddenNote && noteType === "book" && }
    ); } From 95d2160c76c4b5902c0e917f30880cad082081af Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 20:30:15 +0200 Subject: [PATCH 222/873] feat(status_bar): integrate backlinks --- .../src/translations/en/translation.json | 9 +++---- apps/client/src/widgets/BreadcrumbBadges.css | 1 - apps/client/src/widgets/BreadcrumbBadges.tsx | 20 -------------- apps/client/src/widgets/layout/StatusBar.tsx | 27 ++++++++++++++++--- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index d50f1eeda3..2837115676 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2146,10 +2146,6 @@ "shared_publicly_description": "This note has been published online at {{- link}}, and is publicly accessible.\n\nClick to navigate to the shared note or right click for more options.", "shared_locally": "Shared locally", "shared_locally_description": "This note is shared on the local network only at {{- link}}.\n\nClick to navigate to the shared note or right click for more options.", - "backlinks_one": "{{count}} backlink", - "backlinks_other": "{{count}} backlinks", - "backlinks_description_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.", - "backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks.", "clipped_note": "Web clip", "clipped_note_description": "This note was originally taken from {{url}}.\n\nClick to navigate to the source webpage.", "execute_script": "Run script", @@ -2159,6 +2155,9 @@ }, "status_bar": { "language_title": "Change the language of the entire content", - "note_info_title": "View information about this note such as the creation/modification date or the note size." + "note_info_title": "View information about this note such as the creation/modification date or the note size.", + "backlinks": "{{count}}", + "backlinks_title_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.", + "backlinks_title_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks." } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index 582b9c7ba0..55737ae9b9 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -34,7 +34,6 @@ &.read-only-badge { --color: #e33f3b; } &.share-badge { --color: #3b82f6; } &.clipped-note-badge { --color: #57a2a5; } - &.backlinks-badge { color: var(--badge-text-color); } &.execute-badge { --color: #f59e0b; } a { diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 0ab1d7b850..805e9da7bd 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -5,7 +5,6 @@ import { ComponentChildren, MouseEventHandler } from "preact"; import { useRef } from "preact/hooks"; import { t } from "../services/i18n"; -import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "./react/Dropdown"; import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; @@ -16,7 +15,6 @@ export default function BreadcrumbBadges() {
    -
    @@ -66,24 +64,6 @@ function ShareBadge() { ); } -function BacklinksBadge() { - const { note, viewScope } = useNoteContext(); - const count = useBacklinkCount(note, viewScope?.viewMode === "default"); - return (note && count > 0 && - - - - ); -} - function ClippedNoteBadge() { const { note } = useNoteContext(); const [ pageUrl ] = useNoteLabel(note, "pageUrl"); diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 39c4c01f29..6279629a4a 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -6,10 +6,13 @@ import { type ComponentChildren } from "preact"; import { createPortal } from "preact/compat"; import { useState } from "preact/hooks"; +import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; +import { ViewScope } from "../../services/link"; import { openInAppHelpFromUrl } from "../../services/utils"; import { formatDateTime } from "../../utils/formatters"; +import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useActiveNoteContext } from "../react/hooks"; @@ -18,16 +21,16 @@ import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPrope import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; -import NoteContext from "../../components/note_context"; interface StatusBarContext { note: FNote; noteContext: NoteContext; + viewScope: ViewScope; } export default function StatusBar() { - const { note, noteContext } = useActiveNoteContext(); - const context = note && noteContext && { note, noteContext } satisfies StatusBarContext; + const { note, noteContext, viewScope } = useActiveNoteContext(); + const context = note && noteContext && { note, noteContext, viewScope } satisfies StatusBarContext; return (
    @@ -37,6 +40,7 @@ export default function StatusBar() {
    +
    @@ -154,3 +158,20 @@ function NoteInfoValue({ text, title, value }: { text: string; title?: string, v ); } //#endregion + +//#region Backlinks +function BacklinksBadge({ note, viewScope }: StatusBarContext) { + const count = useBacklinkCount(note, viewScope?.viewMode === "default"); + return (note && count > 0 && + + + + ); +} +//#endregion From 6eff62f73f0f2d2626c609e5fe67254fa70fd156 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 20:55:54 +0200 Subject: [PATCH 223/873] feat(status_bar): add new attachment count --- .../src/translations/en/translation.json | 5 +- apps/client/src/widgets/layout/StatusBar.css | 18 +++--- apps/client/src/widgets/layout/StatusBar.tsx | 58 ++++++++++++++++++- .../src/widgets/type_widgets/Attachment.tsx | 35 ++++++----- 4 files changed, 89 insertions(+), 27 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 2837115676..b168d30195 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2156,8 +2156,9 @@ "status_bar": { "language_title": "Change the language of the entire content", "note_info_title": "View information about this note such as the creation/modification date or the note size.", - "backlinks": "{{count}}", "backlinks_title_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.", - "backlinks_title_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks." + "backlinks_title_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks.", + "attachments_title_one": "This note has {{count}} attachment. Click to open the list of attachments in a new tab.", + "attachments_title_other": "This note has {{count}} attachments. Click to open the list of attachments in a new tab." } } diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index 688d8904d0..094e2d3b8d 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -15,27 +15,29 @@ padding: 0.1em; display: flex; gap: 0.1em; + font-size: 0.85em; - .status-bar-dropdown-button { - background: transparent; + .btn { padding: 0 0.5em !important; + background: transparent; display: flex; align-items: center; - - &:after { - content: unset; - } + border: 0; &:focus, &:hover { background: var(--input-background-color); } } + + .status-bar-dropdown-button { + &:after { + content: unset; + } + } } .dropdown { - font-size: 0.85em; - .dropdown-toggle { padding: 0.1em 0.25em; } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 6279629a4a..9be6223711 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -4,7 +4,7 @@ import { Locale } from "@triliumnext/commons"; import clsx from "clsx"; import { type ComponentChildren } from "preact"; import { createPortal } from "preact/compat"; -import { useState } from "preact/hooks"; +import { useContext, useRef, useState } from "preact/hooks"; import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; @@ -15,12 +15,17 @@ import { formatDateTime } from "../../utils/formatters"; import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; -import { useActiveNoteContext } from "../react/hooks"; +import { useActiveNoteContext, useStaticTooltip, useTooltip } from "../react/hooks"; import Icon from "../react/Icon"; import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; +import { useAttachments } from "../type_widgets/Attachment"; +import ActionButton from "../react/ActionButton"; +import Button from "../react/Button"; +import { CommandNames } from "../../components/app_context"; +import { ParentComponent } from "../react/react_utils"; interface StatusBarContext { note: FNote; @@ -40,6 +45,7 @@ export default function StatusBar() {
    + @@ -75,6 +81,35 @@ function StatusBarDropdown({ children, icon, text, buttonClassName, titleOptions ); } +function StatusBarButton({ className, icon, text, title, triggerCommand }: { + className?: string; + icon: string; + title: string; + text?: string | number; + disabled?: boolean; + triggerCommand: CommandNames; +}) { + const parentComponent = useContext(ParentComponent); + const buttonRef = useRef(null); + useStaticTooltip(buttonRef, { + placement: "top", + fallbackPlacements: [ "top" ], + popperConfig: { strategy: "fixed" }, + title + }); + + return ( + + ); +} + //#region Language Switcher function LanguageSwitcher({ note }: StatusBarContext) { const [ modalShown, setModalShown ] = useState(false); @@ -166,7 +201,7 @@ function BacklinksBadge({ note, viewScope }: StatusBarContext) { @@ -175,3 +210,20 @@ function BacklinksBadge({ note, viewScope }: StatusBarContext) { ); } //#endregion + +//#region Attachment count +function AttachmentCount({ note }: StatusBarContext) { + const attachments = useAttachments(note); + const count = attachments.length; + + return (note && count > 0 && + + ); +} +//#endregion diff --git a/apps/client/src/widgets/type_widgets/Attachment.tsx b/apps/client/src/widgets/type_widgets/Attachment.tsx index 3fdc60e932..bef480c6f5 100644 --- a/apps/client/src/widgets/type_widgets/Attachment.tsx +++ b/apps/client/src/widgets/type_widgets/Attachment.tsx @@ -26,24 +26,13 @@ import ws from "../../services/ws"; import appContext from "../../components/app_context"; import { ConvertAttachmentToNoteResponse } from "@triliumnext/commons"; import options from "../../services/options"; +import FNote from "../../entities/fnote"; /** * Displays the full list of attachments of a note and allows the user to interact with them. */ export function AttachmentList({ note }: TypeWidgetProps) { - const [ attachments, setAttachments ] = useState([]); - - function refresh() { - note.getAttachments().then(attachments => setAttachments(Array.from(attachments))); - } - - useEffect(refresh, [ note ]); - - useTriliumEvent("entitiesReloaded", ({ loadResults }) => { - if (loadResults.getAttachmentRows().some((att) => att.attachmentId && att.ownerId === note.noteId)) { - refresh(); - } - }); + const attachments = useAttachments(note); return ( <> @@ -59,7 +48,25 @@ export function AttachmentList({ note }: TypeWidgetProps) { )}
    - ) + ); +} + +export function useAttachments(note: FNote) { + const [ attachments, setAttachments ] = useState([]); + + function refresh() { + note.getAttachments().then(attachments => setAttachments(Array.from(attachments))); + } + + useEffect(refresh, [ note ]); + + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { + if (loadResults.getAttachmentRows().some((att) => att.attachmentId && att.ownerId === note.noteId)) { + refresh(); + } + }); + + return attachments; } function AttachmentListHeader({ noteId }: { noteId: string }) { From 1b725175c6e831638cb4bc1cffa1d9503b048f02 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 20:59:57 +0200 Subject: [PATCH 224/873] refactor(status_bar): solve warnings --- apps/client/src/widgets/layout/StatusBar.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 9be6223711..b28671b2ce 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -6,6 +6,7 @@ import { type ComponentChildren } from "preact"; import { createPortal } from "preact/compat"; import { useContext, useRef, useState } from "preact/hooks"; +import { CommandNames } from "../../components/app_context"; import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; @@ -15,22 +16,19 @@ import { formatDateTime } from "../../utils/formatters"; import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; -import { useActiveNoteContext, useStaticTooltip, useTooltip } from "../react/hooks"; +import { useActiveNoteContext, useStaticTooltip } from "../react/hooks"; import Icon from "../react/Icon"; +import { ParentComponent } from "../react/react_utils"; import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; +import { useAttachments } from "../type_widgets/Attachment"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; -import { useAttachments } from "../type_widgets/Attachment"; -import ActionButton from "../react/ActionButton"; -import Button from "../react/Button"; -import { CommandNames } from "../../components/app_context"; -import { ParentComponent } from "../react/react_utils"; interface StatusBarContext { note: FNote; noteContext: NoteContext; - viewScope: ViewScope; + viewScope?: ViewScope; } export default function StatusBar() { @@ -123,15 +121,16 @@ function LanguageSwitcher({ note }: StatusBarContext) { title={t("status_bar.language_title")} text={{getLocaleName(activeLocale)}} > - {processedLocales.map(locale => { + {processedLocales.map((locale, index) => { if (typeof locale === "object") { return setCurrentNoteLanguage(locale.id)} - >{locale.name} + >{locale.name}
    ; } else { - return + return ; } })} From efff38b11665033da8b5d9cc3822413a5d328998 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 21:19:16 +0200 Subject: [PATCH 225/873] feat(status_bar): attribute button (not yet interactive) --- .../src/translations/en/translation.json | 5 +++- apps/client/src/widgets/layout/StatusBar.tsx | 28 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index b168d30195..c86af5e0f4 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2159,6 +2159,9 @@ "backlinks_title_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.", "backlinks_title_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks.", "attachments_title_one": "This note has {{count}} attachment. Click to open the list of attachments in a new tab.", - "attachments_title_other": "This note has {{count}} attachments. Click to open the list of attachments in a new tab." + "attachments_title_other": "This note has {{count}} attachments. Click to open the list of attachments in a new tab.", + "attributes_one": "{{count}} attribute", + "attributes_other": "{{count}} attributes", + "attributes_title": "Click to open a dedicated pane to edit this note's owned attributes, as well as to see the list of inherited attributes." } } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index b28671b2ce..6696cea3e4 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -16,7 +16,7 @@ import { formatDateTime } from "../../utils/formatters"; import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; -import { useActiveNoteContext, useStaticTooltip } from "../react/hooks"; +import { useActiveNoteContext, useStaticTooltip, useTriliumEvent } from "../react/hooks"; import Icon from "../react/Icon"; import { ParentComponent } from "../react/react_utils"; import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; @@ -24,6 +24,7 @@ import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; import { useAttachments } from "../type_widgets/Attachment"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; +import attributes from "../../services/attributes"; interface StatusBarContext { note: FNote; @@ -43,6 +44,7 @@ export default function StatusBar() {
    + @@ -217,7 +219,7 @@ function AttachmentCount({ note }: StatusBarContext) { return (note && count > 0 && { + if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) { + setCount(note.attributes.length); + } + })); + + return ( + + ); +} +//#endregion From c6d97e3d4bcbb364b3fb7bab02881994c490fc0b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 21:29:40 +0200 Subject: [PATCH 226/873] feat(status_bar): basic integration of attribute editor --- apps/client/src/widgets/layout/StatusBar.css | 112 +++++++++--------- apps/client/src/widgets/layout/StatusBar.tsx | 69 ++++++++--- .../src/widgets/ribbon/OwnedAttributesTab.tsx | 3 +- 3 files changed, 112 insertions(+), 72 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index 094e2d3b8d..e5ee87a087 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -2,70 +2,74 @@ contain: none; border-top: 1px solid var(--main-border-color); background-color: var(--left-pane-background-color); - display: flex; - align-items: center; - padding-inline: 0.25em; - min-height: 32px; - > .breadcrumb-row { - flex-grow: 1; - } - - > .actions-row { - padding: 0.1em; + .status-bar-main-row { + min-height: 32px; display: flex; - gap: 0.1em; - font-size: 0.85em; + align-items: center; + padding-inline: 0.25em; - .btn { - padding: 0 0.5em !important; - background: transparent; + > .breadcrumb-row { + flex-grow: 1; + } + + > .actions-row { + padding: 0.1em; display: flex; - align-items: center; - border: 0; + gap: 0.1em; + font-size: 0.85em; - &:focus, - &:hover { - background: var(--input-background-color); - } - } + .btn { + padding: 0 0.5em !important; + background: transparent; + display: flex; + align-items: center; + border: 0; - .status-bar-dropdown-button { - &:after { - content: unset; - } - } - } - - .dropdown { - .dropdown-toggle { - padding: 0.1em 0.25em; - } - } - - .dropdown-note-info { - width: max-content; - - ul { - list-style-type: none; - padding: 0.5em; - margin: 0; - display: table; - - li { - display: table-row; - - > strong { - display: table-cell; - padding: 0.2em 0; + &:focus, + &:hover { + background: var(--input-background-color); } + } - > span { - display: table-cell; - user-select: text; - padding-left: 2em; + .status-bar-dropdown-button { + &:after { + content: unset; + } + } + } + + .dropdown { + .dropdown-toggle { + padding: 0.1em 0.25em; + } + } + + .dropdown-note-info { + width: max-content; + + ul { + list-style-type: none; + padding: 0.5em; + margin: 0; + display: table; + + li { + display: table-row; + + > strong { + display: table-cell; + padding: 0.2em 0; + } + + > span { + display: table-cell; + user-select: text; + padding-left: 2em; + } } } } } + } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 6696cea3e4..b3ed74b5b6 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -9,6 +9,7 @@ import { useContext, useRef, useState } from "preact/hooks"; import { CommandNames } from "../../components/app_context"; import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; +import attributes from "../../services/attributes"; import { t } from "../../services/i18n"; import { ViewScope } from "../../services/link"; import { openInAppHelpFromUrl } from "../../services/utils"; @@ -20,11 +21,11 @@ import { useActiveNoteContext, useStaticTooltip, useTriliumEvent } from "../reac import Icon from "../react/Icon"; import { ParentComponent } from "../react/react_utils"; import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; +import AttributeEditor, { AttributeEditorImperativeHandlers } from "../ribbon/components/AttributeEditor"; import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; import { useAttachments } from "../type_widgets/Attachment"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; -import attributes from "../../services/attributes"; interface StatusBarContext { note: FNote; @@ -38,19 +39,23 @@ export default function StatusBar() { return (
    - {context && <> -
    - -
    + {context && } -
    - - - - - -
    - } +
    + {context && <> +
    + +
    + +
    + + + + + +
    + } +
    ); } @@ -81,14 +86,18 @@ function StatusBarDropdown({ children, icon, text, buttonClassName, titleOptions ); } -function StatusBarButton({ className, icon, text, title, triggerCommand }: { +interface StatusBarButtonBaseProps { className?: string; icon: string; title: string; text?: string | number; disabled?: boolean; - triggerCommand: CommandNames; -}) { +} + +type StatusBarButtonWithCommand = StatusBarButtonBaseProps & { triggerCommand: CommandNames; }; +type StatusBarButtonWithClick = StatusBarButtonBaseProps & { onClick: () => void; }; + +function StatusBarButton({ className, icon, text, title, ...restProps }: StatusBarButtonWithCommand | StatusBarButtonWithClick) { const parentComponent = useContext(ParentComponent); const buttonRef = useRef(null); useStaticTooltip(buttonRef, { @@ -103,7 +112,13 @@ function StatusBarButton({ className, icon, text, title, triggerCommand }: { ref={buttonRef} className={clsx("btn select-button", className)} type="button" - onClick={() => parentComponent?.triggerCommand(triggerCommand)} + onClick={() => { + if ("triggerCommand" in restProps) { + parentComponent?.triggerCommand(restProps.triggerCommand); + } else { + restProps.onClick(); + } + }} >  {text} @@ -246,7 +261,27 @@ function AttributesButton({ note }: StatusBarContext) { icon="bx bx-list-check" title={t("status_bar.attributes_title")} text={t("status_bar.attributes", { count })} + onClick={() => { + alert("Hi"); + }} /> ); } + +function AttributesPane({ note, noteContext }: StatusBarContext) { + const parentComponent = useContext(ParentComponent); + const api = useRef(null); + + return ( +
    + {parentComponent &&
    + ); +} //#endregion diff --git a/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx index ae3d90c072..b80d748b57 100644 --- a/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx +++ b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx @@ -1,4 +1,5 @@ import { useMemo, useRef } from "preact/hooks"; + import { useLegacyImperativeHandlers, useTriliumEvents } from "../react/hooks"; import AttributeEditor, { AttributeEditorImperativeHandlers } from "./components/AttributeEditor"; import { TabContext } from "./ribbon-interface"; @@ -25,5 +26,5 @@ export default function OwnedAttributesTab({ note, hidden, activate, ntxId, ...r
    - ) + ); } From 870499bc3a05f7fa5c027fc3143abfa5d9fa5cf0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 21:41:05 +0200 Subject: [PATCH 227/873] feat(status_bar): basic integration of inherited attributes --- apps/client/src/widgets/layout/StatusBar.css | 6 +++++- apps/client/src/widgets/layout/StatusBar.tsx | 19 +++++++++++++------ .../widgets/ribbon/InheritedAttributesTab.tsx | 6 +++--- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index e5ee87a087..77475869be 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -3,7 +3,7 @@ border-top: 1px solid var(--main-border-color); background-color: var(--left-pane-background-color); - .status-bar-main-row { + > .status-bar-main-row { min-height: 32px; display: flex; align-items: center; @@ -72,4 +72,8 @@ } } + > .attribute-list { + font-size: 0.9em; + } + } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index b3ed74b5b6..0ca29941c9 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -26,6 +26,7 @@ import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; import { useAttachments } from "../type_widgets/Attachment"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; +import InheritedAttributesTab from "../ribbon/InheritedAttributesTab"; interface StatusBarContext { note: FNote; @@ -272,15 +273,21 @@ function AttributesPane({ note, noteContext }: StatusBarContext) { const parentComponent = useContext(ParentComponent); const api = useRef(null); - return ( + const context = parentComponent && { + componentId: parentComponent.componentId, + note, + hidden: !note + }; + + return (context &&
    - {parentComponent && + +
    ); } diff --git a/apps/client/src/widgets/ribbon/InheritedAttributesTab.tsx b/apps/client/src/widgets/ribbon/InheritedAttributesTab.tsx index bc6f3eb49e..9e7ff42883 100644 --- a/apps/client/src/widgets/ribbon/InheritedAttributesTab.tsx +++ b/apps/client/src/widgets/ribbon/InheritedAttributesTab.tsx @@ -9,7 +9,7 @@ import RawHtml from "../react/RawHtml"; import { joinElements } from "../react/react_utils"; import AttributeDetailWidget from "../attribute_widgets/attribute_detail"; -export default function InheritedAttributesTab({ note, componentId }: TabContext) { +export default function InheritedAttributesTab({ note, componentId }: Pick) { const [ inheritedAttributes, setInheritedAttributes ] = useState(); const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget()); @@ -34,7 +34,7 @@ export default function InheritedAttributesTab({ note, componentId }: TabContext refresh(); } }); - + return (
    @@ -83,4 +83,4 @@ function InheritedAttribute({ attribute, onClick }: { attribute: FAttribute, onC onClick={onClick} /> ); -} \ No newline at end of file +} From 5d438a877b8992cf9030170d2d895070ce36f92f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 21:44:00 +0200 Subject: [PATCH 228/873] feat(status_bar): improve alignment of attribute editor --- apps/client/src/widgets/layout/StatusBar.css | 10 ++++++++++ apps/client/src/widgets/layout/StatusBar.tsx | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/layout/StatusBar.css b/apps/client/src/widgets/layout/StatusBar.css index 77475869be..1fd8450831 100644 --- a/apps/client/src/widgets/layout/StatusBar.css +++ b/apps/client/src/widgets/layout/StatusBar.css @@ -74,6 +74,16 @@ > .attribute-list { font-size: 0.9em; + padding: 0.5em 0.75em; + + .inherited-attributes-widget > div { + padding: 0; + font-size: 0.9em; + } + + .attribute-list-editor { + padding: 0 !important; + } } } diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index 0ca29941c9..78608c0fe5 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -22,11 +22,11 @@ import Icon from "../react/Icon"; import { ParentComponent } from "../react/react_utils"; import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab"; import AttributeEditor, { AttributeEditorImperativeHandlers } from "../ribbon/components/AttributeEditor"; +import InheritedAttributesTab from "../ribbon/InheritedAttributesTab"; import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; import { useAttachments } from "../type_widgets/Attachment"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; -import InheritedAttributesTab from "../ribbon/InheritedAttributesTab"; interface StatusBarContext { note: FNote; From 45927053f3dff72aaf475c28b1148e81fc048c77 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 21:48:11 +0200 Subject: [PATCH 229/873] fix(ribbon): links in inherited attributes not visible --- apps/client/src/widgets/ribbon/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index b1813d65fe..8ffb3e2098 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -355,6 +355,10 @@ body[dir=rtl] .attribute-list-editor { max-height: 200px; overflow: auto; padding: 14px 12px 13px 12px; + + a.reference-link { + text-decoration: underline; + } } /* #endregion */ From 685109556c5f5fda3ef2e06dd8434927b5e6d30a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 21:49:42 +0200 Subject: [PATCH 230/873] chore(ribbon): hide inherited & owned attributes on new layout --- apps/client/src/widgets/ribbon/RibbonDefinition.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index 0be6b9147e..dbb275586a 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -97,15 +97,15 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ title: t("owned_attribute_list.owned_attributes"), icon: "bx bx-list-check", content: OwnedAttributesTab, - show: ({note}) => !note?.isLaunchBarConfig(), + show: ({note}) => !isNewLayout && !note?.isLaunchBarConfig(), toggleCommand: "toggleRibbonTabOwnedAttributes", - stayInDom: true + stayInDom: !isNewLayout }, { title: t("inherited_attribute_list.title"), icon: "bx bx-list-plus", content: InheritedAttributesTab, - show: ({note}) => !note?.isLaunchBarConfig(), + show: ({note}) => !isNewLayout && !note?.isLaunchBarConfig(), toggleCommand: "toggleRibbonTabInheritedAttributes" }, { From 60fc34ffac4c2d503a152b7e2f21b4557c0a4ef6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 12 Dec 2025 21:57:42 +0200 Subject: [PATCH 231/873] feat(status_bar): functional attribute toggle button --- .../src/stylesheets/theme-next/forms.css | 9 ++++--- apps/client/src/widgets/layout/StatusBar.css | 1 + apps/client/src/widgets/layout/StatusBar.tsx | 26 ++++++++++++------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next/forms.css b/apps/client/src/stylesheets/theme-next/forms.css index fb53f167d4..de76231be2 100644 --- a/apps/client/src/stylesheets/theme-next/forms.css +++ b/apps/client/src/stylesheets/theme-next/forms.css @@ -154,7 +154,7 @@ button.btn.btn-success kbd { color: var(--button-group-active-button-text-color); } -/* +/* * Input boxes */ @@ -399,7 +399,8 @@ button.select-button.dropdown-toggle.btn:active { select:focus, select.form-select:focus, select.form-control:focus, -.select-button.dropdown-toggle.btn:focus { +.select-button.dropdown-toggle.btn:focus, +.select-button.focus-outline:focus { box-shadow: unset; outline: 3px solid var(--input-focus-outline-color); outline-offset: 0; @@ -422,7 +423,7 @@ optgroup { line-height: 40px; } -/* +/* * File input * *
    - - } else { - return ( -
    - {inputNode} - { definition.labelType === "color" && } - { definition.labelType === "url" && ( - { - const inputEl = document.getElementById(inputId) as HTMLInputElement | null; - const url = inputEl?.value; - if (url) { - window.open(url, "_blank"); - } - }} - /> - )} -
    - ); + ; } + return ( +
    + {inputNode} + { definition.labelType === "color" && } + { definition.labelType === "url" && ( + { + const inputEl = document.getElementById(inputId) as HTMLInputElement | null; + const url = inputEl?.value; + if (url) { + window.open(url, "_blank"); + } + }} + /> + )} +
    + ); + } @@ -282,7 +293,7 @@ function ColorPicker({ cell, onChange, inputId }: CellProps & { }} /> - ) + ); } function RelationInput({ inputId, ...props }: CellProps & { inputId: string }) { @@ -295,7 +306,7 @@ function RelationInput({ inputId, ...props }: CellProps & { inputId: string }) { await updateAttribute(note, cell, componentId, value, setCells); }} /> - ) + ); } function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, componentId }: CellProps) { @@ -346,13 +357,13 @@ function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, compone name: cell.valueName, value: "" } - }) + }); } setCells(cells.toSpliced(index, 1, ...newOnesToInsert)); }} /> - ) + ); } function PromotedActionButton({ icon, title, onClick }: { @@ -366,7 +377,7 @@ function PromotedActionButton({ icon, title, onClick }: { title={title} onClick={onClick} /> - ) + ); } function InputButton({ icon, className, title, onClick }: { @@ -381,7 +392,7 @@ function InputButton({ icon, className, title, onClick }: { title={title} onClick={onClick} /> - ) + ); } function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute, onChangeListener: OnChangeListener) { @@ -406,7 +417,7 @@ function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute, [ { displayKey: "value", - source: function (term, cb) { + source (term, cb) { term = term.toLowerCase(); const filtered = attributeValues.filter((attr) => attr.value.toLowerCase().includes(term)); diff --git a/apps/client/src/widgets/layout/NoteTitleActions.tsx b/apps/client/src/widgets/layout/NoteTitleActions.tsx index 31323ccbfa..9678084f6b 100644 --- a/apps/client/src/widgets/layout/NoteTitleActions.tsx +++ b/apps/client/src/widgets/layout/NoteTitleActions.tsx @@ -5,16 +5,18 @@ import clsx from "clsx"; import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; import CollectionProperties from "../note_bars/CollectionProperties"; +import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes"; import Collapsible from "../react/Collapsible"; import { useNoteContext, useNoteProperty } from "../react/hooks"; import SearchDefinitionTab from "../ribbon/SearchDefinitionTab"; export default function NoteTitleActions() { - const { note, ntxId } = useNoteContext(); + const { note, ntxId, componentId } = useNoteContext(); const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_"); const noteType = useNoteProperty(note, "type"); const items = [ + note && , note && noteType === "search" && , note && !isHiddenNote && noteType === "book" && ].filter(Boolean); @@ -36,3 +38,16 @@ function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null | ); } + +function PromotedAttributes({ note, componentId }: { note: FNote | null | undefined, componentId: string }) { + const [ cells, setCells ] = usePromotedAttributeData(note, componentId); + if (!cells?.length) return false; + + return (note && ( + + + + )); +} From 455dc5dc11ede6fe54281bccbb966041350b3234 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 12:10:05 +0200 Subject: [PATCH 439/873] feat(layout): automatically collapse promoted attributes in full-height notes --- apps/client/src/widgets/NoteDetail.tsx | 36 ++++++++++--------- .../src/widgets/layout/NoteTitleActions.tsx | 31 ++++++++++++---- apps/client/src/widgets/react/Collapsible.tsx | 12 +++++-- 3 files changed, 52 insertions(+), 27 deletions(-) diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index a5bc629162..894bb4ac54 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -1,16 +1,18 @@ -import { useNoteContext, useTriliumEvent } from "./react/hooks" -import FNote from "../entities/fnote"; -import protected_session_holder from "../services/protected_session_holder"; -import { useEffect, useRef, useState } from "preact/hooks"; -import NoteContext from "../components/note_context"; -import { isValidElement, VNode } from "preact"; -import { TypeWidgetProps } from "./type_widgets/type_widget"; import "./NoteDetail.css"; + +import { isValidElement, VNode } from "preact"; +import { useEffect, useRef, useState } from "preact/hooks"; + +import NoteContext from "../components/note_context"; +import FNote from "../entities/fnote"; import attributes from "../services/attributes"; -import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types"; -import { dynamicRequire, isElectron, isMobile } from "../services/utils"; -import toast from "../services/toast.js"; import { t } from "../services/i18n"; +import protected_session_holder from "../services/protected_session_holder"; +import toast from "../services/toast.js"; +import { dynamicRequire, isElectron, isMobile } from "../services/utils"; +import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types"; +import { useNoteContext, useTriliumEvent } from "./react/hooks"; +import { TypeWidgetProps } from "./type_widgets/type_widget"; /** * The note detail is in charge of rendering the content of a note, by determining its type (e.g. text, code) and using the appropriate view widget. @@ -80,7 +82,7 @@ export default function NoteDetail() { parentComponent.handleEvent("noteTypeMimeChanged", { noteId: note.noteId }); } else if (note.noteId && loadResults.isNoteReloaded(note.noteId, parentComponent.componentId) - && (type !== (await getWidgetType(note, noteContext)) || mime !== note?.mime)) { + && (type !== (await getExtendedWidgetType(note, noteContext)) || mime !== note?.mime)) { // this needs to have a triggerEvent so that e.g., note type (not in the component subtree) is updated parentComponent.triggerEvent("noteTypeMimeChanged", { noteId: note.noteId }); } else { @@ -212,7 +214,7 @@ export default function NoteDetail() { isVisible={type === itemType} isFullHeight={isFullHeight} props={props} - /> + />; })}
    ); @@ -254,7 +256,7 @@ function useNoteInfo() { const [ mime, setMime ] = useState(); function refresh() { - getWidgetType(actualNote, noteContext).then(type => { + getExtendedWidgetType(actualNote, noteContext).then(type => { setNote(actualNote); setType(type); setMime(actualNote?.mime); @@ -282,12 +284,12 @@ async function getCorrespondingWidget(type: ExtendedNoteType): Promise { +export async function getExtendedWidgetType(note: FNote | null | undefined, noteContext: NoteContext | undefined): Promise { if (!noteContext) return undefined; if (!note) { // If the note is null, then it's a new tab. If it's undefined, then it's not loaded yet. @@ -324,7 +326,7 @@ async function getWidgetType(note: FNote | null | undefined, noteContext: NoteCo return resultingType; } -function checkFullHeight(noteContext: NoteContext | undefined, type: ExtendedNoteType | undefined) { +export function checkFullHeight(noteContext: NoteContext | undefined, type: ExtendedNoteType | undefined) { if (!noteContext) return false; // https://github.com/zadam/trilium/issues/2522 diff --git a/apps/client/src/widgets/layout/NoteTitleActions.tsx b/apps/client/src/widgets/layout/NoteTitleActions.tsx index 9678084f6b..a04bfc2429 100644 --- a/apps/client/src/widgets/layout/NoteTitleActions.tsx +++ b/apps/client/src/widgets/layout/NoteTitleActions.tsx @@ -1,22 +1,25 @@ import "./NoteTitleActions.css"; import clsx from "clsx"; +import { useEffect, useState } from "preact/hooks"; +import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; import CollectionProperties from "../note_bars/CollectionProperties"; +import { checkFullHeight, getExtendedWidgetType } from "../NoteDetail"; import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes"; -import Collapsible from "../react/Collapsible"; +import Collapsible, { ExternallyControlledCollapsible } from "../react/Collapsible"; import { useNoteContext, useNoteProperty } from "../react/hooks"; import SearchDefinitionTab from "../ribbon/SearchDefinitionTab"; export default function NoteTitleActions() { - const { note, ntxId, componentId } = useNoteContext(); + const { note, ntxId, componentId, noteContext } = useNoteContext(); const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_"); const noteType = useNoteProperty(note, "type"); const items = [ - note && , + note && , note && noteType === "search" && , note && !isHiddenNote && noteType === "book" && ].filter(Boolean); @@ -39,15 +42,29 @@ function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null | ); } -function PromotedAttributes({ note, componentId }: { note: FNote | null | undefined, componentId: string }) { +function PromotedAttributes({ note, componentId, noteContext }: { + note: FNote | null | undefined, + componentId: string, + noteContext: NoteContext | undefined +}) { const [ cells, setCells ] = usePromotedAttributeData(note, componentId); - if (!cells?.length) return false; + const [ expanded, setExpanded ] = useState(false); + useEffect(() => { + getExtendedWidgetType(note, noteContext).then(extendedNoteType => { + const fullHeight = checkFullHeight(noteContext, extendedNoteType); + setExpanded(!fullHeight); + }); + }, [ note, noteContext ]); + + if (!cells?.length) return false; return (note && ( - - + )); } diff --git a/apps/client/src/widgets/react/Collapsible.tsx b/apps/client/src/widgets/react/Collapsible.tsx index 2bf5be43dc..e98e3782f2 100644 --- a/apps/client/src/widgets/react/Collapsible.tsx +++ b/apps/client/src/widgets/react/Collapsible.tsx @@ -13,10 +13,17 @@ interface CollapsibleProps extends Pick, "classNa initiallyExpanded?: boolean; } -export default function Collapsible({ title, children, className, initiallyExpanded }: CollapsibleProps) { +export default function Collapsible({ initiallyExpanded, ...restProps }: CollapsibleProps) { + const [ expanded, setExpanded ] = useState(initiallyExpanded); + return ; +} + +export function ExternallyControlledCollapsible({ title, children, className, expanded, setExpanded }: Omit & { + expanded: boolean | undefined; + setExpanded: (expanded: boolean) => void +}) { const bodyRef = useRef(null); const innerRef = useRef(null); - const [ expanded, setExpanded ] = useState(initiallyExpanded); const { height } = useElementSize(innerRef) ?? {}; const contentId = useUniqueName(); @@ -48,5 +55,4 @@ export default function Collapsible({ title, children, className, initiallyExpan ); - } From ad8e52f744bfcd4af858aa8f3f51265de1d9e445 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 12:14:44 +0200 Subject: [PATCH 440/873] feat(layout): disable transition when promoted attributes are shown by default --- apps/client/src/widgets/react/Collapsible.css | 7 ++++++- apps/client/src/widgets/react/Collapsible.tsx | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/react/Collapsible.css b/apps/client/src/widgets/react/Collapsible.css index aaa61b6e64..7dca2f5651 100644 --- a/apps/client/src/widgets/react/Collapsible.css +++ b/apps/client/src/widgets/react/Collapsible.css @@ -17,7 +17,6 @@ .collapsible-body { height: 0; overflow: hidden; - transition: height 250ms ease-in; } .collapsible-inner-body { @@ -29,4 +28,10 @@ transform: rotate(90deg); } } + + &.with-transition { + .collapsible-body { + transition: height 250ms ease-in; + } + } } diff --git a/apps/client/src/widgets/react/Collapsible.tsx b/apps/client/src/widgets/react/Collapsible.tsx index e98e3782f2..a6e1957b34 100644 --- a/apps/client/src/widgets/react/Collapsible.tsx +++ b/apps/client/src/widgets/react/Collapsible.tsx @@ -2,7 +2,7 @@ import "./Collapsible.css"; import clsx from "clsx"; import { ComponentChildren, HTMLAttributes } from "preact"; -import { useRef, useState } from "preact/hooks"; +import { useEffect, useRef, useState } from "preact/hooks"; import { useElementSize, useUniqueName } from "./hooks"; import Icon from "./Icon"; @@ -26,9 +26,20 @@ export function ExternallyControlledCollapsible({ title, children, className, ex const innerRef = useRef(null); const { height } = useElementSize(innerRef) ?? {}; const contentId = useUniqueName(); + const [ transitionEnabled, setTransitionEnabled ] = useState(false); + + useEffect(() => { + const timeout = setTimeout(() => { + setTransitionEnabled(true); + }, 200); + return () => clearTimeout(timeout); + }, []); return ( -
    +
    diff --git a/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.tsx b/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.tsx index f41d3701ba..8d768f486c 100644 --- a/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.tsx +++ b/apps/client/src/widgets/type_widgets/options/components/RadioWithIllustration.tsx @@ -10,7 +10,7 @@ interface RadioWithIllustrationProps { illustration: ComponentChild; }[]; currentValue: string; - onChange(newValue: string); + onChange(newValue: string): void; } export default function RadioWithIllustration({ currentValue, onChange, values }: RadioWithIllustrationProps) { From d0b0a13b6d45340f378efb320a480d1d71be0418 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 15:44:01 +0200 Subject: [PATCH 558/873] chore(options/appearance): use translations --- apps/client/src/translations/en/translation.json | 5 ++++- apps/client/src/widgets/type_widgets/options/appearance.tsx | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 8764f80018..7e74fb4713 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2116,7 +2116,10 @@ }, "settings_appearance": { "related_code_blocks": "Color scheme for code blocks in text notes", - "related_code_notes": "Color scheme for code notes" + "related_code_notes": "Color scheme for code notes", + "ui": "User interface", + "ui_old_layout": "Old layout", + "ui_new_layout": "New layout" }, "units": { "percentage": "%" diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index f88ec4058c..1896d1855a 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -115,7 +115,7 @@ function LayoutSwitcher() { const [ newLayout, setNewLayout ] = useTriliumOptionBool("newLayout"); return ( - + { @@ -123,8 +123,8 @@ function LayoutSwitcher() { reloadFrontendApp(); }} values={[ - { key: "old-layout", text: "Old layout", illustration: }, - { key: "new-layout", text: "New layout", illustration: } + { key: "old-layout", text: t("settings_appearance.ui_old_layout"), illustration: }, + { key: "new-layout", text: t("settings_appearance.ui_new_layout"), illustration: } ]} /> From 87d99aaffa0730baa03d9c89b92a499c25fcc01a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 15:46:00 +0200 Subject: [PATCH 559/873] fix(layout): experimental styles not applied --- apps/client/src/services/experimental_features.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index 23477eae9d..d252e2a776 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -28,7 +28,11 @@ export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId): } export function getEnabledExperimentalFeatureIds() { - return getEnabledFeatures().values(); + const values = [ ...getEnabledFeatures().values() ]; + if (options.is("newLayout")) { + values.push("new-layout"); + } + return values; } export async function toggleExperimentalFeature(featureId: ExperimentalFeatureId, enable: boolean) { From 261c1f77cfbe21147b63d24285843ee17bd47c11 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 15:47:11 +0200 Subject: [PATCH 560/873] fix(layout): 2px margin in code notes --- apps/client/src/widgets/layout/InlineTitle.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/layout/InlineTitle.css b/apps/client/src/widgets/layout/InlineTitle.css index a667e35fc5..613c845e3c 100644 --- a/apps/client/src/widgets/layout/InlineTitle.css +++ b/apps/client/src/widgets/layout/InlineTitle.css @@ -7,7 +7,6 @@ } .inline-title { - margin-top: 2px; /* Allow space for the focus outline */ max-width: var(--max-content-width); container-type: inline-size; padding-inline-start: 24px; @@ -111,7 +110,7 @@ body.prefers-centered-content .inline-title { .note-type-switcher { --badge-radius: 12px; - + position: relative; top: 5px; padding: .25em 0; @@ -121,7 +120,7 @@ body.prefers-centered-content .inline-title { min-width: 0; gap: 5px; min-height: 35px; - + >* { flex-shrink: 0; animation: note-type-switcher-intro 200ms ease-in; From cbecc2499937db926dca36c615d224808fe57426 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 16:05:13 +0200 Subject: [PATCH 561/873] feat(call_to_action): inform about the layout change --- .../src/translations/en/translation.json | 2 + .../src/widgets/dialogs/call_to_action.tsx | 13 ++++--- .../dialogs/call_to_action_definitions.ts | 39 +++++++++++-------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 7e74fb4713..19c2940445 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2109,6 +2109,8 @@ "background_effects_title": "Background effects are now stable", "background_effects_message": "On Windows devices, background effects are now fully stable. The background effects adds a touch of color to the user interface by blurring the background behind it. This technique is also used in other applications such as Windows Explorer.", "background_effects_button": "Enable background effects", + "new_layout_title": "New layout", + "new_layout_message": "We’ve introduced a modernized layout for Trilium. The ribbon has been removed and seamlessly integrated into the main interface, with a new status bar and expandable sections (such as promoted attributes) taking over key functions.\n\nThe new layout is enabled by default, and can be temporarily disabled via Options → Appearance.", "dismiss": "Dismiss" }, "settings": { diff --git a/apps/client/src/widgets/dialogs/call_to_action.tsx b/apps/client/src/widgets/dialogs/call_to_action.tsx index 1ae4a14ebb..9267384ae7 100644 --- a/apps/client/src/widgets/dialogs/call_to_action.tsx +++ b/apps/client/src/widgets/dialogs/call_to_action.tsx @@ -1,11 +1,12 @@ import { useMemo, useState } from "preact/hooks"; + +import { t } from "../../services/i18n"; import Button from "../react/Button"; import Modal from "../react/Modal"; import { dismissCallToAction, getCallToActions } from "./call_to_action_definitions"; -import { t } from "../../services/i18n"; export default function CallToActionDialog() { - const activeCallToActions = useMemo(() => getCallToActions(), []); + const activeCallToActions = useMemo(() => getCallToActions(), []); const [ activeIndex, setActiveIndex ] = useState(0); const [ shown, setShown ] = useState(true); const activeItem = activeCallToActions[activeIndex]; @@ -36,11 +37,13 @@ export default function CallToActionDialog() { await dismissCallToAction(activeItem.id); await button.onClick(); goToNext(); - }}/> + }}/> )} } > -

    {activeItem.message}

    +

    {activeItem.message}

    - ) + ); } diff --git a/apps/client/src/widgets/dialogs/call_to_action_definitions.ts b/apps/client/src/widgets/dialogs/call_to_action_definitions.ts index 31982689e4..056672b169 100644 --- a/apps/client/src/widgets/dialogs/call_to_action_definitions.ts +++ b/apps/client/src/widgets/dialogs/call_to_action_definitions.ts @@ -1,6 +1,6 @@ -import utils from "../../services/utils"; -import options from "../../services/options"; import { t } from "../../services/i18n"; +import options from "../../services/options"; +import utils from "../../services/utils"; /** * A "call-to-action" is an interactive message for the user, generally to present new features. @@ -46,20 +46,11 @@ function isNextTheme() { const CALL_TO_ACTIONS: CallToAction[] = [ { - id: "next_theme", - title: t("call_to_action.next_theme_title"), - message: t("call_to_action.next_theme_message"), - enabled: () => !isNextTheme(), - buttons: [ - { - text: t("call_to_action.next_theme_button"), - async onClick() { - await options.save("theme", "next"); - await options.save("backgroundEffects", "true"); - utils.reloadFrontendApp("call-to-action"); - } - } - ] + id: "new_layout", + title: t("call_to_action.new_layout_title"), + message: t("call_to_action.new_layout_message"), + enabled: () => true, + buttons: [] }, { id: "background_effects", @@ -75,6 +66,22 @@ const CALL_TO_ACTIONS: CallToAction[] = [ } } ] + }, + { + id: "next_theme", + title: t("call_to_action.next_theme_title"), + message: t("call_to_action.next_theme_message"), + enabled: () => !isNextTheme(), + buttons: [ + { + text: t("call_to_action.next_theme_button"), + async onClick() { + await options.save("theme", "next"); + await options.save("backgroundEffects", "true"); + utils.reloadFrontendApp("call-to-action"); + } + } + ] } ]; From ea4a3b7f079264dac1ba761892b4bd2f2ff10ac7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 17:04:47 +0200 Subject: [PATCH 562/873] chore(client): address requested changes --- apps/client/src/entities/fnote.ts | 20 ++++++++----------- apps/client/src/stylesheets/style.css | 4 ++++ .../src/widgets/buttons/global_menu.tsx | 15 +++++++------- .../src/widgets/dialogs/call_to_action.tsx | 4 +--- .../type_widgets/options/appearance.tsx | 3 ++- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/apps/client/src/entities/fnote.ts b/apps/client/src/entities/fnote.ts index cd255b72a8..cd1a8b7a7a 100644 --- a/apps/client/src/entities/fnote.ts +++ b/apps/client/src/entities/fnote.ts @@ -268,9 +268,8 @@ export default class FNote { } } return results; - } + } return this.children; - } async getSubtreeNoteIds(includeArchived = false) { @@ -471,9 +470,8 @@ export default class FNote { return a.isHidden ? 1 : -1; } else if (a.isSearch !== b.isSearch) { return a.isSearch ? 1 : -1; - } + } return a.notePath.length - b.notePath.length; - }); return notePaths; @@ -597,14 +595,12 @@ export default class FNote { } else if (this.type === "text") { if (this.isFolder()) { return "bx bx-folder"; - } + } return "bx bx-note"; - } else if (this.type === "code" && this.mime.startsWith("text/x-sql")) { return "bx bx-data"; - } + } return NOTE_TYPE_ICONS[this.type]; - } getColorClass() { @@ -811,9 +807,9 @@ export default class FNote { return this.getLabelValue(nameWithPrefix.substring(1)); } else if (nameWithPrefix.startsWith("~")) { return this.getRelationValue(nameWithPrefix.substring(1)); - } + } return this.getLabelValue(nameWithPrefix); - + } /** @@ -878,10 +874,10 @@ export default class FNote { promotedAttrs.sort((a, b) => { if (a.noteId === b.noteId) { return a.position < b.position ? -1 : 1; - } + } // inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761 return a.noteId < b.noteId ? -1 : 1; - + }); return promotedAttrs; diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 24b9275fd5..f10ace6947 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -724,6 +724,10 @@ table.promoted-attributes-in-tooltip th { z-index: 32767 !important; } +.pre-wrap-text { + white-space: pre-wrap; +} + .bs-tooltip-bottom .tooltip-arrow::before { border-bottom-color: var(--main-border-color) !important; } diff --git a/apps/client/src/widgets/buttons/global_menu.tsx b/apps/client/src/widgets/buttons/global_menu.tsx index fa24c8387f..255cf89c9f 100644 --- a/apps/client/src/widgets/buttons/global_menu.tsx +++ b/apps/client/src/widgets/buttons/global_menu.tsx @@ -1,7 +1,7 @@ import "./global_menu.css"; import { KeyboardActionNames } from "@triliumnext/commons"; -import { ComponentChildren } from "preact"; +import { ComponentChildren, RefObject } from "preact"; import { useContext, useEffect, useRef, useState } from "preact/hooks"; import { CommandNames } from "../../components/app_context"; @@ -30,13 +30,15 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: const parentComponent = useContext(ParentComponent); const { isUpdateAvailable, latestVersion } = useTriliumUpdateStatus(); const isMobileLocal = isMobile(); + const logoRef = useRef(null); + useStaticTooltip(logoRef); return ( - {isVerticalLayout && } + {isVerticalLayout && } {isUpdateAvailable && } @@ -135,9 +137,9 @@ function SwitchToOptions() { return; } else if (!isMobile()) { return ; - } + } return ; - + } function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProps void)>) { @@ -159,10 +161,7 @@ function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps; } -function VerticalLayoutIcon() { - const logoRef = useRef(null); - useStaticTooltip(logoRef); - +export function VerticalLayoutIcon({ logoRef }: { logoRef?: RefObject }) { return ( diff --git a/apps/client/src/widgets/dialogs/call_to_action.tsx b/apps/client/src/widgets/dialogs/call_to_action.tsx index 9267384ae7..4f6da5293c 100644 --- a/apps/client/src/widgets/dialogs/call_to_action.tsx +++ b/apps/client/src/widgets/dialogs/call_to_action.tsx @@ -41,9 +41,7 @@ export default function CallToActionDialog() { )} } > -

    {activeItem.message}

    +

    {activeItem.message}

    ); } diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index 1896d1855a..feecb56f57 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -6,6 +6,7 @@ import { useEffect, useState } from "preact/hooks"; import { t } from "../../../services/i18n"; import server from "../../../services/server"; import { isElectron, isMobile, reloadFrontendApp, restartDesktopApp } from "../../../services/utils"; +import { VerticalLayoutIcon } from "../../buttons/global_menu"; import Button from "../../react/Button"; import Column from "../../react/Column"; import FormCheckbox from "../../react/FormCheckbox"; @@ -135,7 +136,7 @@ function LayoutIllustration({ isNewLayout }: { isNewLayout?: boolean }) { return (
    - + From b9c39d757b5c2922bec9c269b5045b60025e35e7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 17:42:03 +0200 Subject: [PATCH 563/873] style(next): match attachment code border radius with context menu --- .../src/stylesheets/theme-next/notes/text.css | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next/notes/text.css b/apps/client/src/stylesheets/theme-next/notes/text.css index 3636cb99d8..deedf02da2 100644 --- a/apps/client/src/stylesheets/theme-next/notes/text.css +++ b/apps/client/src/stylesheets/theme-next/notes/text.css @@ -22,7 +22,7 @@ --ck-color-button-on-background: transparent; --ck-color-button-on-hover-background: var(--hover-item-background-color); --ck-color-button-default-active-background: var(--hover-item-background-color); - + --ck-color-split-button-hover-background: var(--ck-editor-toolbar-dropdown-button-open-background); --ck-focus-ring: 1px solid transparent; @@ -77,7 +77,7 @@ visibility: collapse; } -/* +/* * Dropdowns */ @@ -85,7 +85,7 @@ :root .ck.ck-dropdown__panel, :root .ck-balloon-panel { --ck-editor-popup-padding: 4px; - + --ck-color-panel-background: var(--menu-background-color); --ck-color-panel-border: var(--ck-editor-popup-border-color); @@ -487,7 +487,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck .ck.ck-labeled-field-view > .ck.ck-labeled-field-view__input-wrapper > label.ck.ck-label { /* Move the label above the text box regardless of the text box state */ transform: translate(0, calc(-.2em - var(--ck-input-label-height))) !important; - + padding-inline-start: 0 !important; background: transparent; font-size: .85em; @@ -518,7 +518,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck */ /* - * Code Blocks + * Code Blocks */ .attachment-content-wrapper pre, @@ -526,10 +526,14 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck .ck-mermaid__editing-view { border: 0; border-radius: 6px; - box-shadow: var(--code-block-box-shadow); + box-shadow: var(--code-block-box-shadow); margin-top: 2px !important; } +.attachment-content-wrapper pre { + border-radius: var(--dropdown-border-radius); +} + :root .ck-content pre:has(> code) { padding: 0; } @@ -542,7 +546,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck * for single-line code blocks */ --copy-button-margin-size: calc((1em * 1.5 + var(--padding-size) * 2 - var(--icon-button-size)) / 2); - + /* Where: │ └ Line height * └───────── Font size */ @@ -690,4 +694,4 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child { .note-list-widget { outline: 0 !important; -} \ No newline at end of file +} From c7f1e46b268395fda205376fbf798541d0cf8112 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 18:05:39 +0200 Subject: [PATCH 564/873] e2e(server): disable new layout and call-to-action for now --- apps/server/spec/db/document.db | Bin 1220608 -> 1318912 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/server/spec/db/document.db b/apps/server/spec/db/document.db index 264a9ff0d1edf2d8b2df3eefedec5ba881e48ffe..06ca200470c85630ba673c461deea8f37fef298a 100644 GIT binary patch delta 138767 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 Ll=UegRqOu+`sI(- delta 57906 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 From ecb972c71cbd39018321719b674ec8357d13cd76 Mon Sep 17 00:00:00 2001 From: Jason Wasem <46989741+Soein@users.noreply.github.com> Date: Thu, 18 Dec 2025 00:42:12 +0800 Subject: [PATCH 565/873] fix(search): add null check for canvas elements in fulltext search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加对 Canvas 笔记 elements 字段的空值检查,防止当 elements 为 null 或非数组时搜索功能报错。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../search/expressions/note_content_fulltext.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/server/src/services/search/expressions/note_content_fulltext.ts b/apps/server/src/services/search/expressions/note_content_fulltext.ts index 8a513d99b1..21fd135c94 100644 --- a/apps/server/src/services/search/expressions/note_content_fulltext.ts +++ b/apps/server/src/services/search/expressions/note_content_fulltext.ts @@ -317,11 +317,16 @@ class NoteContentFulltextExp extends Expression { let canvasContent = JSON.parse(content); const elements: Element[] = canvasContent.elements; - const texts = elements - .filter((element: Element) => element.type === "text" && element.text) // Filter for 'text' type elements with a 'text' property - .map((element: Element) => element.text!); // Use `!` to assert `text` is defined after filtering - content = normalize(texts.toString()); + if (elements && Array.isArray(elements)) { + const texts = elements + .filter((element: Element) => element.type === "text" && element.text) // Filter for 'text' type elements with a 'text' property + .map((element: Element) => element.text!); // Use `!` to assert `text` is defined after filtering + + content = normalize(texts.toString()); + } else { + content = ""; + } } return content.trim(); From 3293ed2ce0dcf81c4a1de98bab6a67d378bd638d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 22:32:27 +0200 Subject: [PATCH 566/873] docs(user): document the new layout --- .../doc_notes/en/User Guide/!!!meta.json | 2 +- .../UI Elements/1_New Layout_image.png | Bin 0 -> 21557 bytes .../UI Elements/2_New Layout_image.png | Bin 0 -> 10292 bytes .../UI Elements/3_New Layout_image.png | Bin 0 -> 3835 bytes .../UI Elements/4_New Layout_image.png | Bin 0 -> 7397 bytes .../UI Elements/5_New Layout_image.png | Bin 0 -> 23694 bytes .../UI Elements/New Layout.html | 218 +++++++++++++ .../UI Elements/New Layout/Breadcrumb.html | 55 ++++ .../New Layout/Breadcrumb_image.png | Bin 0 -> 7397 bytes .../UI Elements/New Layout/Status bar.html | 53 ++++ .../UI Elements/New Layout_image.png | Bin 0 -> 10183 bytes .../UI Elements/Note buttons.html | 11 +- .../User Guide/Collections/Kanban Board.html | 162 +++++----- .../Developer Guide/Documentation.md | 2 +- docs/User Guide/!!!meta.json | 294 ++++++++++++++++++ .../UI Elements/1_New Layout_image.png | Bin 0 -> 21557 bytes .../UI Elements/2_New Layout_image.png | Bin 0 -> 10292 bytes .../UI Elements/3_New Layout_image.png | Bin 0 -> 3835 bytes .../UI Elements/4_New Layout_image.png | Bin 0 -> 7397 bytes .../UI Elements/5_New Layout_image.png | Bin 0 -> 23694 bytes .../UI Elements/New Layout.md | 114 +++++++ .../UI Elements/New Layout/Breadcrumb.md | 26 ++ .../New Layout/Breadcrumb_image.png | Bin 0 -> 7397 bytes .../UI Elements/New Layout/Status bar.md | 27 ++ .../UI Elements/New Layout_image.png | Bin 0 -> 10183 bytes .../UI Elements/Note buttons.md | 4 +- 26 files changed, 877 insertions(+), 91 deletions(-) create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/1_New Layout_image.png create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/2_New Layout_image.png create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/3_New Layout_image.png create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/4_New Layout_image.png create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/5_New Layout_image.png create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout.html create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb.html create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb_image.png create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Status bar.html create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout_image.png create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/1_New Layout_image.png create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/2_New Layout_image.png create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/3_New Layout_image.png create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/4_New Layout_image.png create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/5_New Layout_image.png create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout.md create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb.md create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb_image.png create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Status bar.md create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout_image.png diff --git a/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json b/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json index aca2795a2f..e7a752d77f 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json +++ b/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json @@ -1 +1 @@ -[{"id":"_help_BOCnjTMBCoxW","title":"Feature Highlights","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Feature Highlights"},{"name":"iconClass","value":"bx bx-star","type":"label"}]},{"id":"_help_Otzi9La2YAUX","title":"Installation & Setup","type":"book","attributes":[{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_poXkQfguuA0U","title":"Desktop Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation"},{"name":"iconClass","value":"bx bx-desktop","type":"label"}],"children":[{"id":"_help_nRqcgfTb97uV","title":"Using the desktop application as a server","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Using the desktop application "},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_Rp0q8bSP6Ayl","title":"System Requirements","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/System Requirements"},{"name":"iconClass","value":"bx bx-chip","type":"label"}]},{"id":"_help_Un4wj2Mak2Ky","title":"Nix flake","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Nix flake"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]}]},{"id":"_help_WOcw2SLH6tbX","title":"Server Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation"},{"name":"iconClass","value":"bx bx-server","type":"label"}],"children":[{"id":"_help_Dgg7bR3b6K9j","title":"1. Installing the server","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_3tW6mORuTHnB","title":"Packaged version for Linux","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Packaged version for Linux"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_rWX5eY045zbE","title":"Using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker"},{"name":"iconClass","value":"bx bxl-docker","type":"label"}]},{"id":"_help_moVgBcoxE3EK","title":"On NixOS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/On NixOS"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_J1Bb6lVlwU5T","title":"Manually","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Manually"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]},{"id":"_help_DCmT6e7clMoP","title":"Using Kubernetes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Kubernetes"},{"name":"iconClass","value":"bx bxl-kubernetes","type":"label"}]},{"id":"_help_klCWNks3ReaQ","title":"Multiple server instances","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Multiple server instances"},{"name":"iconClass","value":"bx bxs-user-account","type":"label"}]}]},{"id":"_help_vcjrb3VVYPZI","title":"2. Reverse proxy","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_ud6MShXL4WpO","title":"Nginx","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Nginx"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_fDLvzOx29Pfg","title":"Apache using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Apache using Docker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LLzSMXACKhUs","title":"Trusted proxy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Trusted proxy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_5ERVJb9s4FRD","title":"Traefik","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Traefik"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_l2VkvOwUNfZj","title":"HTTPS (TLS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/HTTPS (TLS)"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_0hzsNCP31IAB","title":"Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Authentication"},{"name":"iconClass","value":"bx bx-user","type":"label"}]},{"id":"_help_7DAiwaf8Z7Rz","title":"Multi-Factor Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Multi-Factor Authentication"},{"name":"iconClass","value":"bx bx-stopwatch","type":"label"}]},{"id":"_help_Un4wj2Mak2Ky","title":"Nix flake","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Nix flake.clone"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_yeEaYqosGLSh","title":"Third-party cloud hosting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Third-party cloud hosting"},{"name":"iconClass","value":"bx bx-cloud","type":"label"}]},{"id":"_help_iGTnKjubbXkA","title":"System Requirements","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/System Requirements"},{"name":"iconClass","value":"bx bx-chip","type":"label"}]}]},{"id":"_help_cbkrhQjrkKrh","title":"Synchronization","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Synchronization"},{"name":"iconClass","value":"bx bx-sync","type":"label"}]},{"id":"_help_RDslemsQ6gCp","title":"Mobile Frontend","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Mobile Frontend"},{"name":"iconClass","value":"bx bx-mobile-alt","type":"label"}]},{"id":"_help_MtPxeAWVAzMg","title":"Web Clipper","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Web Clipper"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_n1lujUxCwipy","title":"Upgrading TriliumNext","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Upgrading TriliumNext"},{"name":"iconClass","value":"bx bx-up-arrow-alt","type":"label"}]},{"id":"_help_ODY7qQn5m2FT","title":"Backup","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Backup"},{"name":"iconClass","value":"bx bx-hdd","type":"label"}]},{"id":"_help_tAassRL4RSQL","title":"Data directory","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Data directory"},{"name":"iconClass","value":"bx bx-folder-open","type":"label"}]}]},{"id":"_help_gh7bpGYxajRS","title":"Basic Concepts and Features","type":"book","attributes":[{"name":"iconClass","value":"bx bx-help-circle","type":"label"}],"children":[{"id":"_help_Vc8PjrjAGuOp","title":"UI Elements","type":"book","attributes":[{"name":"iconClass","value":"bx bx-window-alt","type":"label"}],"children":[{"id":"_help_x0JgW8UqGXvq","title":"Vertical and horizontal layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Vertical and horizontal layout"},{"name":"iconClass","value":"bx bxs-layout","type":"label"}]},{"id":"_help_x3i7MxGccDuM","title":"Global menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Global menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_oPVyFC7WL2Lp","title":"Note Tree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree"},{"name":"iconClass","value":"bx bxs-tree-alt","type":"label"}],"children":[{"id":"_help_YtSN43OrfzaA","title":"Note tree contextual menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_yTjUdsOi4CIE","title":"Multiple selection","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Multiple selection"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_DvdZhoQZY9Yd","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]}]},{"id":"_help_BlN9DFI679QC","title":"Ribbon","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon"},{"name":"iconClass","value":"bx bx-dots-horizontal","type":"label"}]},{"id":"_help_3seOhtN8uLIY","title":"Tabs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs"},{"name":"iconClass","value":"bx bx-dock-top","type":"label"}]},{"id":"_help_xYmIYSP6wE3F","title":"Launch Bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Launch Bar"},{"name":"iconClass","value":"bx bx-sidebar","type":"label"}]},{"id":"_help_8YBEPzcpUgxw","title":"Note buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note buttons"},{"name":"iconClass","value":"bx bx-dots-vertical-rounded","type":"label"}]},{"id":"_help_4TIF1oA4VQRO","title":"Options","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Options"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]},{"id":"_help_luNhaphA37EO","title":"Split View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Split View"},{"name":"iconClass","value":"bx bx-dock-right","type":"label"}]},{"id":"_help_XpOYSgsLkTJy","title":"Floating buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Floating buttons"},{"name":"iconClass","value":"bx bx-rectangle","type":"label"}]},{"id":"_help_RnaPdbciOfeq","title":"Right Sidebar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Right Sidebar"},{"name":"iconClass","value":"bx bxs-dock-right","type":"label"}]},{"id":"_help_r5JGHN99bVKn","title":"Recent Changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Recent Changes"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_ny318J39E5Z0","title":"Zoom","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Zoom"},{"name":"iconClass","value":"bx bx-zoom-in","type":"label"}]},{"id":"_help_lgKX7r3aL30x","title":"Note Tooltip","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip"},{"name":"iconClass","value":"bx bx-message-detail","type":"label"}]}]},{"id":"_help_BFs8mudNFgCS","title":"Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes"},{"name":"iconClass","value":"bx bx-notepad","type":"label"}],"children":[{"id":"_help_p9kXRFAkwN4o","title":"Note Icons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Icons"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_0vhv7lsOLy82","title":"Attachments","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Attachments"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_IakOLONlIfGI","title":"Cloning Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes"},{"name":"iconClass","value":"bx bx-duplicate","type":"label"}],"children":[{"id":"_help_TBwsyfadTA18","title":"Branch prefix","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes/Branch prefix"},{"name":"iconClass","value":"bx bx-rename","type":"label"}]}]},{"id":"_help_bwg0e8ewQMak","title":"Protected Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Protected Notes"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_MKmLg5x6xkor","title":"Archived Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Archived Notes"},{"name":"iconClass","value":"bx bx-box","type":"label"}]},{"id":"_help_vZWERwf8U3nx","title":"Note Revisions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Revisions"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_aGlEvb9hyDhS","title":"Sorting Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Sorting Notes"},{"name":"iconClass","value":"bx bx-sort-up","type":"label"}]},{"id":"_help_NRnIZmSMc5sj","title":"Printing & Exporting as PDF","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF"},{"name":"iconClass","value":"bx bx-printer","type":"label"}]},{"id":"_help_CoFPLs3dRlXc","title":"Read-Only Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Read-Only Notes"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_0ESUbbAxVnoK","title":"Note List","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note List"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]}]},{"id":"_help_wArbEsdSae6g","title":"Navigation","type":"book","attributes":[{"name":"iconClass","value":"bx bx-navigation","type":"label"}],"children":[{"id":"_help_kBrnXNG3Hplm","title":"Tree Concepts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Tree Concepts"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}]},{"id":"_help_MMiBEQljMQh2","title":"Note Navigation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Navigation"},{"name":"iconClass","value":"bx bxs-navigation","type":"label"}]},{"id":"_help_Ms1nauBra7gq","title":"Quick search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_F1r9QtzQLZqm","title":"Jump to...","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Jump to"},{"name":"iconClass","value":"bx bx-send","type":"label"}]},{"id":"_help_eIg8jdvaoNNd","title":"Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_u3YFHC9tQlpm","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmarks","type":"label"}]},{"id":"_help_OR8WJ7Iz9K4U","title":"Note Hoisting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Hoisting"},{"name":"iconClass","value":"bx bxs-chevrons-up","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit"},{"name":"iconClass","value":"bx bx-edit","type":"label"}]},{"id":"_help_9sRHySam5fXb","title":"Workspaces","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Workspaces"},{"name":"iconClass","value":"bx bx-door-open","type":"label"}]},{"id":"_help_xWtq5NUHOwql","title":"Similar Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Similar Notes"},{"name":"iconClass","value":"bx bx-bar-chart","type":"label"}]},{"id":"_help_McngOG2jbUWX","title":"Search in note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search in note"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]}]},{"id":"_help_A9Oc6YKKc65v","title":"Keyboard Shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_Wy267RK4M69c","title":"Themes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes"},{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_VbjZvtUek0Ln","title":"Theme Gallery","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Theme Gallery"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_mHbBMPDPkVV5","title":"Import & Export","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export"},{"name":"iconClass","value":"bx bx-import","type":"label"}],"children":[{"id":"_help_Oau6X9rCuegd","title":"Markdown","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}],"children":[{"id":"_help_rJ9grSgoExl9","title":"Supported syntax","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown/Supported syntax"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]}]},{"id":"_help_syuSEKf2rUGr","title":"Evernote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Evernote"},{"name":"iconClass","value":"bx bx-window-open","type":"label"}]},{"id":"_help_GnhlmrATVqcH","title":"OneNote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/OneNote"},{"name":"iconClass","value":"bx bx-window-open","type":"label"}]}]},{"id":"_help_rC3pL2aptaRE","title":"Zen mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Zen mode"},{"name":"iconClass","value":"bx bxs-yin-yang","type":"label"}]}]},{"id":"_help_s3YCWHBfmYuM","title":"Quick Start","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Quick Start"},{"name":"iconClass","value":"bx bx-run","type":"label"}]},{"id":"_help_i6dbnitykE5D","title":"FAQ","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/FAQ"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_KSZ04uQ2D1St","title":"Note Types","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types"},{"name":"iconClass","value":"bx bx-edit","type":"label"}],"children":[{"id":"_help_iPIMuisry3hd","title":"Text","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text"},{"name":"iconClass","value":"bx bx-note","type":"label"}],"children":[{"id":"_help_NwBbFdNZ9h7O","title":"Block quotes & admonitions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Block quotes & admonitions"},{"name":"iconClass","value":"bx bx-info-circle","type":"label"}]},{"id":"_help_oSuaNgyyKnhu","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmark","type":"label"}]},{"id":"_help_veGu4faJErEM","title":"Content language & Right-to-left support","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Content language & Right-to-le"},{"name":"iconClass","value":"bx bx-align-right","type":"label"}]},{"id":"_help_2x0ZAX9ePtzV","title":"Cut to subnote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Cut to subnote"},{"name":"iconClass","value":"bx bx-cut","type":"label"}]},{"id":"_help_UYuUB1ZekNQU","title":"Developer-specific formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_QxEyIjRBizuC","title":"Code blocks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting/Code blocks"},{"name":"iconClass","value":"bx bx-code","type":"label"}]}]},{"id":"_help_AgjCISero73a","title":"Footnotes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Footnotes"},{"name":"iconClass","value":"bx bx-bracket","type":"label"}]},{"id":"_help_nRhnJkTT8cPs","title":"Formatting toolbar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Formatting toolbar"},{"name":"iconClass","value":"bx bx-text","type":"label"}]},{"id":"_help_Gr6xFaF6ioJ5","title":"General formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/General formatting"},{"name":"iconClass","value":"bx bx-bold","type":"label"}]},{"id":"_help_AxshuNRegLAv","title":"Highlights list","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Highlights list"},{"name":"iconClass","value":"bx bx-highlight","type":"label"}]},{"id":"_help_mT0HEkOsz6i1","title":"Images","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images"},{"name":"iconClass","value":"bx bx-image-alt","type":"label"}],"children":[{"id":"_help_0Ofbk1aSuVRu","title":"Image references","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images/Image references"},{"name":"iconClass","value":"bx bxs-file-image","type":"label"}]}]},{"id":"_help_nBAXQFj20hS1","title":"Include Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Include Note"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_CohkqWQC1iBv","title":"Insert buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Insert buttons"},{"name":"iconClass","value":"bx bx-plus","type":"label"}]},{"id":"_help_oiVPnW8QfnvS","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_QEAPj01N5f7w","title":"Links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links"},{"name":"iconClass","value":"bx bx-link-alt","type":"label"}],"children":[{"id":"_help_3IDVtesTQ8ds","title":"External links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/External links"},{"name":"iconClass","value":"bx bx-link-external","type":"label"}]},{"id":"_help_hrZ1D00cLbal","title":"Internal (reference) links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/Internal (reference) links"},{"name":"iconClass","value":"bx bx-link","type":"label"}]}]},{"id":"_help_S6Xx8QIWTV66","title":"Lists","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Lists"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_QrtTYPmdd1qq","title":"Markdown-like formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Markdown-like formatting"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}]},{"id":"_help_YfYAtQBcfo5V","title":"Math Equations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Math Equations"},{"name":"iconClass","value":"bx bx-math","type":"label"}]},{"id":"_help_dEHYtoWWi8ct","title":"Other features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Other features"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_gLt3vA97tMcp","title":"Premium features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features"},{"name":"iconClass","value":"bx bx-star","type":"label"}],"children":[{"id":"_help_ZlN4nump6EbW","title":"Slash Commands","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Slash Commands"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_pwc194wlRzcH","title":"Text Snippets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Text Snippets"},{"name":"iconClass","value":"bx bx-align-left","type":"label"}]},{"id":"_help_5wZallV2Qo1t","title":"Format Painter","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Format Painter"},{"name":"iconClass","value":"bx bxs-paint-roll","type":"label"}]}]},{"id":"_help_BFvAtE74rbP6","title":"Table of contents","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Table of contents"},{"name":"iconClass","value":"bx bx-heading","type":"label"}]},{"id":"_help_NdowYOC1GFKS","title":"Tables","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Tables"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_6f9hih2hXXZk","title":"Code","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Code"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_m523cpzocqaD","title":"Saved Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Saved Search"},{"name":"iconClass","value":"bx bx-file-find","type":"label"}]},{"id":"_help_iRwzGnHPzonm","title":"Relation Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Relation Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_bdUJEHsAPYQR","title":"Note Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Note Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_HcABDtFCkbFN","title":"Render Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Render Note"},{"name":"iconClass","value":"bx bx-extension","type":"label"}]},{"id":"_help_s1aBHPd79XYj","title":"Mermaid Diagrams","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams"},{"name":"iconClass","value":"bx bx-selection","type":"label"}],"children":[{"id":"_help_RH6yLjjWJHof","title":"ELK layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams/ELK layout"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_WWgeUaBb7UfC","title":"Syntax reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://mermaid.js.org/intro/syntax-reference.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_grjYqerjn243","title":"Canvas","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Canvas"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_1vHRoWCEjj0L","title":"Web View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Web View"},{"name":"iconClass","value":"bx bx-globe-alt","type":"label"}]},{"id":"_help_gBbsAeiuUxI5","title":"Mind Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mind Map"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_W8vYD3Q1zjCR","title":"File","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File"},{"name":"iconClass","value":"bx bx-file-blank","type":"label"}]}]},{"id":"_help_GTwFsgaA0lCt","title":"Collections","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections"},{"name":"iconClass","value":"bx bx-book","type":"label"}],"children":[{"id":"_help_xWbu3jpNWapp","title":"Calendar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Calendar"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_2FvYrpmOXm29","title":"Table","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Table"},{"name":"iconClass","value":"bx bx-table","type":"label"}]},{"id":"_help_CtBQqbwXDx1w","title":"Kanban Board","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Kanban Board"},{"name":"iconClass","value":"bx bx-columns","type":"label"}]},{"id":"_help_81SGnPGMk7Xc","title":"Geo Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Geo Map"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]},{"id":"_help_zP3PMqaG71Ct","title":"Presentation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Presentation"},{"name":"iconClass","value":"bx bx-slideshow","type":"label"}]},{"id":"_help_8QqnMzx393bx","title":"Grid View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Grid View"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_mULW0Q3VojwY","title":"List View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/List View"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]}]},{"id":"_help_BgmBlOIl72jZ","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting"},{"name":"iconClass","value":"bx bx-bug","type":"label"}],"children":[{"id":"_help_wy8So3yZZlH9","title":"Reporting issues","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Reporting issues"},{"name":"iconClass","value":"bx bx-bug-alt","type":"label"}]},{"id":"_help_x59R8J8KV5Bp","title":"Anonymized Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Anonymized Database"},{"name":"iconClass","value":"bx bx-low-vision","type":"label"}]},{"id":"_help_qzNzp9LYQyPT","title":"Error logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs"},{"name":"iconClass","value":"bx bx-comment-error","type":"label"}],"children":[{"id":"_help_bnyigUA2UK7s","title":"Backend (server) logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Backend (server) logs"},{"name":"iconClass","value":"bx bx-server","type":"label"}]},{"id":"_help_9yEHzMyFirZR","title":"Frontend logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Frontend logs"},{"name":"iconClass","value":"bx bx-window-alt","type":"label"}]}]},{"id":"_help_vdlYGAcpXAgc","title":"Synchronization fails with 504 Gateway Timeout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Synchronization fails with 504"},{"name":"iconClass","value":"bx bx-error","type":"label"}]},{"id":"_help_s8alTXmpFR61","title":"Refreshing the application","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Refreshing the application"},{"name":"iconClass","value":"bx bx-refresh","type":"label"}]}]},{"id":"_help_pKK96zzmvBGf","title":"Theme development","type":"book","attributes":[{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_7NfNr5pZpVKV","title":"Creating a custom theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating a custom theme"},{"name":"iconClass","value":"bx bxs-color","type":"label"}]},{"id":"_help_WFGzWeUK6arS","title":"Customize the Next theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Customize the Next theme"},{"name":"iconClass","value":"bx bx-news","type":"label"}]},{"id":"_help_WN5z4M8ASACJ","title":"Reference","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Reference"},{"name":"iconClass","value":"bx bx-book-open","type":"label"}]},{"id":"_help_AlhDUqhENtH7","title":"Custom app-wide CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Custom app-wide CSS"},{"name":"iconClass","value":"bx bxs-file-css","type":"label"}]}]},{"id":"_help_tC7s2alapj8V","title":"Advanced Usage","type":"book","attributes":[{"name":"iconClass","value":"bx bx-rocket","type":"label"}],"children":[{"id":"_help_zEY4DaJG4YT5","title":"Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes"},{"name":"iconClass","value":"bx bx-list-check","type":"label"}],"children":[{"id":"_help_HI6GBBIduIgv","title":"Labels","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Labels"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_Cq5X6iKQop6R","title":"Relations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Relations"},{"name":"iconClass","value":"bx bx-transfer","type":"label"}]},{"id":"_help_bwZpz2ajCEwO","title":"Attribute Inheritance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Attribute Inheritance"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_OFXdgB2nNk1F","title":"Promoted Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_KC1HB96bqqHX","title":"Templates","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Templates"},{"name":"iconClass","value":"bx bx-copy","type":"label"}]},{"id":"_help_BCkXAVs63Ttv","title":"Note Map (Link map, Tree map)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note Map (Link map, Tree map)"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_R9pX4DGra2Vt","title":"Sharing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing"},{"name":"iconClass","value":"bx bx-share-alt","type":"label"}],"children":[{"id":"_help_Qjt68inQ2bRj","title":"Serving directly the content of a note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Serving directly the content o"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_ycBFjKrrwE9p","title":"Exporting static HTML for web publishing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Exporting static HTML for web "},{"name":"iconClass","value":"bx bxs-file-html","type":"label"}]},{"id":"_help_sLIJ6f1dkJYW","title":"Reverse proxy configuration","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Reverse proxy configuration"},{"name":"iconClass","value":"bx bx-world","type":"label"}]}]},{"id":"_help_5668rwcirq1t","title":"Advanced Showcases","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_l0tKav7yLHGF","title":"Day Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Day Notes"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_R7abl2fc6Mxi","title":"Weight Tracker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Weight Tracker"},{"name":"iconClass","value":"bx bx-line-chart","type":"label"}]},{"id":"_help_xYjQUYhpbUEW","title":"Task Manager","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Task Manager"},{"name":"iconClass","value":"bx bx-calendar-check","type":"label"}]}]},{"id":"_help_J5Ex1ZrMbyJ6","title":"Custom Request Handler","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Request Handler"},{"name":"iconClass","value":"bx bx-globe","type":"label"}]},{"id":"_help_d3fAXQ2diepH","title":"Custom Resource Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Resource Providers"},{"name":"iconClass","value":"bx bxs-file-plus","type":"label"}]},{"id":"_help_pgxEVkzLl1OP","title":"ETAPI (REST API)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/ETAPI (REST API)"},{"name":"iconClass","value":"bx bx-extension","type":"label"}],"children":[{"id":"_help_9qPsTWBorUhQ","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/rest-api/etapi/"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_47ZrP6FNuoG8","title":"Default Note Title","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Default Note Title"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_wX4HbRucYSDD","title":"Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database"},{"name":"iconClass","value":"bx bx-data","type":"label"}],"children":[{"id":"_help_oyIAJ9PvvwHX","title":"Manually altering the database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database"},{"name":"iconClass","value":"bx bxs-edit","type":"label"}],"children":[{"id":"_help_YKWqdJhzi2VY","title":"SQL Console","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database/SQL Console"},{"name":"iconClass","value":"bx bx-data","type":"label"}]}]},{"id":"_help_6tZeKvSHEUiB","title":"Demo Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Demo Notes"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_Gzjqa934BdH4","title":"Configuration (config.ini or environment variables)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or e"},{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_c5xB8m4g2IY6","title":"Trilium instance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Trilium instance"},{"name":"iconClass","value":"bx bx-windows","type":"label"}]},{"id":"_help_LWtBjFej3wX3","title":"Cross-Origin Resource Sharing (CORS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Cross-Origin Resource Sharing "},{"name":"iconClass","value":"bx bx-lock","type":"label"}]}]},{"id":"_help_ivYnonVFBxbQ","title":"Bulk Actions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Bulk Actions"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_4FahAwuGTAwC","title":"Note source","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note source"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_1YeN2MzFUluU","title":"Technologies used","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}],"children":[{"id":"_help_MI26XDLSAlCD","title":"CKEditor","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/CKEditor"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_N4IDkixaDG9C","title":"MindElixir","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/MindElixir"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_H0mM1lTxF9JI","title":"Excalidraw","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Excalidraw"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_MQHyy2dIFgxS","title":"Leaflet","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Leaflet"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]}]},{"id":"_help_m1lbrzyKDaRB","title":"Note ID","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note ID"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_0vTSyvhPTAOz","title":"Internal API","type":"book","attributes":[{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_z8O2VG4ZZJD7","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/rest-api/internal/"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_2mUhVmZK8RF3","title":"Hidden Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Hidden Notes"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]},{"id":"_help_uYF7pmepw27K","title":"Metrics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Metrics"},{"name":"iconClass","value":"bx bxs-data","type":"label"}],"children":[{"id":"_help_bOP3TB56fL1V","title":"grafana-dashboard.json","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_64ZTlUPgEPtW","title":"Safe mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Safe mode"},{"name":"iconClass","value":"bx bxs-virus-block","type":"label"}]},{"id":"_help_HAIOFBoYIIdO","title":"Nightly release","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Nightly release"},{"name":"iconClass","value":"bx bx-moon","type":"label"}]},{"id":"_help_ZmT9ln8XJX2o","title":"Read-only database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Read-only database"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_GBBMSlVSOIGP","title":"AI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI"},{"name":"iconClass","value":"bx bx-bot","type":"label"}],"children":[{"id":"_help_WkM7gsEUyCXs","title":"Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Providers"},{"name":"iconClass","value":"bx bx-select-multiple","type":"label"}],"children":[{"id":"_help_7EdTxPADv95W","title":"Ollama","type":"book","attributes":[{"name":"iconClass","value":"bx bx-message-dots","type":"label"}],"children":[{"id":"_help_vvUCN7FDkq7G","title":"Installing Ollama","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Providers/Ollama/Installing Ollama"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ZavFigBX9AwP","title":"OpenAI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Providers/OpenAI"},{"name":"iconClass","value":"bx bx-message-dots","type":"label"}]},{"id":"_help_e0lkirXEiSNc","title":"Anthropic","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Providers/Anthropic"},{"name":"iconClass","value":"bx bx-message-dots","type":"label"}]}]}]},{"id":"_help_CdNpE2pqjmI6","title":"Scripting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting"},{"name":"iconClass","value":"bx bxs-file-js","type":"label"}],"children":[{"id":"_help_yIhgI5H7A2Sm","title":"Frontend Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics"},{"name":"iconClass","value":"bx bx-window","type":"label"}],"children":[{"id":"_help_MgibgPcfeuGz","title":"Custom Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets"},{"name":"iconClass","value":"bx bxs-widget","type":"label"}],"children":[{"id":"_help_YNxAqkI5Kg1M","title":"Word count widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Word count widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_SynTBQiBsdYJ","title":"Widget Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Widget Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_M8IppdwVHSjG","title":"Right pane widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Right pane widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_VqGQnnPGnqAU","title":"CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/CSS"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_es8OU2GuguFU","title":"Examples","type":"book","attributes":[{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_TjLYAo3JMO8X","title":"\"New Task\" launcher button","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/New Task launcher button"},{"name":"iconClass","value":"bx bx-task","type":"label"}]},{"id":"_help_7kZPMD0uFwkH","title":"Downloading responses from Google Forms","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/Downloading responses from Goo"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_DL92EjAaXT26","title":"Using promoted attributes to configure scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/Using promoted attributes to c"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_4Gn3psZKsfSm","title":"Launch Bar Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets"},{"name":"iconClass","value":"bx bx-dock-left","type":"label"}],"children":[{"id":"_help_IPArqVfDQ4We","title":"Note Title Widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets/Note Title Widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_gcI7RPbaNSh3","title":"Analog Watch","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets/Analog Watch"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_SPirpZypehBG","title":"Backend scripts","type":"book","attributes":[{"name":"iconClass","value":"bx bx-server","type":"label"}],"children":[{"id":"_help_fZ2IGYFXjkEy","title":"Server-side imports","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts/Server-side imports"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GPERMystNGTB","title":"Events","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts/Events"},{"name":"iconClass","value":"bx bx-rss","type":"label"}]}]},{"id":"_help_GLks18SNjxmC","title":"Script API","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API"},{"name":"iconClass","value":"bx bx-code-curly","type":"label"}],"children":[{"id":"_help_Q2z6av6JZVWm","title":"Frontend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/frontend"},{"name":"iconClass","value":"bx bx-folder","type":"label"}],"enforceAttributes":true,"children":[{"id":"_help_habiZ3HU8Kw8","title":"FNote","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/frontend/interfaces/FNote.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_MEtfsqa5VwNi","title":"Backend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/backend"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true},{"id":"_help_ApVHZ8JY5ofC","title":"Day.js","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API/Day.js"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]}]},{"id":"_help_vElnKeDNPSVl","title":"Logging","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Logging"},{"name":"iconClass","value":"bx bx-terminal","type":"label"}]}]},{"id":"_help_Fm0j45KqyHpU","title":"Miscellaneous","type":"book","attributes":[{"name":"iconClass","value":"bx bx-info-circle","type":"label"}],"children":[{"id":"_help_WFbFXrgnDyyU","title":"Privacy Policy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Miscellaneous/Privacy Policy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_NcsmUYZRWEW4","title":"Patterns of personal knowledge","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Miscellaneous/Patterns of personal knowledge"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}] \ No newline at end of file +[{"id":"_help_BOCnjTMBCoxW","title":"Feature Highlights","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Feature Highlights"},{"name":"iconClass","value":"bx bx-star","type":"label"}]},{"id":"_help_Otzi9La2YAUX","title":"Installation & Setup","type":"book","attributes":[{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_poXkQfguuA0U","title":"Desktop Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation"},{"name":"iconClass","value":"bx bx-desktop","type":"label"}],"children":[{"id":"_help_nRqcgfTb97uV","title":"Using the desktop application as a server","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Using the desktop application "},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_Rp0q8bSP6Ayl","title":"System Requirements","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/System Requirements"},{"name":"iconClass","value":"bx bx-chip","type":"label"}]},{"id":"_help_Un4wj2Mak2Ky","title":"Nix flake","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Nix flake"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]}]},{"id":"_help_WOcw2SLH6tbX","title":"Server Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation"},{"name":"iconClass","value":"bx bx-server","type":"label"}],"children":[{"id":"_help_Dgg7bR3b6K9j","title":"1. Installing the server","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_3tW6mORuTHnB","title":"Packaged version for Linux","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Packaged version for Linux"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_rWX5eY045zbE","title":"Using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker"},{"name":"iconClass","value":"bx bxl-docker","type":"label"}]},{"id":"_help_moVgBcoxE3EK","title":"On NixOS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/On NixOS"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_J1Bb6lVlwU5T","title":"Manually","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Manually"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]},{"id":"_help_DCmT6e7clMoP","title":"Using Kubernetes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Kubernetes"},{"name":"iconClass","value":"bx bxl-kubernetes","type":"label"}]},{"id":"_help_klCWNks3ReaQ","title":"Multiple server instances","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Multiple server instances"},{"name":"iconClass","value":"bx bxs-user-account","type":"label"}]}]},{"id":"_help_vcjrb3VVYPZI","title":"2. Reverse proxy","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_ud6MShXL4WpO","title":"Nginx","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Nginx"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_fDLvzOx29Pfg","title":"Apache using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Apache using Docker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LLzSMXACKhUs","title":"Trusted proxy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Trusted proxy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_5ERVJb9s4FRD","title":"Traefik","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Traefik"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_l2VkvOwUNfZj","title":"HTTPS (TLS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/HTTPS (TLS)"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_0hzsNCP31IAB","title":"Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Authentication"},{"name":"iconClass","value":"bx bx-user","type":"label"}]},{"id":"_help_7DAiwaf8Z7Rz","title":"Multi-Factor Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Multi-Factor Authentication"},{"name":"iconClass","value":"bx bx-stopwatch","type":"label"}]},{"id":"_help_Un4wj2Mak2Ky","title":"Nix flake","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Nix flake.clone"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_yeEaYqosGLSh","title":"Third-party cloud hosting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Third-party cloud hosting"},{"name":"iconClass","value":"bx bx-cloud","type":"label"}]},{"id":"_help_iGTnKjubbXkA","title":"System Requirements","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/System Requirements"},{"name":"iconClass","value":"bx bx-chip","type":"label"}]}]},{"id":"_help_cbkrhQjrkKrh","title":"Synchronization","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Synchronization"},{"name":"iconClass","value":"bx bx-sync","type":"label"}]},{"id":"_help_RDslemsQ6gCp","title":"Mobile Frontend","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Mobile Frontend"},{"name":"iconClass","value":"bx bx-mobile-alt","type":"label"}]},{"id":"_help_MtPxeAWVAzMg","title":"Web Clipper","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Web Clipper"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_n1lujUxCwipy","title":"Upgrading TriliumNext","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Upgrading TriliumNext"},{"name":"iconClass","value":"bx bx-up-arrow-alt","type":"label"}]},{"id":"_help_ODY7qQn5m2FT","title":"Backup","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Backup"},{"name":"iconClass","value":"bx bx-hdd","type":"label"}]},{"id":"_help_tAassRL4RSQL","title":"Data directory","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Data directory"},{"name":"iconClass","value":"bx bx-folder-open","type":"label"}]}]},{"id":"_help_gh7bpGYxajRS","title":"Basic Concepts and Features","type":"book","attributes":[{"name":"iconClass","value":"bx bx-help-circle","type":"label"}],"children":[{"id":"_help_Vc8PjrjAGuOp","title":"UI Elements","type":"book","attributes":[{"name":"iconClass","value":"bx bx-window-alt","type":"label"}],"children":[{"id":"_help_x0JgW8UqGXvq","title":"Vertical and horizontal layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Vertical and horizontal layout"},{"name":"iconClass","value":"bx bxs-layout","type":"label"}]},{"id":"_help_x3i7MxGccDuM","title":"Global menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Global menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_oPVyFC7WL2Lp","title":"Note Tree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree"},{"name":"iconClass","value":"bx bxs-tree-alt","type":"label"}],"children":[{"id":"_help_YtSN43OrfzaA","title":"Note tree contextual menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_yTjUdsOi4CIE","title":"Multiple selection","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Multiple selection"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_DvdZhoQZY9Yd","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]}]},{"id":"_help_BlN9DFI679QC","title":"Ribbon","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon"},{"name":"iconClass","value":"bx bx-dots-horizontal","type":"label"}]},{"id":"_help_3seOhtN8uLIY","title":"Tabs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs"},{"name":"iconClass","value":"bx bx-dock-top","type":"label"}]},{"id":"_help_xYmIYSP6wE3F","title":"Launch Bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Launch Bar"},{"name":"iconClass","value":"bx bx-sidebar","type":"label"}]},{"id":"_help_8YBEPzcpUgxw","title":"Note buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note buttons"},{"name":"iconClass","value":"bx bx-dots-vertical-rounded","type":"label"}]},{"id":"_help_4TIF1oA4VQRO","title":"Options","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Options"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]},{"id":"_help_luNhaphA37EO","title":"Split View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Split View"},{"name":"iconClass","value":"bx bx-dock-right","type":"label"}]},{"id":"_help_XpOYSgsLkTJy","title":"Floating buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Floating buttons"},{"name":"iconClass","value":"bx bx-rectangle","type":"label"}]},{"id":"_help_RnaPdbciOfeq","title":"Right Sidebar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Right Sidebar"},{"name":"iconClass","value":"bx bxs-dock-right","type":"label"}]},{"id":"_help_r5JGHN99bVKn","title":"Recent Changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Recent Changes"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_ny318J39E5Z0","title":"Zoom","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Zoom"},{"name":"iconClass","value":"bx bx-zoom-in","type":"label"}]},{"id":"_help_lgKX7r3aL30x","title":"Note Tooltip","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip"},{"name":"iconClass","value":"bx bx-message-detail","type":"label"}]},{"id":"_help_IjZS7iK5EXtb","title":"New Layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout"},{"name":"iconClass","value":"bx bx-layout","type":"label"}],"children":[{"id":"_help_I6p2a06hdnL6","title":"Breadcrumb","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb"},{"name":"iconClass","value":"bx bx-chevron-right","type":"label"}]},{"id":"_help_AlJ73vBCjWDw","title":"Status bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Status bar"},{"name":"iconClass","value":"bx bx-dock-bottom","type":"label"}]}]}]},{"id":"_help_BFs8mudNFgCS","title":"Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes"},{"name":"iconClass","value":"bx bx-notepad","type":"label"}],"children":[{"id":"_help_p9kXRFAkwN4o","title":"Note Icons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Icons"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_0vhv7lsOLy82","title":"Attachments","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Attachments"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_IakOLONlIfGI","title":"Cloning Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes"},{"name":"iconClass","value":"bx bx-duplicate","type":"label"}],"children":[{"id":"_help_TBwsyfadTA18","title":"Branch prefix","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes/Branch prefix"},{"name":"iconClass","value":"bx bx-rename","type":"label"}]}]},{"id":"_help_bwg0e8ewQMak","title":"Protected Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Protected Notes"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_MKmLg5x6xkor","title":"Archived Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Archived Notes"},{"name":"iconClass","value":"bx bx-box","type":"label"}]},{"id":"_help_vZWERwf8U3nx","title":"Note Revisions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Revisions"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_aGlEvb9hyDhS","title":"Sorting Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Sorting Notes"},{"name":"iconClass","value":"bx bx-sort-up","type":"label"}]},{"id":"_help_NRnIZmSMc5sj","title":"Printing & Exporting as PDF","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF"},{"name":"iconClass","value":"bx bx-printer","type":"label"}]},{"id":"_help_CoFPLs3dRlXc","title":"Read-Only Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Read-Only Notes"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_0ESUbbAxVnoK","title":"Note List","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note List"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]}]},{"id":"_help_wArbEsdSae6g","title":"Navigation","type":"book","attributes":[{"name":"iconClass","value":"bx bx-navigation","type":"label"}],"children":[{"id":"_help_kBrnXNG3Hplm","title":"Tree Concepts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Tree Concepts"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}]},{"id":"_help_MMiBEQljMQh2","title":"Note Navigation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Navigation"},{"name":"iconClass","value":"bx bxs-navigation","type":"label"}]},{"id":"_help_Ms1nauBra7gq","title":"Quick search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_F1r9QtzQLZqm","title":"Jump to...","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Jump to"},{"name":"iconClass","value":"bx bx-send","type":"label"}]},{"id":"_help_eIg8jdvaoNNd","title":"Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_u3YFHC9tQlpm","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmarks","type":"label"}]},{"id":"_help_OR8WJ7Iz9K4U","title":"Note Hoisting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Hoisting"},{"name":"iconClass","value":"bx bxs-chevrons-up","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit"},{"name":"iconClass","value":"bx bx-edit","type":"label"}]},{"id":"_help_9sRHySam5fXb","title":"Workspaces","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Workspaces"},{"name":"iconClass","value":"bx bx-door-open","type":"label"}]},{"id":"_help_xWtq5NUHOwql","title":"Similar Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Similar Notes"},{"name":"iconClass","value":"bx bx-bar-chart","type":"label"}]},{"id":"_help_McngOG2jbUWX","title":"Search in note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search in note"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]}]},{"id":"_help_A9Oc6YKKc65v","title":"Keyboard Shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_Wy267RK4M69c","title":"Themes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes"},{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_VbjZvtUek0Ln","title":"Theme Gallery","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Theme Gallery"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_mHbBMPDPkVV5","title":"Import & Export","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export"},{"name":"iconClass","value":"bx bx-import","type":"label"}],"children":[{"id":"_help_Oau6X9rCuegd","title":"Markdown","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}],"children":[{"id":"_help_rJ9grSgoExl9","title":"Supported syntax","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown/Supported syntax"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]}]},{"id":"_help_syuSEKf2rUGr","title":"Evernote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Evernote"},{"name":"iconClass","value":"bx bx-window-open","type":"label"}]},{"id":"_help_GnhlmrATVqcH","title":"OneNote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/OneNote"},{"name":"iconClass","value":"bx bx-window-open","type":"label"}]}]},{"id":"_help_rC3pL2aptaRE","title":"Zen mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Zen mode"},{"name":"iconClass","value":"bx bxs-yin-yang","type":"label"}]}]},{"id":"_help_s3YCWHBfmYuM","title":"Quick Start","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Quick Start"},{"name":"iconClass","value":"bx bx-run","type":"label"}]},{"id":"_help_i6dbnitykE5D","title":"FAQ","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/FAQ"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_KSZ04uQ2D1St","title":"Note Types","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types"},{"name":"iconClass","value":"bx bx-edit","type":"label"}],"children":[{"id":"_help_iPIMuisry3hd","title":"Text","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text"},{"name":"iconClass","value":"bx bx-note","type":"label"}],"children":[{"id":"_help_NwBbFdNZ9h7O","title":"Block quotes & admonitions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Block quotes & admonitions"},{"name":"iconClass","value":"bx bx-info-circle","type":"label"}]},{"id":"_help_oSuaNgyyKnhu","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmark","type":"label"}]},{"id":"_help_veGu4faJErEM","title":"Content language & Right-to-left support","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Content language & Right-to-le"},{"name":"iconClass","value":"bx bx-align-right","type":"label"}]},{"id":"_help_2x0ZAX9ePtzV","title":"Cut to subnote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Cut to subnote"},{"name":"iconClass","value":"bx bx-cut","type":"label"}]},{"id":"_help_UYuUB1ZekNQU","title":"Developer-specific formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_QxEyIjRBizuC","title":"Code blocks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting/Code blocks"},{"name":"iconClass","value":"bx bx-code","type":"label"}]}]},{"id":"_help_AgjCISero73a","title":"Footnotes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Footnotes"},{"name":"iconClass","value":"bx bx-bracket","type":"label"}]},{"id":"_help_nRhnJkTT8cPs","title":"Formatting toolbar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Formatting toolbar"},{"name":"iconClass","value":"bx bx-text","type":"label"}]},{"id":"_help_Gr6xFaF6ioJ5","title":"General formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/General formatting"},{"name":"iconClass","value":"bx bx-bold","type":"label"}]},{"id":"_help_AxshuNRegLAv","title":"Highlights list","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Highlights list"},{"name":"iconClass","value":"bx bx-highlight","type":"label"}]},{"id":"_help_mT0HEkOsz6i1","title":"Images","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images"},{"name":"iconClass","value":"bx bx-image-alt","type":"label"}],"children":[{"id":"_help_0Ofbk1aSuVRu","title":"Image references","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images/Image references"},{"name":"iconClass","value":"bx bxs-file-image","type":"label"}]}]},{"id":"_help_nBAXQFj20hS1","title":"Include Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Include Note"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_CohkqWQC1iBv","title":"Insert buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Insert buttons"},{"name":"iconClass","value":"bx bx-plus","type":"label"}]},{"id":"_help_oiVPnW8QfnvS","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_QEAPj01N5f7w","title":"Links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links"},{"name":"iconClass","value":"bx bx-link-alt","type":"label"}],"children":[{"id":"_help_3IDVtesTQ8ds","title":"External links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/External links"},{"name":"iconClass","value":"bx bx-link-external","type":"label"}]},{"id":"_help_hrZ1D00cLbal","title":"Internal (reference) links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/Internal (reference) links"},{"name":"iconClass","value":"bx bx-link","type":"label"}]}]},{"id":"_help_S6Xx8QIWTV66","title":"Lists","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Lists"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_QrtTYPmdd1qq","title":"Markdown-like formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Markdown-like formatting"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}]},{"id":"_help_YfYAtQBcfo5V","title":"Math Equations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Math Equations"},{"name":"iconClass","value":"bx bx-math","type":"label"}]},{"id":"_help_dEHYtoWWi8ct","title":"Other features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Other features"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_gLt3vA97tMcp","title":"Premium features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features"},{"name":"iconClass","value":"bx bx-star","type":"label"}],"children":[{"id":"_help_ZlN4nump6EbW","title":"Slash Commands","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Slash Commands"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_pwc194wlRzcH","title":"Text Snippets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Text Snippets"},{"name":"iconClass","value":"bx bx-align-left","type":"label"}]},{"id":"_help_5wZallV2Qo1t","title":"Format Painter","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Format Painter"},{"name":"iconClass","value":"bx bxs-paint-roll","type":"label"}]}]},{"id":"_help_BFvAtE74rbP6","title":"Table of contents","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Table of contents"},{"name":"iconClass","value":"bx bx-heading","type":"label"}]},{"id":"_help_NdowYOC1GFKS","title":"Tables","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Tables"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_6f9hih2hXXZk","title":"Code","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Code"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_m523cpzocqaD","title":"Saved Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Saved Search"},{"name":"iconClass","value":"bx bx-file-find","type":"label"}]},{"id":"_help_iRwzGnHPzonm","title":"Relation Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Relation Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_bdUJEHsAPYQR","title":"Note Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Note Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_HcABDtFCkbFN","title":"Render Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Render Note"},{"name":"iconClass","value":"bx bx-extension","type":"label"}]},{"id":"_help_s1aBHPd79XYj","title":"Mermaid Diagrams","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams"},{"name":"iconClass","value":"bx bx-selection","type":"label"}],"children":[{"id":"_help_RH6yLjjWJHof","title":"ELK layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams/ELK layout"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_WWgeUaBb7UfC","title":"Syntax reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://mermaid.js.org/intro/syntax-reference.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_grjYqerjn243","title":"Canvas","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Canvas"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_1vHRoWCEjj0L","title":"Web View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Web View"},{"name":"iconClass","value":"bx bx-globe-alt","type":"label"}]},{"id":"_help_gBbsAeiuUxI5","title":"Mind Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mind Map"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_W8vYD3Q1zjCR","title":"File","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File"},{"name":"iconClass","value":"bx bx-file-blank","type":"label"}]}]},{"id":"_help_GTwFsgaA0lCt","title":"Collections","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections"},{"name":"iconClass","value":"bx bx-book","type":"label"}],"children":[{"id":"_help_xWbu3jpNWapp","title":"Calendar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Calendar"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_2FvYrpmOXm29","title":"Table","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Table"},{"name":"iconClass","value":"bx bx-table","type":"label"}]},{"id":"_help_CtBQqbwXDx1w","title":"Kanban Board","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Kanban Board"},{"name":"iconClass","value":"bx bx-columns","type":"label"}]},{"id":"_help_81SGnPGMk7Xc","title":"Geo Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Geo Map"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]},{"id":"_help_zP3PMqaG71Ct","title":"Presentation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Presentation"},{"name":"iconClass","value":"bx bx-slideshow","type":"label"}]},{"id":"_help_8QqnMzx393bx","title":"Grid View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Grid View"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_mULW0Q3VojwY","title":"List View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/List View"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]}]},{"id":"_help_BgmBlOIl72jZ","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting"},{"name":"iconClass","value":"bx bx-bug","type":"label"}],"children":[{"id":"_help_wy8So3yZZlH9","title":"Reporting issues","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Reporting issues"},{"name":"iconClass","value":"bx bx-bug-alt","type":"label"}]},{"id":"_help_x59R8J8KV5Bp","title":"Anonymized Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Anonymized Database"},{"name":"iconClass","value":"bx bx-low-vision","type":"label"}]},{"id":"_help_qzNzp9LYQyPT","title":"Error logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs"},{"name":"iconClass","value":"bx bx-comment-error","type":"label"}],"children":[{"id":"_help_bnyigUA2UK7s","title":"Backend (server) logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Backend (server) logs"},{"name":"iconClass","value":"bx bx-server","type":"label"}]},{"id":"_help_9yEHzMyFirZR","title":"Frontend logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Frontend logs"},{"name":"iconClass","value":"bx bx-window-alt","type":"label"}]}]},{"id":"_help_vdlYGAcpXAgc","title":"Synchronization fails with 504 Gateway Timeout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Synchronization fails with 504"},{"name":"iconClass","value":"bx bx-error","type":"label"}]},{"id":"_help_s8alTXmpFR61","title":"Refreshing the application","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Refreshing the application"},{"name":"iconClass","value":"bx bx-refresh","type":"label"}]}]},{"id":"_help_pKK96zzmvBGf","title":"Theme development","type":"book","attributes":[{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_7NfNr5pZpVKV","title":"Creating a custom theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating a custom theme"},{"name":"iconClass","value":"bx bxs-color","type":"label"}]},{"id":"_help_WFGzWeUK6arS","title":"Customize the Next theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Customize the Next theme"},{"name":"iconClass","value":"bx bx-news","type":"label"}]},{"id":"_help_WN5z4M8ASACJ","title":"Reference","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Reference"},{"name":"iconClass","value":"bx bx-book-open","type":"label"}]},{"id":"_help_AlhDUqhENtH7","title":"Custom app-wide CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Custom app-wide CSS"},{"name":"iconClass","value":"bx bxs-file-css","type":"label"}]}]},{"id":"_help_tC7s2alapj8V","title":"Advanced Usage","type":"book","attributes":[{"name":"iconClass","value":"bx bx-rocket","type":"label"}],"children":[{"id":"_help_zEY4DaJG4YT5","title":"Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes"},{"name":"iconClass","value":"bx bx-list-check","type":"label"}],"children":[{"id":"_help_HI6GBBIduIgv","title":"Labels","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Labels"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_Cq5X6iKQop6R","title":"Relations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Relations"},{"name":"iconClass","value":"bx bx-transfer","type":"label"}]},{"id":"_help_bwZpz2ajCEwO","title":"Attribute Inheritance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Attribute Inheritance"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_OFXdgB2nNk1F","title":"Promoted Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_KC1HB96bqqHX","title":"Templates","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Templates"},{"name":"iconClass","value":"bx bx-copy","type":"label"}]},{"id":"_help_BCkXAVs63Ttv","title":"Note Map (Link map, Tree map)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note Map (Link map, Tree map)"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_R9pX4DGra2Vt","title":"Sharing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing"},{"name":"iconClass","value":"bx bx-share-alt","type":"label"}],"children":[{"id":"_help_Qjt68inQ2bRj","title":"Serving directly the content of a note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Serving directly the content o"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_ycBFjKrrwE9p","title":"Exporting static HTML for web publishing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Exporting static HTML for web "},{"name":"iconClass","value":"bx bxs-file-html","type":"label"}]},{"id":"_help_sLIJ6f1dkJYW","title":"Reverse proxy configuration","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Reverse proxy configuration"},{"name":"iconClass","value":"bx bx-world","type":"label"}]}]},{"id":"_help_5668rwcirq1t","title":"Advanced Showcases","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_l0tKav7yLHGF","title":"Day Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Day Notes"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_R7abl2fc6Mxi","title":"Weight Tracker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Weight Tracker"},{"name":"iconClass","value":"bx bx-line-chart","type":"label"}]},{"id":"_help_xYjQUYhpbUEW","title":"Task Manager","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Task Manager"},{"name":"iconClass","value":"bx bx-calendar-check","type":"label"}]}]},{"id":"_help_J5Ex1ZrMbyJ6","title":"Custom Request Handler","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Request Handler"},{"name":"iconClass","value":"bx bx-globe","type":"label"}]},{"id":"_help_d3fAXQ2diepH","title":"Custom Resource Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Resource Providers"},{"name":"iconClass","value":"bx bxs-file-plus","type":"label"}]},{"id":"_help_pgxEVkzLl1OP","title":"ETAPI (REST API)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/ETAPI (REST API)"},{"name":"iconClass","value":"bx bx-extension","type":"label"}],"children":[{"id":"_help_9qPsTWBorUhQ","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/rest-api/etapi/"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_47ZrP6FNuoG8","title":"Default Note Title","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Default Note Title"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_wX4HbRucYSDD","title":"Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database"},{"name":"iconClass","value":"bx bx-data","type":"label"}],"children":[{"id":"_help_oyIAJ9PvvwHX","title":"Manually altering the database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database"},{"name":"iconClass","value":"bx bxs-edit","type":"label"}],"children":[{"id":"_help_YKWqdJhzi2VY","title":"SQL Console","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database/SQL Console"},{"name":"iconClass","value":"bx bx-data","type":"label"}]}]},{"id":"_help_6tZeKvSHEUiB","title":"Demo Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Demo Notes"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_Gzjqa934BdH4","title":"Configuration (config.ini or environment variables)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or e"},{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_c5xB8m4g2IY6","title":"Trilium instance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Trilium instance"},{"name":"iconClass","value":"bx bx-windows","type":"label"}]},{"id":"_help_LWtBjFej3wX3","title":"Cross-Origin Resource Sharing (CORS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Cross-Origin Resource Sharing "},{"name":"iconClass","value":"bx bx-lock","type":"label"}]}]},{"id":"_help_ivYnonVFBxbQ","title":"Bulk Actions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Bulk Actions"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_4FahAwuGTAwC","title":"Note source","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note source"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_1YeN2MzFUluU","title":"Technologies used","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}],"children":[{"id":"_help_MI26XDLSAlCD","title":"CKEditor","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/CKEditor"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_N4IDkixaDG9C","title":"MindElixir","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/MindElixir"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_H0mM1lTxF9JI","title":"Excalidraw","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Excalidraw"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_MQHyy2dIFgxS","title":"Leaflet","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Leaflet"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]}]},{"id":"_help_m1lbrzyKDaRB","title":"Note ID","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note ID"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_0vTSyvhPTAOz","title":"Internal API","type":"book","attributes":[{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_z8O2VG4ZZJD7","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/rest-api/internal/"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_2mUhVmZK8RF3","title":"Hidden Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Hidden Notes"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]},{"id":"_help_uYF7pmepw27K","title":"Metrics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Metrics"},{"name":"iconClass","value":"bx bxs-data","type":"label"}],"children":[{"id":"_help_bOP3TB56fL1V","title":"grafana-dashboard.json","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_64ZTlUPgEPtW","title":"Safe mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Safe mode"},{"name":"iconClass","value":"bx bxs-virus-block","type":"label"}]},{"id":"_help_HAIOFBoYIIdO","title":"Nightly release","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Nightly release"},{"name":"iconClass","value":"bx bx-moon","type":"label"}]},{"id":"_help_ZmT9ln8XJX2o","title":"Read-only database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Read-only database"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_GBBMSlVSOIGP","title":"AI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI"},{"name":"iconClass","value":"bx bx-bot","type":"label"}],"children":[{"id":"_help_WkM7gsEUyCXs","title":"Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Providers"},{"name":"iconClass","value":"bx bx-select-multiple","type":"label"}],"children":[{"id":"_help_7EdTxPADv95W","title":"Ollama","type":"book","attributes":[{"name":"iconClass","value":"bx bx-message-dots","type":"label"}],"children":[{"id":"_help_vvUCN7FDkq7G","title":"Installing Ollama","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Providers/Ollama/Installing Ollama"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ZavFigBX9AwP","title":"OpenAI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Providers/OpenAI"},{"name":"iconClass","value":"bx bx-message-dots","type":"label"}]},{"id":"_help_e0lkirXEiSNc","title":"Anthropic","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Providers/Anthropic"},{"name":"iconClass","value":"bx bx-message-dots","type":"label"}]}]}]},{"id":"_help_CdNpE2pqjmI6","title":"Scripting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting"},{"name":"iconClass","value":"bx bxs-file-js","type":"label"}],"children":[{"id":"_help_yIhgI5H7A2Sm","title":"Frontend Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics"},{"name":"iconClass","value":"bx bx-window","type":"label"}],"children":[{"id":"_help_MgibgPcfeuGz","title":"Custom Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets"},{"name":"iconClass","value":"bx bxs-widget","type":"label"}],"children":[{"id":"_help_YNxAqkI5Kg1M","title":"Word count widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Word count widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_SynTBQiBsdYJ","title":"Widget Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Widget Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_M8IppdwVHSjG","title":"Right pane widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Right pane widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_VqGQnnPGnqAU","title":"CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/CSS"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_es8OU2GuguFU","title":"Examples","type":"book","attributes":[{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_TjLYAo3JMO8X","title":"\"New Task\" launcher button","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/New Task launcher button"},{"name":"iconClass","value":"bx bx-task","type":"label"}]},{"id":"_help_7kZPMD0uFwkH","title":"Downloading responses from Google Forms","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/Downloading responses from Goo"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_DL92EjAaXT26","title":"Using promoted attributes to configure scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/Using promoted attributes to c"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_4Gn3psZKsfSm","title":"Launch Bar Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets"},{"name":"iconClass","value":"bx bx-dock-left","type":"label"}],"children":[{"id":"_help_IPArqVfDQ4We","title":"Note Title Widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets/Note Title Widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_gcI7RPbaNSh3","title":"Analog Watch","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets/Analog Watch"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_SPirpZypehBG","title":"Backend scripts","type":"book","attributes":[{"name":"iconClass","value":"bx bx-server","type":"label"}],"children":[{"id":"_help_fZ2IGYFXjkEy","title":"Server-side imports","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts/Server-side imports"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GPERMystNGTB","title":"Events","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts/Events"},{"name":"iconClass","value":"bx bx-rss","type":"label"}]}]},{"id":"_help_GLks18SNjxmC","title":"Script API","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API"},{"name":"iconClass","value":"bx bx-code-curly","type":"label"}],"children":[{"id":"_help_Q2z6av6JZVWm","title":"Frontend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/frontend"},{"name":"iconClass","value":"bx bx-folder","type":"label"}],"enforceAttributes":true,"children":[{"id":"_help_habiZ3HU8Kw8","title":"FNote","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/frontend/interfaces/FNote.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_MEtfsqa5VwNi","title":"Backend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/backend"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true},{"id":"_help_ApVHZ8JY5ofC","title":"Day.js","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API/Day.js"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]}]},{"id":"_help_vElnKeDNPSVl","title":"Logging","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Logging"},{"name":"iconClass","value":"bx bx-terminal","type":"label"}]}]},{"id":"_help_Fm0j45KqyHpU","title":"Miscellaneous","type":"book","attributes":[{"name":"iconClass","value":"bx bx-info-circle","type":"label"}],"children":[{"id":"_help_WFbFXrgnDyyU","title":"Privacy Policy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Miscellaneous/Privacy Policy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_NcsmUYZRWEW4","title":"Patterns of personal knowledge","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Miscellaneous/Patterns of personal knowledge"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}] \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/1_New Layout_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/1_New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..c9a85f777338c42c4f31f7268ebd650fea5e1ad1 GIT binary patch literal 21557 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 MikfG#<&AIsFNP9jiU0rr literal 0 HcmV?d00001 diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/2_New Layout_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/2_New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..10676a08ef7008a837f000e98f7ce06fb32c0b10 GIT binary patch literal 10292 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 literal 0 HcmV?d00001 diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/3_New Layout_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/3_New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..b4452f695280e4f0feb9deca13302bd2e76a8818 GIT binary patch literal 3835 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 m|MU~#fBV4r|7s}qw>Aui;%y0xo+SeGYHnrd< literal 0 HcmV?d00001 diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/4_New Layout_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/4_New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2f36ae2ecc5de8f27527172960e336c54504d9 GIT binary patch literal 7397 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 New layout is a series of UI/UX changes that were introduced + in v0.101.0 that heavily change both existing UI elements, as well as adding + some new ones. The goal of this new layout is to modernize the application + and to make it more intuitive but at the same time to reduce clutter.

    +

    Newly introduced features

    +

    Status bar

    +

    At the bottom of the window there is a new bar called the Status bar. + This bar houses multiple items such as the Breadcrumb navigation and information + and settings about the current note, such as the content language and  + Attributes.

    +

    For more information, consult the dedicated page.

    +
    + +
    +

    Inline title

    +

    In previous versions of Trilium, the title bar was fixed at all times. + In the new layout, there is both a fixed title bar and one that scrolls + with the text. The newly introduced title is called the Inline title and + it displays the title in a larger font, while also displaying additional + information such as the creation and the modification date.

    +

    Whenever the title is scrolled past, the fixed title is shown instead.

    +

    This only affects Text and  + Code notes. Note types that take the entirety of the screen such + as Canvas will + always have only the fixed title bar.

    +

    Depending on the note type, the inline title will also present some more + interactive options such as being able to switch the note type (see below).

    +
    + +
    The Inline title, which is displayed at the top of the note and + can be scrolled past.
    +
    +
    + +
    The fixed title bar. The title only appears after scrolling past the Inline title.
    +
    +

    New note type switcher

    +

    When a new Text or  + Code note is created, a note type switcher will appear below + the Inline title. Apart from changing the note type, it's also + possible to apply a template.

    +

    The switcher will disappear as soon as a text is entered.

    +

    + +

    +

    Note badges

    +

    Note badges appear near the fixed note title and indicate important information + about the note such as whether it is read-only. Some of the badges are + also interactive.

    +
    + +
    +

    The following badges are available:

    +
      +
    • Read-only badge, which will be shown if the note is not + editable due to either automatic read-only or manual read-only. Clicking + on the badge will temporarily edit the note (similar to the Edit floating button).
    • +
    • Share badge, which will indicate that the current note + is shared. The badge will also indicate if the share is on the local network + (for the desktop application without Synchronization set + up) or publicly accessible (for the server). 
    • +
    • Web clip badge, which will indicate if the note was clipped + using the Web Clipper. + The badge acts as a link, so it can be clicked on to navigate to the page + or right clicked for more options.
    • +
    • Execute badge, for scripts or + saved SQL querieswhich have an execute button or a description.
    • +
    +

    Some of these badges replace the dedicated panels at the top of the note.

    +

    Collapsible sections

    +
    + +
    +

    The following sections have been made collapsible:

    +
      +
    • Promoted Attributes +
        +
      • For full-height notes such as Canvas, + the promoted attributes are collapsed by default to make room.
      • +
      • The keyboard shortcut previously used to trigger the promoted attributes + ribbon tab (which was no longer working) has been repurposed to toggle + the promoted attributes instead.
      • +
      +
    • +
    • Edited Notes, which appears for Day Notes is + now shown underneath the title. +
        +
      • Whether the section is collapsed or not depends on the choice in  + Options → Appearance.
      • +
      +
    • +
    • Search Properties, which appears for the full Search and  + Saved Search.
    • +
    +

    Changing to the existing layout

    +

    Removal of the ribbon

    +

    The most significant change is the removal of the ribbon. All the actions + and options from the ribbon were integrated in other places in the application.

    +

    Here's how all the different tabs that were once part of the ribbon are + now available in the new layout:

    +
      +
    • “Formatting toolbar” was relocated to the top of the page. +
        +
      • Instead of having one per split, now there is a single formatting toolbar + per tab. This allows more space for the toolbar items.
      • +
      +
    • +
    • “Owned attributes” and “Inherited attributes” were merged and moved to + the status bar region (displayed one above the other).
    • +
    • “Basic Properties” were integrated in the Note buttons menu. +
        +
      • The only exception here is the Language combo box which can now be found + in the status bar (top-right of the screen).
      • +
      +
    • +
    • “File” and “Image” tabs +
        +
      • The buttons were moved to the right of the note title, as dedicated entries + in Note buttons.
      • +
      • The info section has been merged into the Note info section of + the status bar.
      • +
      +
    • +
    • Edited notes +
        +
      • Moved underneath the title, displayed under a collapsible area and the + notes are represented as badges/chips.
      • +
      • Whether the section is expanded or collapsed depends on the “Edited Notes + ribbon tab will automatically open on day notes” setting from Options → + Appearance.
      • +
      +
    • +
    • Search definition tab +
        +
      • Moved underneath the title under a collapsible area.
      • +
      • Expanded by default for new searches, collapsed for saved searches.
      • +
      +
    • +
    • The Note map is now available in the Note actions menu. +
        +
      • Instead of opening into a panel in the ribbon, the note map now opens + in a side split (similar to the in-app help).
      • +
      +
    • +
    • “Note info” tab was moved to a small (i) icon in the status bar.
    • +
    • “Similar notes” tab +
        +
      • Moved to the status bar, by going to the “Note info” section and pressing + the button to show similar notes.
      • +
      • Displayed as a fixed panel, similar to the attributes.
      • +
      +
    • +
    • The Collection properties tab were relocated under the note title and + grouped into: +
        +
      • A combo box to quickly switch between views.
      • +
      • Individual settings for the current view in a submenu.
      • +
      +
    • +
    • Some smaller ribbon tabs were converted to badges that appear near the + note title in the breadcrumb section: +
        +
      • Original URL indicator for clipped web pages (#pageUrl).
      • +
      • SQL and script execute buttons.
      • +
      +
    • +
    + +

    Removal of the floating buttons

    +

    Most of the buttons were relocated to the right of the note title, in + the Note buttons area, + with the exception of:

    +
      +
    • The Edit button is displayed near the note title, as a badge.
    • +
    • Backlinks is displayed in the status bar. When clicked, the same + list of backlinks is displayed.
    • +
    • Relation map zoom buttons are now part of the relation map itself.
    • +
    • Export image to PNG/SVG are now in the Note actions menu, in the Export as image option.
    • +
    +

    How to toggle the new layout

    +

    Starting with v0.101.0, this new layout is enabled by default. It is possible + to fall back to the old layout by going to Options → + Appearance and selecting Old layout.

    + \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb.html new file mode 100644 index 0000000000..6ed2908527 --- /dev/null +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb.html @@ -0,0 +1,55 @@ +
    + +
    +

    The breadcrumb allows quickly viewing the note hierarchy of the current + note and navigating through it.

    +

    It is part of the Status bar, + displayed in the bottom-left of the screen.

    +

    Layout and Interaction

    +
      +
    • If a note or workspace is hoisted, a badge will appear on the left-most + side. +
        +
      • Clicking on the badge will un-hoist the note/workspace.
      • +
      +
    • +
    • The left-most icon represents the root note, or the hoisted note or workspace. +
        +
      • Clicking the icon will jump to the root note.
      • +
      • Right clicking the icon will display a menu that allows opening the note + in a new tab, split, etc.
      • +
      +
    • +
    • Each segment shows the title of a note in the current note hierarchy. +
        +
      • Clicking the icon will jump to that note.
      • +
      • Right clicking will open a menu with multiple options such as opening + the note in a different tab/split/window, hoisting, moving/cloning the + note, duplicating as well as changing the color of the note.
      • +
      +
    • +
    • Clicking the arrow next to each segment will reveal the child notes of + the segment on the left. +
        +
      • Clicking on an icon will navigate to that particular note.
      • +
      • It's also possible to create a new child note from here.
      • +
      • The menu can optionally hide the archived notes.
      • +
      +
    • +
    • If the current note is deep within a hierarchy, the segments will collapse + into a […] button in order not to occupy too much space. +
        +
      • Clicking this button will display each collapsed entry as a menu item. + Clicking on it will navigate to that particular note.
      • +
      +
    • +
    • Right clicking on an empty space to the right of the breadcrumb (before + the other status bar items) will reveal another menu that allows: +
        +
      • Toggling whether archived notes are displayed in the breadcrumb and in + the note tree.
      • +
      • Copying the current note path to clipboard.
      • +
      +
    • +
    \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb_image.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2f36ae2ecc5de8f27527172960e336c54504d9 GIT binary patch literal 7397 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 xHWm3_ughfULT~)PcXI#7o3;ObCHM6bThe status bar displays information about the current note and allows + changing settings related to it such as configuring the language or attributes.

    +

    Layout and interaction

    +

    On the left side, the Breadcrumb is + displayed which indicates the current note as well as its parent notes + and allows for quick navigation throughout the hierarchy.

    +

    On the right side, specific sections will show depending on the type of + the current note.

    +
      +
    1. For code notes, the language mode of the note is indicated (e.g. JavaScript, + plain text), as well as allowing easy switching to another mode.
    2. +
    3. For text notes, the content language is displayed and can be changed, + thus configuring the spell-check and the right-to-left support. +
        +
      1. Note that this applies to the entire note and not the selection, unlike + some text editors.
      2. +
      +
    4. +
    5. If a note is placed in multiple places in the tree (cloned), the number + of the note paths will be displayed. +
        +
      1. Clicking it will reveal the full list of note paths and a button to place + it somewhere else.
      2. +
      +
    6. +
    7. If a note has attachments, their number will be displayed. +
        +
      1. Clicking on it will reveal the list of attachments in a new tab.
      2. +
      +
    8. +
    9. If a note is linked from other text notes (backlinks), the number of backlinks + will be displayed. +
        +
      1. Clicking on it will show the list of notes that link to this note, as + well as an excerpt of where the note is referenced.
      2. +
      +
    10. +
    +

    Regardless of note type, the following items will always be displayed + if there is a note:

    +
      +
    1. Note info, which displays: +
        +
      1. The creation/modification date of the note.
      2. +
      3. The type and MIME of the note.
      4. +
      5. The note ID.
      6. +
      7. An estimation of the note size of the note itself and its children.
      8. +
      9. A button to show Similar notes.
      10. +
      +
    2. +
    \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..12f5e0d5f0a5e4470faf5ab5ae73473d2c2834a6 GIT binary patch literal 10183 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

      -
    • The Note Revisions button displays the Note Revisions for +
    • The Note Revisions button displays the Note Revisions for that particular note.
    • -
    • The contextual menu offers commands for the note or its subtree, such +
    • The contextual menu offers commands for the note or its subtree, such as import, export, viewing the Note source code or  Attachments.
    • -
    \ No newline at end of file + +

    On the New Layout, + the button area is populated by some more buttons that are specific to + the current note. For example, for Image and File notes, + the download and copy buttons were relocated there.

    \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html index c2696105a6..30acffb59c 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html @@ -7,98 +7,94 @@ adjusted.

    How it works

    When first creating a collection of Board type, a few subnotes - will be created, each having a #status label - set. The board then groups each note by the value of the status attribute.

    + will be created, each having a #status label set. The board + then groups each note by the value of the status attribute.

    Notes are displayed recursively, so even the child notes of the child notes will be displayed. However, unlike the Table, the notes are not displayed in a hierarchy.

    Interaction

    Working with columns

      -
    • Create a new column by pressing Add Column near the last column. +
    • Create a new column by pressing Add Column near the last column.
        -
      • Once pressed, a text box will be displayed to set the name of the column. +
      • Once pressed, a text box will be displayed to set the name of the column. Press Enter to confirm, or Escape to dismiss.
    • -
    • To reorder a column, simply hold the mouse over the title and drag it +
    • To reorder a column, simply hold the mouse over the title and drag it to the desired position.
    • -
    • To delete a column, right click on its title and select Delete column.
    • -
    • To rename a column, click on the note title. +
    • To delete a column, right click on its title and select Delete column.
    • +
    • To rename a column, click on the note title.
        -
      • Press Enter to confirm.
      • -
      • Upon renaming a column, the corresponding status attribute of all its +
      • Press Enter to confirm.
      • +
      • Upon renaming a column, the corresponding status attribute of all its notes will be changed in bulk.
      -
    • -
    • If there are many columns, use the mouse wheel to scroll.
    • + +
    • If there are many columns, use the mouse wheel to scroll.

    Working with notes

      -
    • Create a new note in any column by pressing New item +
    • Create a new note in any column by pressing New item
        -
      • Enter the name of the note and press Enter or click away. To +
      • Enter the name of the note and press Enter or click away. To dismiss the creation of a new note, simply press Escape or leave the name empty.
      • -
      • Once created, the new note will have an attribute (status label +
      • Once created, the new note will have an attribute (status label by default) set to the name of the column.
    • -
    • To open the note, simply click on it.
    • -
    • To change the title of the note directly from the board, hover the mouse +
    • To open the note, simply click on it.
    • +
    • To change the title of the note directly from the board, hover the mouse over its card and press the edit button on the right.
    • -
    • To change the state of a note, simply drag a note from one column to the +
    • To change the state of a note, simply drag a note from one column to the other to change its state.
    • -
    • The order of the notes in each column corresponds to their position in +
    • The order of the notes in each column corresponds to their position in the tree.
        -
      • It's possible to reorder notes simply by dragging them to the desired +
      • It's possible to reorder notes simply by dragging them to the desired position within the same columns.
      • -
      • It's also possible to drag notes across columns, at the desired position.
      • +
      • It's also possible to drag notes across columns, at the desired position.
    • -
    • For more options, right click on a note to display a context menu with +
    • For more options, right click on a note to display a context menu with the following options:
        -
      • Open the note in a new tab/split/window or quick edit.
      • -
      • Move the note to any column.
      • -
      • Insert a new note above/below the current one.
      • -
      • Archive/unarchive the current note.
      • -
      • Delete the current note.
      • +
      • Open the note in a new tab/split/window or quick edit.
      • +
      • Move the note to any column.
      • +
      • Insert a new note above/below the current one.
      • +
      • Archive/unarchive the current note.
      • +
      • Delete the current note.
    • -
    • If there are many notes within the column, move the mouse over the column +
    • If there are many notes within the column, move the mouse over the column and use the mouse wheel to scroll.

    Working with the note tree

    It's also possible to add items on the board using the Note Tree.

      -
    1. Select the desired note in the Note Tree.
    2. -
    3. Hold the mouse on the note and drag it to the to the desired column.
    4. +
    5. Select the desired note in the Note Tree.
    6. +
    7. Hold the mouse on the note and drag it to the to the desired column.

    This works for:

      -
    • Notes that are not children of the board, case in which a clone will +
    • Notes that are not children of the board, case in which a clone will be created.
    • -
    • Notes that are children of the board, but not yet assigned on the board.
    • -
    • Notes that are children of the board, case in which they will be moved +
    • Notes that are children of the board, but not yet assigned on the board.
    • +
    • Notes that are children of the board, case in which they will be moved to the new column.

    Keyboard interaction

    The board view has mild support for keyboard-based navigation:

      -
    • Use Tab and Shift+Tab to navigate between +
    • Use Tab and Shift+Tab to navigate between column titles, notes and the “New item” button for each of the columns, in sequential order.
    • -
    • To rename a column or a note, press F2 while it is focused.
    • -
    • To open a specific note or create a new item, press Enter while +
    • To rename a column or a note, press F2 while it is focused.
    • +
    • To open a specific note or create a new item, press Enter while it is focused.
    • -
    • To dismiss a rename of a note or a column, press Escape.
    • +
    • To dismiss a rename of a note or a column, press Escape.

    Configuration

    Displaying custom attributes

    @@ -112,37 +108,33 @@ href="#root/_help_OFXdgB2nNk1F">Promoted Attributes). The easiest way to add these is:

      -
    1. Go to board note.
    2. -
    3. In the ribbon select Owned Attributes → plus button → Add new label/relation definition.
    4. -
    5. Configure the attribute as desired.
    6. -
    7. Check Inheritable to make it applicable to child notes automatically.
    8. +
    9. Go to board note.
    10. +
    11. In the ribbon select Owned Attributes → plus button → Add new label/relation definition.
    12. +
    13. Configure the attribute as desired.
    14. +
    15. Check Inheritable to make it applicable to child notes automatically.

    After creating the attribute, click on a note and fill in the promoted attributes which should then reflect inside the board.

    Of note:

      -
    • Both promoted and non-promoted attribute definitions are supported. The +
    • Both promoted and non-promoted attribute definitions are supported. The only difference is that non-promoted attributes don't have an “Alias” for assigning a custom name.
    • -
    • Both “Single value” and “Multi value” attributes are supported. In case +
    • Both “Single value” and “Multi value” attributes are supported. In case of multi-value, a badge is displayed for every instance of the attribute.
    • -
    • All label types are supported, including dates, booleans and URLs.
    • -
    • Relation attributes are also supported as well, showing a link with the - target note title and icon.
    • -
    • Currently, it's not possible to adjust which promoted attributes are displayed, - since all promoted attributes will be displayed (except the board:groupBy one). - There are plans to improve upon this being able to hide promoted attributes - individually.
    • +
    • All label types are supported, including dates, booleans and URLs.
    • +
    • Relation attributes are also supported as well, showing a link with the + target note title and icon.
    • +
    • Currently, it's not possible to adjust which promoted attributes are displayed, + since all promoted attributes will be displayed (except the board:groupBy one). + There are plans to improve upon this being able to hide promoted attributes + individually.

    Grouping by another label

    -

    By default, the label used to group the notes is #status. - It is possible to use a different label if needed by defining a label named - #board:groupBywith the value being the attribute to use (with or - without # attribute prefix).

    +

    By default, the label used to group the notes is #status. + It is possible to use a different label if needed by defining a label named #board:groupBy with + the value being the attribute to use (with or without # attribute + prefix).

    Grouping by relations

    A more advanced use-case is grouping by Relations.

    During this mode:

      -
    • The columns represent the target notes of a relation.
    • -
    • When creating a new column, a note is selected instead of a column name.
    • -
    • The column icon will match the target note.
    • -
    • Moving notes between columns will change its relation.
    • -
    • Renaming an existing column will change the target note of all the notes - in that column.
    • +
    • The columns represent the target notes of a relation.
    • +
    • When creating a new column, a note is selected instead of a column name.
    • +
    • The column icon will match the target note.
    • +
    • Moving notes between columns will change its relation.
    • +
    • Renaming an existing column will change the target note of all the notes + in that column.

    Using relations instead of labels has some benefits:

      -
    • The status/grouping of the notes is visible outside the Kanban board, +
    • The status/grouping of the notes is visible outside the Kanban board, for example on the Note Map.
    • -
    • Columns can have icons.
    • -
    • Renaming columns is less intensive since it simply involves changing the - note title of the target note instead of having to do a bulk rename.
    • +
    • Columns can have icons.
    • +
    • Renaming columns is less intensive since it simply involves changing the + note title of the target note instead of having to do a bulk rename.

    To do so:

      -
    1. First, create a Kanban board from scratch and not a template:
    2. -
    3. Assign #viewType=board #hidePromotedAttributes to - emulate the default template.
    4. -
    5. Set #board:groupBy to the name of a relation - to group by, including the ~ prefix (e.g. - ~status).
    6. -
    7. -

      Optionally, use Promoted Attributes for - easy status change within the note:

      #relation:status(inheritable)="promoted,alias=Status,single"
      -
    8. +
    9. +

      First, create a Kanban board from scratch and not a template:

      +
    10. +
    11. +

      Assign #viewType=board #hidePromotedAttributes to emulate the + default template.

      +
    12. +
    13. +

      Set #board:groupBy to the name of a relation to group by, including the ~ prefix (e.g. ~status).

      +
    14. +
    15. +

      Optionally, use Promoted Attributes for + easy status change within the note:

      #relation:status(inheritable)="promoted,alias=Status,single"
      +
    \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Documentation.md b/docs/Developer Guide/Developer Guide/Documentation.md index 6dca184859..8f16c43a3f 100644 --- a/docs/Developer Guide/Developer Guide/Documentation.md +++ b/docs/Developer Guide/Developer Guide/Documentation.md @@ -1,5 +1,5 @@ # Documentation -There are multiple types of documentation for Trilium: +There are multiple types of documentation for Trilium: * The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing F1. * The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers. diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json index d846c6d274..2e16e2add5 100644 --- a/docs/User Guide/!!!meta.json +++ b/docs/User Guide/!!!meta.json @@ -2851,6 +2851,20 @@ "value": "note-buttons", "isInheritable": false, "position": 60 + }, + { + "type": "relation", + "name": "internalLink", + "value": "IjZS7iK5EXtb", + "isInheritable": false, + "position": 70 + }, + { + "type": "relation", + "name": "internalLink", + "value": "W8vYD3Q1zjCR", + "isInheritable": false, + "position": 80 } ], "format": "markdown", @@ -3436,6 +3450,286 @@ "dataFileName": "Note Tooltip_image.png" } ] + }, + { + "isClone": false, + "noteId": "IjZS7iK5EXtb", + "notePath": [ + "pOsGYCXsbNQG", + "gh7bpGYxajRS", + "Vc8PjrjAGuOp", + "IjZS7iK5EXtb" + ], + "title": "New Layout", + "notePosition": 220, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "iconClass", + "value": "bx bx-layout", + "isInheritable": false, + "position": 30 + }, + { + "type": "relation", + "name": "internalLink", + "value": "4TIF1oA4VQRO", + "isInheritable": false, + "position": 40 + }, + { + "type": "relation", + "name": "internalLink", + "value": "iPIMuisry3hd", + "isInheritable": false, + "position": 50 + }, + { + "type": "relation", + "name": "internalLink", + "value": "6f9hih2hXXZk", + "isInheritable": false, + "position": 60 + }, + { + "type": "relation", + "name": "internalLink", + "value": "KC1HB96bqqHX", + "isInheritable": false, + "position": 70 + }, + { + "type": "relation", + "name": "internalLink", + "value": "8YBEPzcpUgxw", + "isInheritable": false, + "position": 80 + }, + { + "type": "relation", + "name": "internalLink", + "value": "grjYqerjn243", + "isInheritable": false, + "position": 90 + }, + { + "type": "relation", + "name": "internalLink", + "value": "XpOYSgsLkTJy", + "isInheritable": false, + "position": 100 + }, + { + "type": "relation", + "name": "internalLink", + "value": "cbkrhQjrkKrh", + "isInheritable": false, + "position": 110 + }, + { + "type": "relation", + "name": "internalLink", + "value": "MtPxeAWVAzMg", + "isInheritable": false, + "position": 120 + }, + { + "type": "relation", + "name": "internalLink", + "value": "CdNpE2pqjmI6", + "isInheritable": false, + "position": 140 + }, + { + "type": "relation", + "name": "internalLink", + "value": "YKWqdJhzi2VY", + "isInheritable": false, + "position": 150 + }, + { + "type": "relation", + "name": "internalLink", + "value": "veGu4faJErEM", + "isInheritable": false, + "position": 160 + }, + { + "type": "relation", + "name": "internalLink", + "value": "zEY4DaJG4YT5", + "isInheritable": false, + "position": 170 + }, + { + "type": "relation", + "name": "internalLink", + "value": "AlJ73vBCjWDw", + "isInheritable": false, + "position": 180 + }, + { + "type": "relation", + "name": "internalLink", + "value": "l0tKav7yLHGF", + "isInheritable": false, + "position": 190 + }, + { + "type": "relation", + "name": "internalLink", + "value": "eIg8jdvaoNNd", + "isInheritable": false, + "position": 200 + }, + { + "type": "relation", + "name": "internalLink", + "value": "m523cpzocqaD", + "isInheritable": false, + "position": 210 + } + ], + "format": "markdown", + "dataFileName": "New Layout.md", + "attachments": [ + { + "attachmentId": "3DFGaMiTTHQ1", + "title": "image.png", + "role": "image", + "mime": "image/png", + "position": 10, + "dataFileName": "New Layout_image.png" + }, + { + "attachmentId": "6iN5nrmdwG6z", + "title": "image.png", + "role": "image", + "mime": "image/png", + "position": 10, + "dataFileName": "1_New Layout_image.png" + }, + { + "attachmentId": "KvNAEoJjRhyr", + "title": "image.png", + "role": "image", + "mime": "image/png", + "position": 10, + "dataFileName": "2_New Layout_image.png" + }, + { + "attachmentId": "lEKxf6dYMG6u", + "title": "image.png", + "role": "image", + "mime": "image/png", + "position": 10, + "dataFileName": "3_New Layout_image.png" + }, + { + "attachmentId": "SYOTVGCyx749", + "title": "image.png", + "role": "image", + "mime": "image/png", + "position": 10, + "dataFileName": "4_New Layout_image.png" + }, + { + "attachmentId": "wCwzwfGspejR", + "title": "image.png", + "role": "image", + "mime": "image/png", + "position": 10, + "dataFileName": "5_New Layout_image.png" + } + ], + "dirFileName": "New Layout", + "children": [ + { + "isClone": false, + "noteId": "I6p2a06hdnL6", + "notePath": [ + "pOsGYCXsbNQG", + "gh7bpGYxajRS", + "Vc8PjrjAGuOp", + "IjZS7iK5EXtb", + "I6p2a06hdnL6" + ], + "title": "Breadcrumb", + "notePosition": 10, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "AlJ73vBCjWDw", + "isInheritable": false, + "position": 30 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-chevron-right", + "isInheritable": false, + "position": 40 + } + ], + "format": "markdown", + "dataFileName": "Breadcrumb.md", + "attachments": [ + { + "attachmentId": "CjYmaJD0L1D4", + "title": "image.png", + "role": "image", + "mime": "image/png", + "position": 10, + "dataFileName": "Breadcrumb_image.png" + } + ] + }, + { + "isClone": false, + "noteId": "AlJ73vBCjWDw", + "notePath": [ + "pOsGYCXsbNQG", + "gh7bpGYxajRS", + "Vc8PjrjAGuOp", + "IjZS7iK5EXtb", + "AlJ73vBCjWDw" + ], + "title": "Status bar", + "notePosition": 20, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "I6p2a06hdnL6", + "isInheritable": false, + "position": 30 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-dock-bottom", + "isInheritable": false, + "position": 40 + } + ], + "format": "markdown", + "dataFileName": "Status bar.md", + "attachments": [] + } + ] } ] }, diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/1_New Layout_image.png b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/1_New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..c9a85f777338c42c4f31f7268ebd650fea5e1ad1 GIT binary patch literal 21557 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 MikfG#<&AIsFNP9jiU0rr literal 0 HcmV?d00001 diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/2_New Layout_image.png b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/2_New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..10676a08ef7008a837f000e98f7ce06fb32c0b10 GIT binary patch literal 10292 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 literal 0 HcmV?d00001 diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/3_New Layout_image.png b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/3_New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..b4452f695280e4f0feb9deca13302bd2e76a8818 GIT binary patch literal 3835 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 m|MU~#fBV4r|7s}qw>Aui;%y0xo+SeGYHnrd< literal 0 HcmV?d00001 diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/4_New Layout_image.png b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/4_New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2f36ae2ecc5de8f27527172960e336c54504d9 GIT binary patch literal 7397 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. + +For more information, consult the [dedicated page](New%20Layout/Status%20bar.md). + +
    + +### Inline title + +In previous versions of Trilium, the title bar was fixed at all times. In the new layout, there is both a fixed title bar and one that scrolls with the text. The newly introduced title is called the _Inline title_ and it displays the title in a larger font, while also displaying additional information such as the creation and the modification date. + +Whenever the title is scrolled past, the fixed title is shown instead. + +This only affects Text and Code notes. Note types that take the entirety of the screen such as Canvas will always have only the fixed title bar. + +Depending on the note type, the inline title will also present some more interactive options such as being able to switch the note type (see below). + +
    The Inline title, which is displayed at the top of the note and can be scrolled past.
    The fixed title bar. The title only appears after scrolling past the Inline title.
    + +### New note type switcher + +When a new Text or Code note is created, a note type switcher will appear below the _Inline title_. Apart from changing the note type, it's also possible to apply a [template](../../Advanced%20Usage/Templates.md). + +The switcher will disappear as soon as a text is entered. + + + +### Note badges + +Note badges appear near the fixed note title and indicate important information about the note such as whether it is read-only. Some of the badges are also interactive. + +
    + +The following badges are available: + +* **Read-only badge**, which will be shown if the note is not editable due to either automatic read-only or manual read-only. Clicking on the badge will temporarily edit the note (similar to the Edit [floating button](Floating%20buttons.md)). +* **Share badge**, which will indicate that the current note is shared. The badge will also indicate if the share is on the local network (for the desktop application without Synchronization set up) or publicly accessible (for the server). +* **Web clip badge**, which will indicate if the note was clipped using the Web Clipper. The badge acts as a link, so it can be clicked on to navigate to the page or right clicked for more options. +* **Execute badge**, for [scripts](../../Scripting.md) or [saved SQL queries](../../Advanced%20Usage/Database/Manually%20altering%20the%20database/SQL%20Console.md) which have an execute button or a description. + +Some of these badges replace the dedicated panels at the top of the note. + +### Collapsible sections + +
    + +The following sections have been made collapsible: + +* _Promoted Attributes_ + * For full-height notes such as Canvas, the promoted attributes are collapsed by default to make room. + * The keyboard shortcut previously used to trigger the promoted attributes ribbon tab (which was no longer working) has been repurposed to toggle the promoted attributes instead. +* _Edited Notes_, which appears for Day Notes is now shown underneath the title. + * Whether the section is collapsed or not depends on the choice in Options → Appearance. +* _Search Properties_, which appears for the full Search and Saved Search. + +## Changing to the existing layout + +### Removal of the ribbon + +The most significant change is the removal of the ribbon. All the actions and options from the ribbon were integrated in other places in the application. + +Here's how all the different tabs that were once part of the ribbon are now available in the new layout: + +* “Formatting toolbar” was relocated to the top of the page. + * Instead of having one per split, now there is a single formatting toolbar per tab. This allows more space for the toolbar items. +* “Owned attributes” and “Inherited attributes” were merged and moved to the status bar region (displayed one above the other). +* “Basic Properties” were integrated in the Note buttons menu. + * The only exception here is the Language combo box which can now be found in the status bar (top-right of the screen). +* “File” and “Image” tabs + * The buttons were moved to the right of the note title, as dedicated entries in Note buttons. + * The info section has been merged into the _Note info_ section of the status bar. +* Edited notes + * Moved underneath the title, displayed under a collapsible area and the notes are represented as badges/chips. + * Whether the section is expanded or collapsed depends on the “Edited Notes ribbon tab will automatically open on day notes” setting from Options → Appearance. +* Search definition tab + * Moved underneath the title under a collapsible area. + * Expanded by default for new searches, collapsed for saved searches. +* The Note map is now available in the Note actions menu. + * Instead of opening into a panel in the ribbon, the note map now opens in a side split (similar to the in-app help). +* “Note info” tab was moved to a small (i) icon in the status bar. +* “Similar notes” tab + * Moved to the status bar, by going to the “Note info” section and pressing the button to show similar notes. + * Displayed as a fixed panel, similar to the attributes. +* The Collection properties tab were relocated under the note title and grouped into: + * A combo box to quickly switch between views. + * Individual settings for the current view in a submenu. +* Some smaller ribbon tabs were converted to badges that appear near the note title in the breadcrumb section: + * Original URL indicator for clipped web pages (`#pageUrl`). + * SQL and script execute buttons. + +> [!NOTE] +> The ribbon keyboard shortcuts (e.g. `toggleRibbonTabClassicEditor`) have been repurposed to work on the new layout, where they will toggle the appropriate panel. + +### Removal of the floating buttons + +Most of the buttons were relocated to the right of the note title, in the Note buttons area, with the exception of: + +* The Edit button is displayed near the note title, as a badge. +* _Backlinks_ is displayed in the status bar. When clicked, the same list of backlinks is displayed. +* Relation map zoom buttons are now part of the relation map itself. +* Export image to PNG/SVG are now in the Note actions menu, in the _Export as image_ option. + +## How to toggle the new layout + +Starting with v0.101.0, this new layout is enabled by default. It is possible to fall back to the old layout by going to Options → Appearance and selecting _Old layout_. + +> [!IMPORTANT] +> Since a new layout was introduced, this becomes the standard one. The _Old layout_ is considered deprecated and will not receive new features (for example, the breadcrumb) as we focus on the new one. At some point the old layout will be removed entirely, as maintaining two layouts with major differences creates a maintenance burden. \ No newline at end of file diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb.md b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb.md new file mode 100644 index 0000000000..fb27f092a2 --- /dev/null +++ b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb.md @@ -0,0 +1,26 @@ +# Breadcrumb +
    + +The breadcrumb allows quickly viewing the note hierarchy of the current note and navigating through it. + +It is part of the Status bar, displayed in the bottom-left of the screen. + +## Layout and Interaction + +* If a note or workspace is hoisted, a badge will appear on the left-most side. + * Clicking on the badge will un-hoist the note/workspace. +* The left-most icon represents the root note, or the hoisted note or workspace. + * Clicking the icon will jump to the root note. + * Right clicking the icon will display a menu that allows opening the note in a new tab, split, etc. +* Each segment shows the title of a note in the current note hierarchy. + * Clicking the icon will jump to that note. + * Right clicking will open a menu with multiple options such as opening the note in a different tab/split/window, hoisting, moving/cloning the note, duplicating as well as changing the color of the note. +* Clicking the arrow next to each segment will reveal the child notes of the segment on the left. + * Clicking on an icon will navigate to that particular note. + * It's also possible to create a new child note from here. + * The menu can optionally hide the archived notes. +* If the current note is deep within a hierarchy, the segments will collapse into a \[…\] button in order not to occupy too much space. + * Clicking this button will display each collapsed entry as a menu item. Clicking on it will navigate to that particular note. +* Right clicking on an empty space to the right of the breadcrumb (before the other status bar items) will reveal another menu that allows: + * Toggling whether archived notes are displayed in the breadcrumb and in the note tree. + * Copying the current note path to clipboard. \ No newline at end of file diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb_image.png b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb_image.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2f36ae2ecc5de8f27527172960e336c54504d9 GIT binary patch literal 7397 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 is displayed which indicates the current note as well as its parent notes and allows for quick navigation throughout the hierarchy. + +On the right side, specific sections will show depending on the type of the current note. + +1. For code notes, the language mode of the note is indicated (e.g. JavaScript, plain text), as well as allowing easy switching to another mode. +2. For text notes, the content language is displayed and can be changed, thus configuring the spell-check and the right-to-left support. + 1. Note that this applies to the entire note and not the selection, unlike some text editors. +3. If a note is placed in multiple places in the tree (cloned), the number of the note paths will be displayed. + 1. Clicking it will reveal the full list of note paths and a button to place it somewhere else. +4. If a note has attachments, their number will be displayed. + 1. Clicking on it will reveal the list of attachments in a new tab. +5. If a note is linked from other text notes (backlinks), the number of backlinks will be displayed. + 1. Clicking on it will show the list of notes that link to this note, as well as an excerpt of where the note is referenced. + +Regardless of note type, the following items will always be displayed if there is a note: + +1. Note info, which displays: + 1. The creation/modification date of the note. + 2. The type and MIME of the note. + 3. The note ID. + 4. An estimation of the note size of the note itself and its children. + 5. A button to show Similar notes. \ No newline at end of file diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout_image.png b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout_image.png new file mode 100644 index 0000000000000000000000000000000000000000..12f5e0d5f0a5e4470faf5ab5ae73473d2c2834a6 GIT binary patch literal 10183 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 Layout, the button area is populated by some more buttons that are specific to the current note. For example, for Image and File notes, the download and copy buttons were relocated there. \ No newline at end of file From f3b274650e736d2acb27fe8fe9bf7b95106b97ee Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 22:33:50 +0200 Subject: [PATCH 567/873] docs(user): mark new layout in feature highlights --- .../User Guide/Feature Highlights.html | 156 ++++++++++-------- docs/User Guide/!!!meta.json | 14 ++ .../User Guide/Feature Highlights.md | 12 +- 3 files changed, 105 insertions(+), 77 deletions(-) diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Feature Highlights.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Feature Highlights.html index 523a2e819d..86f103977a 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Feature Highlights.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Feature Highlights.html @@ -3,81 +3,93 @@ of brevity, beta versions are skipped and the features gathered to the nearest stable version.

      -
    • v0.97.0: +
    • v0.101.0:
        -
      • Books are now Collections.
      • -
      • Table View is +
      • New Layout has + been introduced, making significant modifications to the UI/UX such as + integrating the ribbon and the floating buttons into other UI elements + and introducing new functionality such as the Breadcrumb.
      • +
      +
    • +
    • v0.97.0: +
        +
      • Books are now Collections.
      • +
      • Table is a new collection type displaying notes and attributes in an editable grid.
      • -
      • Quick edit is - introduced, adding a new way to edit notes in a popup instead of opening - a new tab. It also integrates well with Collections.
      • +
      • Quick edit is + introduced, adding a new way to edit notes in a popup instead of opening + a new tab. It also integrates well with Collections.
      -
    • -
    • v0.96.0: -
        -
      • Text gain - premium features thanks to a collaboration with the CKEditor team: - -
      • + +
      • v0.96.0: + +
      • +
      • v0.95.0: +
          +
        • A more friendly theme was introduced for Sharing, with search, expandable tree, night + mode and more.
        • +
        +
      • +
      • v0.94.0: +
          +
        • Added integration with [missing note] (using + self-hosted LLMs such as Ollama or industry standards such as ChatGPT).
        • +
        +
      • +
      • v0.92.5: + +
      • +
      • v0.92.4: + -
      • -
      • v0.95.0: -
          -
        • A more friendly theme was introduced for Sharing, with search, expandable tree, night - mode and more.
        • + +
        • v0.91.5: +
            +
          • Significant improvements for mobile.
          • +
          • Footnotes are + now supported in Text notes.
          • +
          • Mermaid diagrams can now be inserted inline within Text notes.
          • +
          • The TriliumNext theme is introduced, bringing a more modern design to + the application.
          • +
          • Geo Map, displaying + notes as markers on a geographical map for easy trip planning.
          -
        • -
        • v0.94.0: -
            -
          • Added integration with AI (using - self-hosted LLMs such as Ollama or industry standards such as ChatGPT).
          • -
          -
        • -
        • v0.92.5: - -
        • -
        • v0.92.4: - -
        • -
        • v0.91.5: -
            -
          • Significant improvements for mobile.
          • -
          • Footnotes are - now supported in Text notes.
          • -
          • Mermaid diagrams can now be inserted inline within Text notes.
          • -
          • The TriliumNext theme is introduced, bringing a more modern design to - the application.
          • -
          • Geo Map View, - displaying notes as markers on a geographical map for easy trip planning.
          • -
          -
        • -
        • v0.90.8: -
            -
          • A new note type was introduced: Mind Map -
          • -
          -
        • + +
        • v0.90.8: +
            +
          • A new note type was introduced: Mind Map +
          • +
          +
        \ No newline at end of file diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json index 2e16e2add5..e1b7b80476 100644 --- a/docs/User Guide/!!!meta.json +++ b/docs/User Guide/!!!meta.json @@ -237,6 +237,20 @@ "value": "feature-highlights", "isInheritable": false, "position": 170 + }, + { + "type": "relation", + "name": "internalLink", + "value": "IjZS7iK5EXtb", + "isInheritable": false, + "position": 180 + }, + { + "type": "relation", + "name": "internalLink", + "value": "I6p2a06hdnL6", + "isInheritable": false, + "position": 190 } ], "format": "markdown", diff --git a/docs/User Guide/User Guide/Feature Highlights.md b/docs/User Guide/User Guide/Feature Highlights.md index 59526c8b13..831e5c0a8e 100644 --- a/docs/User Guide/User Guide/Feature Highlights.md +++ b/docs/User Guide/User Guide/Feature Highlights.md @@ -1,9 +1,11 @@ # Feature Highlights This section presents the most important changes by version. For a full set of changes, please consult the change log of each release. For purposes of brevity, beta versions are skipped and the features gathered to the nearest stable version. +* v0.101.0: + * A New Layout has been introduced, making significant modifications to the UI/UX such as integrating the ribbon and the floating buttons into other UI elements and introducing new functionality such as the Breadcrumb. * v0.97.0: * Books are now Collections. - * Table View is a new collection type displaying notes and attributes in an editable grid. + * Table is a new collection type displaying notes and attributes in an editable grid. * Quick edit is introduced, adding a new way to edit notes in a popup instead of opening a new tab. It also integrates well with Collections. * v0.96.0: * Text gain premium features thanks to a collaboration with the CKEditor team: @@ -12,21 +14,21 @@ This section presents the most important changes by version. For a full set of c * v0.95.0: * A more friendly theme was introduced for Sharing, with search, expandable tree, night mode and more. * v0.94.0: - * Added integration with AI (using self-hosted LLMs such as Ollama or industry standards such as ChatGPT). + * Added integration with [missing note] (using self-hosted LLMs such as Ollama or industry standards such as ChatGPT). * v0.92.5: * Windows binaries are now signed. * Multi-Factor Authentication was introduced. * v0.92.4: * macOS binaries are now signed. * Text notes can now have adjustable Content language & Right-to-left support. - * Export as PDF + * Printing & Exporting as PDF * Zen mode - * Calendar View, allowing notes to be displayed in a monthly grid based on start and end dates. + * Calendar, allowing notes to be displayed in a monthly grid based on start and end dates. * v0.91.5: * Significant improvements for mobile. * Footnotes are now supported in Text notes. * Mermaid diagrams can now be inserted inline within Text notes. * The TriliumNext theme is introduced, bringing a more modern design to the application. - * Geo Map View, displaying notes as markers on a geographical map for easy trip planning. + * Geo Map, displaying notes as markers on a geographical map for easy trip planning. * v0.90.8: * A new note type was introduced: Mind Map \ No newline at end of file From 851169e061d55853a75d8aa9f47a2b6f04993b00 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 22:39:24 +0200 Subject: [PATCH 568/873] fix(edited_notes): no message if there are no edited notes on a day --- .../client/src/widgets/layout/InlineTitle.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/client/src/widgets/layout/InlineTitle.tsx b/apps/client/src/widgets/layout/InlineTitle.tsx index 6dec10c100..bb043cca25 100644 --- a/apps/client/src/widgets/layout/InlineTitle.tsx +++ b/apps/client/src/widgets/layout/InlineTitle.tsx @@ -328,20 +328,19 @@ function EditedNotes() { function EditedNotesContent({ note }: { note: FNote }) { const editedNotes = useEditedNotes(note); - return ( - <> - {editedNotes?.map(editedNote => ( - - )} - /> - ))} - - ); + return (editedNotes !== undefined && + (editedNotes.length > 0 ? editedNotes?.map(editedNote => ( + + )} + /> + )) : ( +
        {t("edited_notes.no_edited_notes_found")}
        + ))); } //#endregion From 9872a3d522af10cdf30527eb33ef954736b56fea Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 22:46:25 +0200 Subject: [PATCH 569/873] feat(call_to_action): add more info button for new layout --- apps/client/src/translations/en/translation.json | 1 + .../src/widgets/dialogs/call_to_action_definitions.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 19c2940445..0f1e12781d 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2111,6 +2111,7 @@ "background_effects_button": "Enable background effects", "new_layout_title": "New layout", "new_layout_message": "We’ve introduced a modernized layout for Trilium. The ribbon has been removed and seamlessly integrated into the main interface, with a new status bar and expandable sections (such as promoted attributes) taking over key functions.\n\nThe new layout is enabled by default, and can be temporarily disabled via Options → Appearance.", + "new_layout_button": "More info", "dismiss": "Dismiss" }, "settings": { diff --git a/apps/client/src/widgets/dialogs/call_to_action_definitions.ts b/apps/client/src/widgets/dialogs/call_to_action_definitions.ts index 056672b169..d783b1dbba 100644 --- a/apps/client/src/widgets/dialogs/call_to_action_definitions.ts +++ b/apps/client/src/widgets/dialogs/call_to_action_definitions.ts @@ -1,3 +1,4 @@ +import appContext from "../../components/app_context"; import { t } from "../../services/i18n"; import options from "../../services/options"; import utils from "../../services/utils"; @@ -50,7 +51,13 @@ const CALL_TO_ACTIONS: CallToAction[] = [ title: t("call_to_action.new_layout_title"), message: t("call_to_action.new_layout_message"), enabled: () => true, - buttons: [] + buttons: [ + { + + text: t("call_to_action.new_layout_button"), + onClick: () => appContext.tabManager.openInNewTab("_help_IjZS7iK5EXtb", "_help", true) + } + ] }, { id: "background_effects", From c9fae88a86caec99112f6c07ebf187d98f44c4f8 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Wed, 17 Dec 2025 22:48:47 +0200 Subject: [PATCH 570/873] style/note: add custom note color CSS variables on the split containers --- apps/client/src/widgets/note_wrapper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_wrapper.ts b/apps/client/src/widgets/note_wrapper.ts index d743d9ffa0..9eaed96c77 100644 --- a/apps/client/src/widgets/note_wrapper.ts +++ b/apps/client/src/widgets/note_wrapper.ts @@ -63,6 +63,7 @@ export default class NoteWrapperWidget extends FlexContainer { this.$widget.addClass(utils.getNoteTypeClass(note.type)); this.$widget.addClass(utils.getMimeTypeClass(note.mime)); this.$widget.addClass(`view-mode-${this.noteContext?.viewScope?.viewMode ?? "default"}`); + this.$widget.addClass(note.getColorClass()); this.$widget.toggleClass(["bgfx", "options"], note.isOptions()); this.$widget.toggleClass("protected", note.isProtected); @@ -93,7 +94,7 @@ export default class NoteWrapperWidget extends FlexContainer { const noteId = this.noteContext?.noteId; if ( loadResults.isNoteReloaded(noteId) || - loadResults.getAttributeRows().find((attr) => attr.type === "label" && ["cssClass", "language", "viewType"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.noteContext?.note)) + loadResults.getAttributeRows().find((attr) => attr.type === "label" && ["cssClass", "language", "viewType", "color"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.noteContext?.note)) ) { this.refresh(); } From 616af1502f6def57df0392c4750f228897867266 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 23:01:44 +0200 Subject: [PATCH 571/873] feat(layout/right_pane): create empty container --- apps/client/src/layouts/desktop_layout.tsx | 4 +++- apps/client/src/widgets/sidebar/RightPanelContainer.tsx | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/sidebar/RightPanelContainer.tsx diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 4f6f7e5af6..013460723f 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -44,6 +44,7 @@ import Ribbon from "../widgets/ribbon/Ribbon.jsx"; import ScrollPadding from "../widgets/scroll_padding.js"; import SearchResult from "../widgets/search_result.jsx"; import SharedInfo from "../widgets/shared_info.jsx"; +import RightPanelContainer from "../widgets/sidebar/RightPanelContainer.jsx"; import SqlResults from "../widgets/sql_result.js"; import SqlTableSchemas from "../widgets/sql_table_schemas.js"; import TabRowWidget from "../widgets/tab_row.js"; @@ -174,12 +175,13 @@ export default class DesktopLayout { .child(...this.customWidgets.get("center-pane")) ) - .child( + .optChild(!isNewLayout, new RightPaneContainer() .child(new TocWidget()) .child(new HighlightsListWidget()) .child(...this.customWidgets.get("right-pane")) ) + .optChild(isNewLayout, ) ) .optChild(!launcherPaneIsHorizontal && isNewLayout, ) ) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx new file mode 100644 index 0000000000..fc379c5a9a --- /dev/null +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -0,0 +1,9 @@ +//! This is currently only used for the new layout. + +export default function RightPanelContainer() { + return ( +
        + Hi there. +
        + ); +} From f46de50f17efb271b8e12f72deb4f36fece4d01d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 23:03:57 +0200 Subject: [PATCH 572/873] refactor(layout/right_pane): CSS for container --- apps/client/src/widgets/sidebar/RightPanelContainer.css | 3 +++ apps/client/src/widgets/sidebar/RightPanelContainer.tsx | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/sidebar/RightPanelContainer.css diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css new file mode 100644 index 0000000000..65cff510e6 --- /dev/null +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -0,0 +1,3 @@ +body.experimental-feature-new-layout #right-pane { + width: 300px; +} diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index fc379c5a9a..b00386a101 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -1,8 +1,9 @@ //! This is currently only used for the new layout. +import "./RightPanelContainer.css"; export default function RightPanelContainer() { return ( -
        +
        Hi there.
        ); From dac923e45d9a225d6f1f030f227f9b656a612948 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 23:17:25 +0200 Subject: [PATCH 573/873] chore(layout/right_pane): bring back resizer --- .../widgets/sidebar/RightPanelContainer.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index b00386a101..8b57147ce0 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -1,7 +1,28 @@ //! This is currently only used for the new layout. import "./RightPanelContainer.css"; +import Split from "@triliumnext/split.js"; +import { useEffect } from "preact/hooks"; + +import options from "../../services/options"; +import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; + +const MIN_WIDTH_PERCENT = 5; + export default function RightPanelContainer() { + useEffect(() => { + // We are intentionally omitting useTriliumOption to avoid re-render due to size change. + const rightPaneWidth = Math.max(MIN_WIDTH_PERCENT, options.getInt("rightPaneWidth") ?? MIN_WIDTH_PERCENT); + const splitInstance = Split(["#center-pane", "#right-pane"], { + sizes: [100 - rightPaneWidth, rightPaneWidth], + gutterSize: DEFAULT_GUTTER_SIZE, + minSize: [300, 180], + rtl: glob.isRtl, + onDragEnd: (sizes) => options.save("rightPaneWidth", Math.round(sizes[1])) + }); + return () => splitInstance.destroy(); + }, []); + return (
        Hi there. From 98ed442d27cc763883ea4630d479099b9fd6ab1b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 23:19:42 +0200 Subject: [PATCH 574/873] chore(layout/right_pane): empty table of contents --- .../src/widgets/sidebar/RightPanelContainer.tsx | 3 ++- apps/client/src/widgets/sidebar/TableOfContents.tsx | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/sidebar/TableOfContents.tsx diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 8b57147ce0..34ec8360c9 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -6,6 +6,7 @@ import { useEffect } from "preact/hooks"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; +import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; @@ -25,7 +26,7 @@ export default function RightPanelContainer() { return (
        - Hi there. +
        ); } diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx new file mode 100644 index 0000000000..23e08506d8 --- /dev/null +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -0,0 +1,12 @@ +import { t } from "../../services/i18n"; +import RightPanelWidget from "./RightPanelWidget"; + +export default function TableOfContents() { + + return ( + + Toc is here. + + ); + +} From 2e484a11e635908ebcb644b143cec9f6b2f0d960 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Dec 2025 23:36:26 +0200 Subject: [PATCH 575/873] feat(layout/right_pane): basic store to read content without blob --- apps/client/src/widgets/react/NoteStore.ts | 33 +++++++++++++++++++ apps/client/src/widgets/react/hooks.tsx | 13 +++++++- .../widgets/sidebar/RightPanelContainer.tsx | 6 +++- .../src/widgets/sidebar/TableOfContents.tsx | 7 ++-- 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 apps/client/src/widgets/react/NoteStore.ts diff --git a/apps/client/src/widgets/react/NoteStore.ts b/apps/client/src/widgets/react/NoteStore.ts new file mode 100644 index 0000000000..6f34c6d23f --- /dev/null +++ b/apps/client/src/widgets/react/NoteStore.ts @@ -0,0 +1,33 @@ +type Listener = () => void; + +class NoteSavedDataStore { + private data = new Map(); + private listeners = new Map>(); + + get(noteId: string) { + return this.data.get(noteId); + } + + set(noteId: string, value: string) { + this.data.set(noteId, value); + this.listeners.get(noteId)?.forEach(l => l()); + } + + subscribe(noteId: string, listener: Listener) { + let set = this.listeners.get(noteId); + if (!set) { + set = new Set(); + this.listeners.set(noteId, set); + } + set.add(listener); + + return () => { + set!.delete(listener); + if (set!.size === 0) { + this.listeners.delete(noteId); + } + }; + } +} + +export const noteSavedDataStore = new NoteSavedDataStore(); diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index e43ee4c8de..0a91772098 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -2,7 +2,7 @@ import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } f import { Tooltip } from "bootstrap"; import Mark from "mark.js"; import { RefObject, VNode } from "preact"; -import { CSSProperties } from "preact/compat"; +import { CSSProperties, useSyncExternalStore } from "preact/compat"; import { MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; import appContext, { EventData, EventNames } from "../../components/app_context"; @@ -25,6 +25,7 @@ import utils, { escapeRegExp, randomString, reloadFrontendApp } from "../../serv import BasicWidget, { ReactWrappedWidget } from "../basic_widget"; import NoteContextAwareWidget from "../note_context_aware_widget"; import { DragData } from "../note_tree"; +import { noteSavedDataStore } from "./NoteStore"; import { NoteContextContext, ParentComponent, refToJQuerySelector } from "./react_utils"; export function useTriliumEvent(eventName: T, handler: (data: EventData) => void) { @@ -112,6 +113,7 @@ export function useEditorSpacedUpdate({ note, noteContext, getData, onContentCha protected_session_holder.touchProtectedSessionIfNecessary(note); await server.put(`notes/${note.noteId}/data`, data, parentComponent?.componentId); + noteSavedDataStore.set(note.noteId, data.content); dataSaved?.(data); }; }, [ note, getData, dataSaved ]); @@ -120,6 +122,7 @@ export function useEditorSpacedUpdate({ note, noteContext, getData, onContentCha // React to note/blob changes. useEffect(() => { if (!blob) return; + noteSavedDataStore.set(note.noteId, blob.content); spacedUpdate.allowUpdateWithoutChange(() => onContentChange(blob.content)); }, [ blob ]); @@ -151,6 +154,14 @@ export function useEditorSpacedUpdate({ note, noteContext, getData, onContentCha return spacedUpdate; } +export function useNoteSavedData(noteId: string | undefined) { + return useSyncExternalStore( + (cb) => noteId ? noteSavedDataStore.subscribe(noteId, cb) : () => {}, + () => noteId ? noteSavedDataStore.get(noteId) : undefined + ); +} + + /** * Allows a React component to read and write a Trilium option, while also watching for external changes. * diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 34ec8360c9..7b838c5185 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -6,11 +6,13 @@ import { useEffect } from "preact/hooks"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; +import { useActiveNoteContext } from "../react/hooks"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; export default function RightPanelContainer() { + const { note } = useActiveNoteContext(); useEffect(() => { // We are intentionally omitting useTriliumOption to avoid re-render due to size change. const rightPaneWidth = Math.max(MIN_WIDTH_PERCENT, options.getInt("rightPaneWidth") ?? MIN_WIDTH_PERCENT); @@ -26,7 +28,9 @@ export default function RightPanelContainer() { return (
        - + {note && <> + + }
        ); } diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 23e08506d8..01135d4407 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -1,11 +1,14 @@ +import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; +import { useNoteSavedData } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; -export default function TableOfContents() { +export default function TableOfContents({ note }: { note: FNote }) { + const content = useNoteSavedData(note.noteId); return ( - Toc is here. + {content?.length} ); From 3a46a9fbc3e4885ecb72ab83d300c4e443c317f1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 00:16:03 +0200 Subject: [PATCH 576/873] chore(toc): attempt to read using CKEditor API --- .../src/widgets/sidebar/TableOfContents.tsx | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 01135d4407..2fc3db61e7 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -1,15 +1,62 @@ +import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5"; +import { useEffect, useState } from "preact/hooks"; + import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; -import { useNoteSavedData } from "../react/hooks"; +import { useActiveNoteContext, useNoteSavedData, useTriliumEvent } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; -export default function TableOfContents({ note }: { note: FNote }) { - const content = useNoteSavedData(note.noteId); +interface CKHeading { + level: number; + text: string; + element: ModelElement; +} + +export default function TableOfContents() { + const { ntxId } = useActiveNoteContext(); + const [ textEditor, setTextEditor ] = useState(null); + const [ headings, setHeadings ] = useState([]); + + // Populate the cache with the toolbar of every note context. + useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => { + if (eventNtxId !== ntxId) return; + setTextEditor(editor); + }); + + useEffect(() => { + if (!textEditor) return; + const headings = extractTocFromTextEditor(textEditor); + setHeadings(headings); + }, [ textEditor ]); + + console.log("Render with ", headings); return ( - {content?.length} + {headings.map(heading => ( +
      • {heading.text}
      • + ))} ); } + +function extractTocFromTextEditor(editor: CKTextEditor) { + const headings: CKHeading[] = []; + + const root = editor.model.document.getRoot(); + if (!root) return []; + + for (const { type, item } of editor.model.createRangeIn(root).getWalker()) { + if (type !== "elementStart" || !item.is('element') || !item.name.startsWith('heading')) continue; + + const level = Number(item.name.replace( 'heading', '' )); + const text = Array.from( item.getChildren() ) + .map( c => c.is( '$text' ) ? c.data : '' ) + .join( '' ); + + headings.push({ level, text, element: item }); + } + + return headings; +} From eceb7179b8fe761b719dda81dc5f016af51cbc63 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 00:20:48 +0200 Subject: [PATCH 577/873] style(attachment): code block cuts off card --- apps/client/src/widgets/type_widgets/Attachment.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/Attachment.css b/apps/client/src/widgets/type_widgets/Attachment.css index 19c5757a28..cab6290713 100644 --- a/apps/client/src/widgets/type_widgets/Attachment.css +++ b/apps/client/src/widgets/type_widgets/Attachment.css @@ -22,12 +22,13 @@ margin-bottom: 20px; display: flex; flex-direction: column; + overflow: hidden; } .attachment-title { font-size: 1.1rem; margin: 0; - + a { color: inherit !important; } @@ -72,7 +73,7 @@ .attachment-detail-wrapper.list-view { border-radius: 12px; - background-color: var(--card-background-color); + background-color: var(--card-background-color); padding: 0 6px; box-shadow: var(--card-box-shadow); } @@ -152,4 +153,4 @@ background-color: transparent !important; pointer-events: none; /* makes it unclickable */ } -/* #endregion */ \ No newline at end of file +/* #endregion */ From b2bcbdde3f95dea6e27410941ce3da854828979a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 00:25:16 +0200 Subject: [PATCH 578/873] style(attachment): top padding not matching side padding --- apps/client/src/widgets/type_widgets/Attachment.css | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/Attachment.css b/apps/client/src/widgets/type_widgets/Attachment.css index cab6290713..ce0ae88561 100644 --- a/apps/client/src/widgets/type_widgets/Attachment.css +++ b/apps/client/src/widgets/type_widgets/Attachment.css @@ -74,7 +74,7 @@ .attachment-detail-wrapper.list-view { border-radius: 12px; background-color: var(--card-background-color); - padding: 0 6px; + padding: 6px 6px 0 6px; box-shadow: var(--card-box-shadow); } @@ -127,15 +127,6 @@ /* #endregion */ /* #region Attachment actions */ -.attachment-actions { - width: 35px; - height: 35px; -} - -.attachment-actions .select-button { - position: relative; - top: 3px; -} .attachment-actions .dropdown-menu { width: 20em; From 094f77b1afc1062f79f927a52638aa8857f1797a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 00:38:40 +0200 Subject: [PATCH 579/873] chore(toc): react to changes --- .../src/widgets/sidebar/TableOfContents.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 2fc3db61e7..16c4102bf5 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -1,9 +1,8 @@ import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5"; import { useEffect, useState } from "preact/hooks"; -import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; -import { useActiveNoteContext, useNoteSavedData, useTriliumEvent } from "../react/hooks"; +import { useActiveNoteContext, useTriliumEvent } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; interface CKHeading { @@ -26,10 +25,26 @@ export default function TableOfContents() { useEffect(() => { if (!textEditor) return; const headings = extractTocFromTextEditor(textEditor); - setHeadings(headings); - }, [ textEditor ]); - console.log("Render with ", headings); + // React to changes. + const changeCallback = () => { + const changes = textEditor.model.document.differ.getChanges(); + + const affectsHeadings = changes.some( change => { + return ( + change.type === 'insert' || change.type === 'remove' || (change.type === 'attribute' && change.attributeKey === 'headingLevel') + ); + }); + if (affectsHeadings) { + setHeadings(extractTocFromTextEditor(textEditor)); + } + }; + + textEditor.model.document.on("change:data", changeCallback); + setHeadings(headings); + + return () => textEditor.model.document.off("change:data", changeCallback); + }, [ textEditor ]); return ( From 76c16f3a622cb20c9029ffa7ba5c889d6fcd275c Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 18 Dec 2025 00:53:09 +0200 Subject: [PATCH 580/873] style/note icon: apply the note custom color on the icon --- apps/client/src/stylesheets/theme-next-dark.css | 5 +++++ apps/client/src/stylesheets/theme-next-light.css | 5 +++++ apps/client/src/widgets/note_icon.css | 6 +++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 248efee756..87ead0e47c 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -325,3 +325,8 @@ body .todo-list input[type="checkbox"]:not(:checked):before { .use-note-color { --custom-color: var(--dark-theme-custom-color); } + +.note-split.with-hue { + --note-icon-custom-background-color: hsl(var(--custom-color-hue), 8%, 29.4%); + --note-icon-custom-color: hsl(var(--custom-color-hue), 89%, 78%); +} \ No newline at end of file diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index d1364ebd48..d24d002cce 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -296,3 +296,8 @@ --modal-border-color: hsl(var(--custom-color-hue), 33%, 41%); --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%); } + +.note-split.with-hue { + --note-icon-custom-background-color: hsl(var(--custom-color-hue), 50%, 86.7%); + --note-icon-custom-color: hsl(var(--custom-color-hue), 36%, 23%); +} \ No newline at end of file diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index 7168daf5bf..7bcb794a43 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -90,7 +90,7 @@ body.experimental-feature-new-layout { &::before { position: relative; z-index: 1; - color: var(--note-icon-button-color); + color: var(--note-icon-custom-color, var(--note-icon-button-color)); } /* The background circle */ @@ -99,11 +99,11 @@ body.experimental-feature-new-layout { position: absolute; inset: 0; border-radius: 50%; - background: var(--note-icon-button-background-color); + background: var(--note-icon-custom-background-color, var(--note-icon-button-background-color)); } &:focus-visible { - outline: 2px solid var(--note-icon-button-color); + outline: 2px solid var(--note-icon-custom-color, var(--note-icon-button-color)); } &:hover:not(.bx-empty:disabled)::after { From ea2dd0293fa8590df1f03eb9504c4819d1ee48bd Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 18 Dec 2025 02:05:49 +0200 Subject: [PATCH 581/873] style/note icon: tweak colors --- apps/client/src/stylesheets/theme-next-light.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index d24d002cce..db01775798 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -298,6 +298,6 @@ } .note-split.with-hue { - --note-icon-custom-background-color: hsl(var(--custom-color-hue), 50%, 86.7%); + --note-icon-custom-background-color: hsl(var(--custom-color-hue), 53.8%, 87.3%); --note-icon-custom-color: hsl(var(--custom-color-hue), 36%, 23%); } \ No newline at end of file From 7cfc67cf9fe0e3a172032a30af4edf0334de9592 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 18 Dec 2025 03:14:21 +0200 Subject: [PATCH 582/873] style/note icon: tweak colors --- apps/client/src/stylesheets/theme-next-dark.css | 4 ++-- apps/client/src/stylesheets/theme-next-light.css | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 87ead0e47c..574476806a 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -327,6 +327,6 @@ body .todo-list input[type="checkbox"]:not(:checked):before { } .note-split.with-hue { - --note-icon-custom-background-color: hsl(var(--custom-color-hue), 8%, 29.4%); - --note-icon-custom-color: hsl(var(--custom-color-hue), 89%, 78%); + --note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%); + --note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%); } \ No newline at end of file diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index db01775798..30396e8544 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -298,6 +298,11 @@ } .note-split.with-hue { + /* --note-icon-custom-background-color: hsl(var(--custom-color-hue), 53.8%, 87.3%); --note-icon-custom-color: hsl(var(--custom-color-hue), 36%, 23%); + */ + --note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%); + --note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%); + } \ No newline at end of file From ee6f988c35ba29aca211b8f01baf88b78cbf80cd Mon Sep 17 00:00:00 2001 From: Jason Wasem <46989741+Soein@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:01:13 +0800 Subject: [PATCH 583/873] refactor(search): simplify null check and use join for text concatenation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根据代码审查建议优化代码: - 移除多余的 `elements &&` 检查,因为 Array.isArray() 本身可处理 null/undefined - 使用 `join(" ")` 替代 `toString()` 以确保文本元素用空格分隔,更适合全文搜索 - 移除显式类型声明,让 TypeScript 自动推断 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../services/search/expressions/note_content_fulltext.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/server/src/services/search/expressions/note_content_fulltext.ts b/apps/server/src/services/search/expressions/note_content_fulltext.ts index 21fd135c94..89ba1bc984 100644 --- a/apps/server/src/services/search/expressions/note_content_fulltext.ts +++ b/apps/server/src/services/search/expressions/note_content_fulltext.ts @@ -315,15 +315,15 @@ class NoteContentFulltextExp extends Expression { [key: string]: any; // Other properties that may exist } - let canvasContent = JSON.parse(content); - const elements: Element[] = canvasContent.elements; + const canvasContent = JSON.parse(content); + const elements = canvasContent.elements; - if (elements && Array.isArray(elements)) { + if (Array.isArray(elements)) { const texts = elements .filter((element: Element) => element.type === "text" && element.text) // Filter for 'text' type elements with a 'text' property .map((element: Element) => element.text!); // Use `!` to assert `text` is defined after filtering - content = normalize(texts.toString()); + content = normalize(texts.join(" ")); } else { content = ""; } From 97a3e439d290c5666cc375e8e0b90a9bafa68a41 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 09:58:10 +0200 Subject: [PATCH 584/873] refactor(toc): decouple CKEditor TOC --- .../src/widgets/sidebar/TableOfContents.tsx | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 16c4102bf5..1bbdb225e5 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -2,7 +2,7 @@ import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5"; import { useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; -import { useActiveNoteContext, useTriliumEvent } from "../react/hooks"; +import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTriliumEvent } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; interface CKHeading { @@ -12,11 +12,22 @@ interface CKHeading { } export default function TableOfContents() { + const { note, noteContext } = useActiveNoteContext(); + const noteType = useNoteProperty(note, "type"); + const { isReadOnly } = useIsNoteReadOnly(note, noteContext); + + return ( + + {noteType === "text" && !isReadOnly && } + + ); +} + +function EditableTextTableOfContents() { const { ntxId } = useActiveNoteContext(); const [ textEditor, setTextEditor ] = useState(null); const [ headings, setHeadings ] = useState([]); - // Populate the cache with the toolbar of every note context. useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => { if (eventNtxId !== ntxId) return; setTextEditor(editor); @@ -46,14 +57,18 @@ export default function TableOfContents() { return () => textEditor.model.document.off("change:data", changeCallback); }, [ textEditor ]); - return ( - - {headings.map(heading => ( -
      • {heading.text}
      • - ))} -
        - ); + return ; +} +function AbstractTableOfContents({ headings }: { + headings: { + level: number; + text: string; + }[]; +}) { + return headings.map(heading => ( +
      • {heading.text}
      • + )); } function extractTocFromTextEditor(editor: CKTextEditor) { From 60342c0f6f6c290106f1ca48292bf871b21bb2d1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 10:08:10 +0200 Subject: [PATCH 585/873] fix(toc): not working on note switch --- apps/client/src/widgets/react/hooks.tsx | 30 +++++++++++++++++++ .../src/widgets/sidebar/TableOfContents.tsx | 15 ++++------ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 0a91772098..b3bd6cf293 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -1,3 +1,4 @@ +import { CKTextEditor } from "@triliumnext/ckeditor5"; import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons"; import { Tooltip } from "bootstrap"; import Mark from "mark.js"; @@ -25,6 +26,7 @@ import utils, { escapeRegExp, randomString, reloadFrontendApp } from "../../serv import BasicWidget, { ReactWrappedWidget } from "../basic_widget"; import NoteContextAwareWidget from "../note_context_aware_widget"; import { DragData } from "../note_tree"; +import CKEditor from "./CKEditor"; import { noteSavedDataStore } from "./NoteStore"; import { NoteContextContext, ParentComponent, refToJQuerySelector } from "./react_utils"; @@ -1085,3 +1087,31 @@ export function useNoteColorClass(note: FNote | null | undefined) { }, [ color, note ]); return colorClass; } + +export function useTextEditor(noteContext: NoteContext | null | undefined) { + const [ textEditor, setTextEditor ] = useState(null); + const requestIdRef = useRef(0); + + // React to note context change and initial state. + useEffect(() => { + if (!noteContext) { + setTextEditor(null); + return; + } + + const requestId = ++requestIdRef.current; + noteContext.getTextEditor((textEditor) => { + // Prevent stale async. + if (requestId !== requestIdRef.current) return; + setTextEditor(textEditor); + }); + }, [ noteContext ]); + + // React to editor initializing. + useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => { + if (eventNtxId !== noteContext?.ntxId) return; + setTextEditor(editor); + }); + + return textEditor; +} diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 1bbdb225e5..14378248bd 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -2,7 +2,7 @@ import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5"; import { useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; -import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTriliumEvent } from "../react/hooks"; +import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor, useTriliumEvent } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; interface CKHeading { @@ -24,18 +24,13 @@ export default function TableOfContents() { } function EditableTextTableOfContents() { - const { ntxId } = useActiveNoteContext(); - const [ textEditor, setTextEditor ] = useState(null); + const { note, noteContext } = useActiveNoteContext(); + const textEditor = useTextEditor(noteContext); const [ headings, setHeadings ] = useState([]); - useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => { - if (eventNtxId !== ntxId) return; - setTextEditor(editor); - }); - useEffect(() => { if (!textEditor) return; - const headings = extractTocFromTextEditor(textEditor); + const headings = extractTocFromTextEditor(textEditor); // React to changes. const changeCallback = () => { @@ -55,7 +50,7 @@ function EditableTextTableOfContents() { setHeadings(headings); return () => textEditor.model.document.off("change:data", changeCallback); - }, [ textEditor ]); + }, [ textEditor, note ]); return ; } From 87a98201b4ba0b699cba70466c55ce23cc61e13b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 10:28:35 +0200 Subject: [PATCH 586/873] chore(toc): reintroduce hierarchy --- .../src/widgets/sidebar/TableOfContents.tsx | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 14378248bd..b4391b6787 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -2,15 +2,22 @@ import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5"; import { useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; -import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor, useTriliumEvent } from "../react/hooks"; +import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; -interface CKHeading { +interface RawHeading { level: number; text: string; +} + +interface CKHeading extends RawHeading { element: ModelElement; } +interface HeadingsWithNesting extends RawHeading { + children: HeadingsWithNesting[]; +} + export default function TableOfContents() { const { note, noteContext } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); @@ -56,16 +63,48 @@ function EditableTextTableOfContents() { } function AbstractTableOfContents({ headings }: { - headings: { - level: number; - text: string; - }[]; + headings: RawHeading[]; }) { - return headings.map(heading => ( -
      • {heading.text}
      • - )); + const nestedHeadings = buildHeadingTree(headings); + return nestedHeadings.map(heading => ); } +function TableOfContentsHeading({ heading }: { heading: HeadingsWithNesting }) { + return ( +
      • + {heading.text} + {heading.children && ( +
          + {heading.children.map(heading => )} +
        + )} +
      • + ); +} + +function buildHeadingTree(headings: RawHeading[]): HeadingsWithNesting[] { + const root: HeadingsWithNesting = { level: 0, text: "", children: [] }; + const stack: HeadingsWithNesting[] = [root]; + + for (const h of headings) { + const node: HeadingsWithNesting = { ...h, children: [] }; + + // Pop until we find a parent with lower level + while (stack.length > 1 && stack[stack.length - 1].level >= h.level) { + stack.pop(); + } + + // Attach to current parent + stack[stack.length - 1].children.push(node); + + // This node becomes the new parent + stack.push(node); + } + + return root.children; +} + + function extractTocFromTextEditor(editor: CKTextEditor) { const headings: CKHeading[] = []; @@ -85,3 +124,4 @@ function extractTocFromTextEditor(editor: CKTextEditor) { return headings; } + From 73f1b91d341d71945d3f96a94611aab35c91c671 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 10:49:33 +0200 Subject: [PATCH 587/873] chore(toc): reintroduce basic collapse support --- apps/client/src/widgets/react/Icon.tsx | 16 +++- .../src/widgets/sidebar/TableOfContents.css | 82 +++++++++++++++++++ .../src/widgets/sidebar/TableOfContents.tsx | 32 ++++++-- 3 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 apps/client/src/widgets/sidebar/TableOfContents.css diff --git a/apps/client/src/widgets/react/Icon.tsx b/apps/client/src/widgets/react/Icon.tsx index e047a1762b..6d17dc3b98 100644 --- a/apps/client/src/widgets/react/Icon.tsx +++ b/apps/client/src/widgets/react/Icon.tsx @@ -1,8 +1,16 @@ -interface IconProps { +import clsx from "clsx"; +import { HTMLAttributes } from "preact"; + +interface IconProps extends Pick, "className" | "onClick"> { icon?: string; className?: string; } -export default function Icon({ icon, className }: IconProps) { - return -} \ No newline at end of file +export default function Icon({ icon, className, ...restProps }: IconProps) { + return ( + + ); +} diff --git a/apps/client/src/widgets/sidebar/TableOfContents.css b/apps/client/src/widgets/sidebar/TableOfContents.css new file mode 100644 index 0000000000..79c6ee548f --- /dev/null +++ b/apps/client/src/widgets/sidebar/TableOfContents.css @@ -0,0 +1,82 @@ +.toc ol { + position: relative; + overflow: hidden; + padding-inline-start: 0px; + transition: max-height 0.3s ease; +} + +.toc li.collapsed + ol { + display:none; +} + +.toc li + ol:before { + content: ""; + position: absolute; + height: 100%; + border-inline-start: 1px solid var(--main-border-color); + z-index: 10; +} + +.toc li { + display: flex; + position: relative; + list-style: none; + align-items: center; + padding-inline-start: 7px; + cursor: pointer; + text-align: justify; + word-wrap: break-word; + hyphens: auto; +} + +.toc > ol { + --toc-depth-level: 1; +} +.toc > ol > ol { + --toc-depth-level: 2; +} +.toc > ol > ol > ol { + --toc-depth-level: 3; +} +.toc > ol > ol > ol > ol { + --toc-depth-level: 4; +} +.toc > ol > ol > ol > ol > ol { + --toc-depth-level: 5; +} + +.toc > ol ol::before { + inset-inline-start: calc((var(--toc-depth-level) - 2) * 20px + 14px); +} + +.toc li { + padding-inline-start: calc((var(--toc-depth-level) - 1) * 20px + 4px); +} + +.toc li .collapse-button { + display: flex; + position: relative; + width: 21px; + height: 21px; + flex-shrink: 0; + align-items: center; + justify-content: center; + transition: transform 0.3s ease; +} + +.toc li.collapsed .collapse-button { + transform: rotate(-90deg); +} + +.toc li .item-content { + margin-inline-start: 25px; + flex: 1; +} + +.toc li .collapse-button + .item-content { + margin-inline-start: 4px; +} + +.toc li:hover { + font-weight: bold; +} diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index b4391b6787..8ed728c262 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -1,8 +1,12 @@ +import "./TableOfContents.css"; + import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5"; +import clsx from "clsx"; import { useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; +import Icon from "../react/Icon"; import RightPanelWidget from "./RightPanelWidget"; interface RawHeading { @@ -66,19 +70,35 @@ function AbstractTableOfContents({ headings }: { headings: RawHeading[]; }) { const nestedHeadings = buildHeadingTree(headings); - return nestedHeadings.map(heading => ); + return ( + +
          + {nestedHeadings.map(heading => )} +
        +
        + ); } function TableOfContentsHeading({ heading }: { heading: HeadingsWithNesting }) { + const [ collapsed, setCollapsed ] = useState(false); return ( -
      • - {heading.text} + <> +
      • + {heading.children.length > 0 && ( + setCollapsed(!collapsed)} + /> + )} + {heading.text} +
      • {heading.children && ( -
          +
            {heading.children.map(heading => )} -
        + )} - + ); } From 852398426ecb8b15e73306c5a4909de83d9dca14 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 10:56:44 +0200 Subject: [PATCH 588/873] chore(toc): add unique keys to headings --- .../src/widgets/sidebar/TableOfContents.tsx | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 8ed728c262..3d63ef0df6 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -10,6 +10,7 @@ import Icon from "../react/Icon"; import RightPanelWidget from "./RightPanelWidget"; interface RawHeading { + id: string; level: number; text: string; } @@ -73,7 +74,7 @@ function AbstractTableOfContents({ headings }: { return (
          - {nestedHeadings.map(heading => )} + {nestedHeadings.map(heading => )}
        ); @@ -95,7 +96,7 @@ function TableOfContentsHeading({ heading }: { heading: HeadingsWithNesting }) { {heading.children && (
          - {heading.children.map(heading => )} + {heading.children.map(heading => )}
        )} @@ -103,7 +104,7 @@ function TableOfContentsHeading({ heading }: { heading: HeadingsWithNesting }) { } function buildHeadingTree(headings: RawHeading[]): HeadingsWithNesting[] { - const root: HeadingsWithNesting = { level: 0, text: "", children: [] }; + const root: HeadingsWithNesting = { level: 0, text: "", children: [], id: "_root" }; const stack: HeadingsWithNesting[] = [root]; for (const h of headings) { @@ -124,6 +125,7 @@ function buildHeadingTree(headings: RawHeading[]): HeadingsWithNesting[] { return root.children; } +const TOC_ID = 'tocId'; function extractTocFromTextEditor(editor: CKTextEditor) { const headings: CKHeading[] = []; @@ -131,16 +133,25 @@ function extractTocFromTextEditor(editor: CKTextEditor) { const root = editor.model.document.getRoot(); if (!root) return []; - for (const { type, item } of editor.model.createRangeIn(root).getWalker()) { - if (type !== "elementStart" || !item.is('element') || !item.name.startsWith('heading')) continue; + editor.model.change(writer => { + for (const { type, item } of editor.model.createRangeIn(root).getWalker()) { + if (type !== "elementStart" || !item.is('element') || !item.name.startsWith('heading')) continue; - const level = Number(item.name.replace( 'heading', '' )); - const text = Array.from( item.getChildren() ) - .map( c => c.is( '$text' ) ? c.data : '' ) - .join( '' ); + const level = Number(item.name.replace( 'heading', '' )); + const text = Array.from( item.getChildren() ) + .map( c => c.is( '$text' ) ? c.data : '' ) + .join( '' ); - headings.push({ level, text, element: item }); - } + // Assign a unique ID + let tocId = item.getAttribute(TOC_ID) as string | undefined; + if (!tocId) { + tocId = crypto.randomUUID(); + writer.setAttribute(TOC_ID, tocId, item); + } + + headings.push({ level, text, element: item, id: tocId }); + } + }); return headings; } From 41751c205caa8e6110e16a652b2a2a7ec8da891c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 10:58:25 +0200 Subject: [PATCH 589/873] refactor(toc): reorder according to purpose --- .../src/widgets/sidebar/TableOfContents.tsx | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 3d63ef0df6..55178811c2 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -9,16 +9,13 @@ import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor import Icon from "../react/Icon"; import RightPanelWidget from "./RightPanelWidget"; +//#region Generic impl. interface RawHeading { id: string; level: number; text: string; } -interface CKHeading extends RawHeading { - element: ModelElement; -} - interface HeadingsWithNesting extends RawHeading { children: HeadingsWithNesting[]; } @@ -35,38 +32,6 @@ export default function TableOfContents() { ); } -function EditableTextTableOfContents() { - const { note, noteContext } = useActiveNoteContext(); - const textEditor = useTextEditor(noteContext); - const [ headings, setHeadings ] = useState([]); - - useEffect(() => { - if (!textEditor) return; - const headings = extractTocFromTextEditor(textEditor); - - // React to changes. - const changeCallback = () => { - const changes = textEditor.model.document.differ.getChanges(); - - const affectsHeadings = changes.some( change => { - return ( - change.type === 'insert' || change.type === 'remove' || (change.type === 'attribute' && change.attributeKey === 'headingLevel') - ); - }); - if (affectsHeadings) { - setHeadings(extractTocFromTextEditor(textEditor)); - } - }; - - textEditor.model.document.on("change:data", changeCallback); - setHeadings(headings); - - return () => textEditor.model.document.off("change:data", changeCallback); - }, [ textEditor, note ]); - - return ; -} - function AbstractTableOfContents({ headings }: { headings: RawHeading[]; }) { @@ -124,9 +89,47 @@ function buildHeadingTree(headings: RawHeading[]): HeadingsWithNesting[] { return root.children; } +//#endregion +//#region Editable text (CKEditor) const TOC_ID = 'tocId'; +interface CKHeading extends RawHeading { + element: ModelElement; +} + +function EditableTextTableOfContents() { + const { note, noteContext } = useActiveNoteContext(); + const textEditor = useTextEditor(noteContext); + const [ headings, setHeadings ] = useState([]); + + useEffect(() => { + if (!textEditor) return; + const headings = extractTocFromTextEditor(textEditor); + + // React to changes. + const changeCallback = () => { + const changes = textEditor.model.document.differ.getChanges(); + + const affectsHeadings = changes.some( change => { + return ( + change.type === 'insert' || change.type === 'remove' || (change.type === 'attribute' && change.attributeKey === 'headingLevel') + ); + }); + if (affectsHeadings) { + setHeadings(extractTocFromTextEditor(textEditor)); + } + }; + + textEditor.model.document.on("change:data", changeCallback); + setHeadings(headings); + + return () => textEditor.model.document.off("change:data", changeCallback); + }, [ textEditor, note ]); + + return ; +} + function extractTocFromTextEditor(editor: CKTextEditor) { const headings: CKHeading[] = []; @@ -155,4 +158,4 @@ function extractTocFromTextEditor(editor: CKTextEditor) { return headings; } - +//#endregion From b93c80fe7bbc4272d0391875912daa00d0d446ff Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 11:12:27 +0200 Subject: [PATCH 590/873] feat(toc): basic support for read-only text --- apps/client/src/components/note_context.ts | 2 +- apps/client/src/widgets/react/hooks.tsx | 16 ++++++++++ .../src/widgets/sidebar/TableOfContents.tsx | 29 +++++++++++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/client/src/components/note_context.ts b/apps/client/src/components/note_context.ts index 15791c7417..5295007a7d 100644 --- a/apps/client/src/components/note_context.ts +++ b/apps/client/src/components/note_context.ts @@ -390,7 +390,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded"> * If no content could be determined `null` is returned instead. */ async getContentElement() { - return this.timeout>( + return this.timeout | null>( new Promise((resolve) => appContext.triggerCommand("executeWithContentElement", { resolve, diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index b3bd6cf293..5ec68a42d5 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -1115,3 +1115,19 @@ export function useTextEditor(noteContext: NoteContext | null | undefined) { return textEditor; } + +export function useContentElement(noteContext: NoteContext | null | undefined) { + const [ contentElement, setContentElement ] = useState(null); + const requestIdRef = useRef(0); + + useEffect(() => { + const requestId = ++requestIdRef.current; + noteContext?.getContentElement().then(contentElement => { + // Prevent stale async. + if (requestId !== requestIdRef.current) return; + setContentElement(contentElement?.[0] ?? null); + }); + }, [ noteContext ]); + + return contentElement; +} diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 55178811c2..64249bd27c 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -5,7 +5,7 @@ import clsx from "clsx"; import { useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; -import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; +import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; import Icon from "../react/Icon"; import RightPanelWidget from "./RightPanelWidget"; @@ -27,7 +27,9 @@ export default function TableOfContents() { return ( - {noteType === "text" && !isReadOnly && } + {noteType === "text" && ( + isReadOnly ? : + )} ); } @@ -159,3 +161,26 @@ function extractTocFromTextEditor(editor: CKTextEditor) { return headings; } //#endregion + +function ReadOnlyTextTableOfContents() { + const { noteContext } = useActiveNoteContext(); + const contentEl = useContentElement(noteContext); + const headings = extractTocFromStaticHtml(contentEl); + + return ; +} + +function extractTocFromStaticHtml(el: HTMLElement | null) { + if (!el) return []; + + const headings: RawHeading[] = []; + for (const headingEl of el.querySelectorAll("h1,h2,h3,h4,h5,h6")) { + headings.push({ + id: crypto.randomUUID(), + level: parseInt(headingEl.tagName.substring(1), 10), + text: headingEl.textContent + }); + } + + return headings; +} From 704dcd011e6096df241cdacfc6cff3c6cd16c0f2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 11:46:13 +0200 Subject: [PATCH 591/873] feat(toc): basic support for docs --- apps/client/src/components/app_context.ts | 74 +++++++++---------- apps/client/src/widgets/react/hooks.tsx | 9 +++ .../src/widgets/sidebar/TableOfContents.tsx | 5 +- apps/client/src/widgets/type_widgets/Doc.tsx | 16 ++-- 4 files changed, 58 insertions(+), 46 deletions(-) diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index b5ad30003c..e0b8c651b6 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -1,40 +1,41 @@ -import froca from "../services/froca.js"; -import RootCommandExecutor from "./root_command_executor.js"; -import Entrypoints from "./entrypoints.js"; -import options from "../services/options.js"; -import utils, { hasTouchBar } from "../services/utils.js"; -import zoomComponent from "./zoom.js"; -import TabManager from "./tab_manager.js"; -import Component from "./component.js"; -import keyboardActionsService from "../services/keyboard_actions.js"; -import linkService, { type ViewScope } from "../services/link.js"; -import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js"; -import MainTreeExecutors from "./main_tree_executors.js"; -import toast from "../services/toast.js"; -import ShortcutComponent from "./shortcut_component.js"; -import { t, initLocale } from "../services/i18n.js"; -import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js"; -import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js"; -import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js"; -import type LoadResults from "../services/load_results.js"; -import type { Attribute } from "../services/attribute_parser.js"; -import type NoteTreeWidget from "../widgets/note_tree.js"; -import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; -import type { NativeImage, TouchBar } from "electron"; -import TouchBarComponent from "./touch_bar.js"; import type { CKTextEditor } from "@triliumnext/ckeditor5"; import type CodeMirror from "@triliumnext/codemirror"; -import { StartupChecks } from "./startup_checks.js"; -import type { CreateNoteOpts } from "../services/note_create.js"; -import { ColumnComponent } from "tabulator-tables"; -import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx"; -import type RootContainer from "../widgets/containers/root_container.js"; import { SqlExecuteResults } from "@triliumnext/commons"; -import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx"; -import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx"; +import type { NativeImage, TouchBar } from "electron"; +import { ColumnComponent } from "tabulator-tables"; + +import type { Attribute } from "../services/attribute_parser.js"; +import froca from "../services/froca.js"; +import { initLocale,t } from "../services/i18n.js"; +import keyboardActionsService from "../services/keyboard_actions.js"; +import linkService, { type ViewScope } from "../services/link.js"; +import type LoadResults from "../services/load_results.js"; +import type { CreateNoteOpts } from "../services/note_create.js"; +import options from "../services/options.js"; +import toast from "../services/toast.js"; +import utils, { hasTouchBar } from "../services/utils.js"; import { ReactWrappedWidget } from "../widgets/basic_widget.js"; -import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx"; +import type RootContainer from "../widgets/containers/root_container.js"; +import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx"; +import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js"; +import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js"; +import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx"; import type { InfoProps } from "../widgets/dialogs/info.jsx"; +import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx"; +import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx"; +import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js"; +import type NoteTreeWidget from "../widgets/note_tree.js"; +import Component from "./component.js"; +import Entrypoints from "./entrypoints.js"; +import MainTreeExecutors from "./main_tree_executors.js"; +import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js"; +import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; +import RootCommandExecutor from "./root_command_executor.js"; +import ShortcutComponent from "./shortcut_component.js"; +import { StartupChecks } from "./startup_checks.js"; +import TabManager from "./tab_manager.js"; +import TouchBarComponent from "./touch_bar.js"; +import zoomComponent from "./zoom.js"; interface Layout { getRootWidget: (appContext: AppContext) => RootContainer; @@ -447,6 +448,7 @@ type EventMappings = { }; searchRefreshed: { ntxId?: string | null }; textEditorRefreshed: { ntxId?: string | null, editor: CKTextEditor }; + contentElRefreshed: { ntxId?: string | null, contentEl: HTMLElement }; hoistedNoteChanged: { noteId: string; ntxId: string | null; @@ -695,10 +697,8 @@ $(window).on("beforeunload", () => { console.log(`Component ${component.componentId} is not finished saving its state.`); allSaved = false; } - } else { - if (!listener()) { - allSaved = false; - } + } else if (!listener()) { + allSaved = false; } } @@ -708,7 +708,7 @@ $(window).on("beforeunload", () => { } }); -$(window).on("hashchange", function () { +$(window).on("hashchange", () => { const { notePath, ntxId, viewScope, searchString } = linkService.parseNavigationStateFromUrl(window.location.href); if (notePath || ntxId) { diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 5ec68a42d5..73fbfc1016 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -1119,6 +1119,7 @@ export function useTextEditor(noteContext: NoteContext | null | undefined) { export function useContentElement(noteContext: NoteContext | null | undefined) { const [ contentElement, setContentElement ] = useState(null); const requestIdRef = useRef(0); + const [, forceUpdate] = useState(0); useEffect(() => { const requestId = ++requestIdRef.current; @@ -1126,8 +1127,16 @@ export function useContentElement(noteContext: NoteContext | null | undefined) { // Prevent stale async. if (requestId !== requestIdRef.current) return; setContentElement(contentElement?.[0] ?? null); + forceUpdate(v => v + 1); }); }, [ noteContext ]); + // React to content changes initializing. + useTriliumEvent("contentElRefreshed", ({ ntxId: eventNtxId, contentEl }) => { + if (eventNtxId !== noteContext?.ntxId) return; + setContentElement(contentEl); + forceUpdate(v => v + 1); + }); + return contentElement; } diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 64249bd27c..696e37e56f 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -27,9 +27,8 @@ export default function TableOfContents() { return ( - {noteType === "text" && ( - isReadOnly ? : - )} + {((noteType === "text" && isReadOnly) || (noteType === "doc")) && } + {noteType === "text" && !isReadOnly && } ); } diff --git a/apps/client/src/widgets/type_widgets/Doc.tsx b/apps/client/src/widgets/type_widgets/Doc.tsx index 5c7a31890e..93929c7bab 100644 --- a/apps/client/src/widgets/type_widgets/Doc.tsx +++ b/apps/client/src/widgets/type_widgets/Doc.tsx @@ -1,10 +1,12 @@ -import { useEffect, useRef, useState } from "preact/hooks"; -import { RawHtmlBlock } from "../react/RawHtml"; -import renderDoc from "../../services/doc_renderer"; import "./Doc.css"; -import { TypeWidgetProps } from "./type_widget"; + +import { useEffect, useRef } from "preact/hooks"; + +import appContext from "../../components/app_context"; +import renderDoc from "../../services/doc_renderer"; import { useTriliumEvent } from "../react/hooks"; import { refToJQuerySelector } from "../react/react_utils"; +import { TypeWidgetProps } from "./type_widget"; export default function Doc({ note, viewScope, ntxId }: TypeWidgetProps) { const initialized = useRef | null>(null); @@ -14,9 +16,11 @@ export default function Doc({ note, viewScope, ntxId }: TypeWidgetProps) { if (!note) return; initialized.current = renderDoc(note).then($content => { - containerRef.current?.replaceChildren(...$content); + if (!containerRef.current) return; + containerRef.current.replaceChildren(...$content); + appContext.triggerEvent("contentElRefreshed", { ntxId, contentEl: containerRef.current }); }); - }, [ note ]); + }, [ note, ntxId ]); useTriliumEvent("executeWithContentElement", async ({ resolve, ntxId: eventNtxId}) => { if (eventNtxId !== ntxId) return; From 96ccb1e67e90b45d320a8fbe89881cc1797223ae Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 11:46:21 +0200 Subject: [PATCH 592/873] fix(toc): sometimes not reacting to read-only note switching --- .../type_widgets/text/ReadOnlyText.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/text/ReadOnlyText.tsx b/apps/client/src/widgets/type_widgets/text/ReadOnlyText.tsx index e233800308..3352f621a0 100644 --- a/apps/client/src/widgets/type_widgets/text/ReadOnlyText.tsx +++ b/apps/client/src/widgets/type_widgets/text/ReadOnlyText.tsx @@ -1,23 +1,24 @@ -import { useEffect, useMemo, useRef } from "preact/hooks"; -import { TypeWidgetProps } from "../type_widget"; import "./ReadOnlyText.css"; -import { useNoteBlob, useNoteLabel, useTriliumEvent, useTriliumOptionBool } from "../../react/hooks"; -import { RawHtmlBlock } from "../../react/RawHtml"; - // we load CKEditor also for read only notes because they contain content styles required for correct rendering of even read only notes // we could load just ckeditor-content.css but that causes CSS conflicts when both build CSS and this content CSS is loaded at the same time // (see https://github.com/zadam/trilium/issues/1590 for example of such conflict) import "@triliumnext/ckeditor5"; + +import clsx from "clsx"; +import { useEffect, useMemo, useRef } from "preact/hooks"; + +import appContext from "../../../components/app_context"; import FNote from "../../../entities/fnote"; +import { applyInlineMermaid, rewriteMermaidDiagramsInContainer } from "../../../services/content_renderer_text"; import { getLocaleById } from "../../../services/i18n"; -import { loadIncludedNote, refreshIncludedNote, setupImageOpening } from "./utils"; import { renderMathInElement } from "../../../services/math"; import { formatCodeBlocks } from "../../../services/syntax_highlight"; +import { useNoteBlob, useNoteLabel, useTriliumEvent, useTriliumOptionBool } from "../../react/hooks"; +import { RawHtmlBlock } from "../../react/RawHtml"; import TouchBar, { TouchBarButton, TouchBarSpacer } from "../../react/TouchBar"; -import appContext from "../../../components/app_context"; +import { TypeWidgetProps } from "../type_widget"; import { applyReferenceLinks } from "./read_only_helper"; -import { applyInlineMermaid, rewriteMermaidDiagramsInContainer } from "../../../services/content_renderer_text"; -import clsx from "clsx"; +import { loadIncludedNote, refreshIncludedNote, setupImageOpening } from "./utils"; export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetProps) { const blob = useNoteBlob(note); @@ -30,6 +31,8 @@ export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetPro const container = contentRef.current; if (!container) return; + appContext.triggerEvent("contentElRefreshed", { ntxId, contentEl: container }); + rewriteMermaidDiagramsInContainer(container); applyInlineMermaid(container); applyIncludedNotes(container); @@ -74,7 +77,7 @@ export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetPro /> - ) + ); } function useNoteLanguage(note: FNote) { From bf5c56a61ad3c4da03ece2e9c0ed53a12ecb7c83 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 12:04:42 +0200 Subject: [PATCH 593/873] chore(toc): reintroduce navigation in editable text notes --- .../src/widgets/sidebar/TableOfContents.tsx | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 696e37e56f..b84a138bf8 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -2,7 +2,7 @@ import "./TableOfContents.css"; import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5"; import clsx from "clsx"; -import { useEffect, useState } from "preact/hooks"; +import { useCallback, useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; @@ -33,20 +33,25 @@ export default function TableOfContents() { ); } -function AbstractTableOfContents({ headings }: { +function AbstractTableOfContents({ headings, scrollToHeading }: { headings: RawHeading[]; + scrollToHeading(heading: RawHeading): void; }) { const nestedHeadings = buildHeadingTree(headings); return (
          - {nestedHeadings.map(heading => )} + {nestedHeadings.map(heading => )}
        ); } -function TableOfContentsHeading({ heading }: { heading: HeadingsWithNesting }) { +function TableOfContentsHeading({ heading, scrollToHeading }: { + heading: HeadingsWithNesting; + scrollToHeading(heading: RawHeading): void; +}) { + console.log("Got ", scrollToHeading); const [ collapsed, setCollapsed ] = useState(false); return ( <> @@ -58,11 +63,14 @@ function TableOfContentsHeading({ heading }: { heading: HeadingsWithNesting }) { onClick={() => setCollapsed(!collapsed)} /> )} - {heading.text} + scrollToHeading(heading)} + >{heading.text} {heading.children && (
          - {heading.children.map(heading => )} + {heading.children.map(heading => )}
        )} @@ -128,7 +136,20 @@ function EditableTextTableOfContents() { return () => textEditor.model.document.off("change:data", changeCallback); }, [ textEditor, note ]); - return ; + const scrollToHeading = useCallback((heading: CKHeading) => { + if (!textEditor) return; + + const viewEl = textEditor.editing.mapper.toViewElement(heading.element); + if (!viewEl) return; + + const domEl = textEditor.editing.view.domConverter.mapViewToDom(viewEl); + domEl?.scrollIntoView(); + }, [ textEditor ]); + + return ; } function extractTocFromTextEditor(editor: CKTextEditor) { From b0e1751dc751de069592d99d748567cf0c064ae4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 12:09:05 +0200 Subject: [PATCH 594/873] chore(toc): reintroduce navigation in readonly text notes --- .../src/widgets/sidebar/TableOfContents.tsx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index b84a138bf8..b6f7553e55 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -33,9 +33,9 @@ export default function TableOfContents() { ); } -function AbstractTableOfContents({ headings, scrollToHeading }: { - headings: RawHeading[]; - scrollToHeading(heading: RawHeading): void; +function AbstractTableOfContents({ headings, scrollToHeading }: { + headings: T[]; + scrollToHeading(heading: T): void; }) { const nestedHeadings = buildHeadingTree(headings); return ( @@ -51,7 +51,6 @@ function TableOfContentsHeading({ heading, scrollToHeading }: { heading: HeadingsWithNesting; scrollToHeading(heading: RawHeading): void; }) { - console.log("Got ", scrollToHeading); const [ collapsed, setCollapsed ] = useState(false); return ( <> @@ -181,24 +180,35 @@ function extractTocFromTextEditor(editor: CKTextEditor) { return headings; } //#endregion +interface DomHeading extends RawHeading { + element: HTMLHeadingElement; +} function ReadOnlyTextTableOfContents() { const { noteContext } = useActiveNoteContext(); const contentEl = useContentElement(noteContext); const headings = extractTocFromStaticHtml(contentEl); - return ; + const scrollToHeading = useCallback((heading: DomHeading) => { + heading.element.scrollIntoView(); + }, []); + + return ; } function extractTocFromStaticHtml(el: HTMLElement | null) { if (!el) return []; - const headings: RawHeading[] = []; - for (const headingEl of el.querySelectorAll("h1,h2,h3,h4,h5,h6")) { + const headings: DomHeading[] = []; + for (const headingEl of el.querySelectorAll("h1,h2,h3,h4,h5,h6")) { headings.push({ id: crypto.randomUUID(), level: parseInt(headingEl.tagName.substring(1), 10), - text: headingEl.textContent + text: headingEl.textContent, + element: headingEl }); } From dbf29ed23f2ca962ab1ad5cb2502330891479c93 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 12:38:45 +0200 Subject: [PATCH 595/873] chore(highlights_list): start from scratch --- .../src/widgets/sidebar/HighlightsList.tsx | 28 +++++++++++++++++++ .../widgets/sidebar/RightPanelContainer.tsx | 4 ++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/sidebar/HighlightsList.tsx diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx new file mode 100644 index 0000000000..c18f1770bc --- /dev/null +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -0,0 +1,28 @@ +import { t } from "../../services/i18n"; +import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty } from "../react/hooks"; +import RightPanelWidget from "./RightPanelWidget"; + +export default function HighlightsList() { + const { note, noteContext } = useActiveNoteContext(); + const noteType = useNoteProperty(note, "type"); + const { isReadOnly } = useIsNoteReadOnly(note, noteContext); + + return ( + + {((noteType === "text" && isReadOnly) || (noteType === "doc")) && } + {noteType === "text" && !isReadOnly && } + + ); +} + +//#region Editable text (CKEditor) +function EditableTextHighlightsList() { + return "Editable"; +} +//#endregion + +//#region Read-only text +function ReadOnlyTextHighlightsList() { + return "Read-only"; +} +//#endregion diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 7b838c5185..b5ad216fd8 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -7,6 +7,7 @@ import { useEffect } from "preact/hooks"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; import { useActiveNoteContext } from "../react/hooks"; +import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; @@ -29,7 +30,8 @@ export default function RightPanelContainer() { return (
        {note && <> - + + }
        ); From 73f2f56932684b7af29b971deb2aadb6a3f673b4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 12:52:14 +0200 Subject: [PATCH 596/873] chore(highlights_list): read highlights from CK --- .../src/widgets/sidebar/HighlightsList.tsx | 77 ++++++++++++++++++- .../src/widgets/sidebar/TableOfContents.tsx | 2 +- packages/ckeditor5/src/index.ts | 2 +- 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index c18f1770bc..1755230f8c 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -1,7 +1,16 @@ +import { CKTextEditor, ModelTextProxy } from "@triliumnext/ckeditor5"; +import { useEffect, useState } from "preact/hooks"; + import { t } from "../../services/i18n"; -import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty } from "../react/hooks"; +import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; +interface RawHighlight { + id: string; + text: string; + attrs: Record; +} + export default function HighlightsList() { const { note, noteContext } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); @@ -15,9 +24,73 @@ export default function HighlightsList() { ); } +function AbstractHighlightsList({ highlights }: { + highlights: RawHighlight[] +}) { + return ( + +
          + {highlights.map(highlight => ( +
        1. + {highlight.text} +
        2. + ))} +
        +
        + ); +} + //#region Editable text (CKEditor) +interface CKHighlight extends RawHighlight { + element: ModelTextProxy; +} + function EditableTextHighlightsList() { - return "Editable"; + const { note, noteContext } = useActiveNoteContext(); + const textEditor = useTextEditor(noteContext); + const [ highlights, setHighlights ] = useState([]); + + useEffect(() => { + if (!textEditor) return; + + const highlights = extractHighlightsFromTextEditor(textEditor); + setHighlights(highlights); + }, [ textEditor, note ]); + + return ; +} + +function extractHighlightsFromTextEditor(editor: CKTextEditor) { + const result: CKHighlight[] = []; + const root = editor.model.document.getRoot(); + if (!root) return []; + + for (const { item } of editor.model.createRangeIn(root).getWalker({ ignoreElementEnd: true })) { + if (!item.is('$textProxy')) continue; + console.log("Got ", item); + + const attrs = { + bold: item.hasAttribute('bold'), + italic: item.hasAttribute('italic'), + underline: item.hasAttribute('underline'), + color: item.getAttribute('fontColor'), + background: item.getAttribute('fontBackgroundColor') + }; + console.log("Got ", attrs); + + if (Object.values(attrs).some(Boolean)) { + result.push({ + id: crypto.randomUUID(), + text: item.data, + attrs, + element: item + }); + } + } + + return result; } //#endregion diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index b6f7553e55..d60d4e8dc2 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -114,6 +114,7 @@ function EditableTextTableOfContents() { useEffect(() => { if (!textEditor) return; const headings = extractTocFromTextEditor(textEditor); + setHeadings(headings); // React to changes. const changeCallback = () => { @@ -130,7 +131,6 @@ function EditableTextTableOfContents() { }; textEditor.model.document.on("change:data", changeCallback); - setHeadings(headings); return () => textEditor.model.document.off("change:data", changeCallback); }, [ textEditor, note ]); diff --git a/packages/ckeditor5/src/index.ts b/packages/ckeditor5/src/index.ts index a6b1934775..745c428cfe 100644 --- a/packages/ckeditor5/src/index.ts +++ b/packages/ckeditor5/src/index.ts @@ -6,7 +6,7 @@ import { BalloonEditor, DecoupledEditor, FindAndReplaceEditing, FindCommand } fr import "./translation_overrides.js"; export { default as EditorWatchdog } from "./custom_watchdog"; export { PREMIUM_PLUGINS } from "./plugins.js"; -export type { EditorConfig, MentionFeed, MentionFeedObjectItem, ModelNode, ModelPosition, ModelElement, WatchdogConfig, WatchdogState } from "ckeditor5"; +export type { EditorConfig, MentionFeed, MentionFeedObjectItem, ModelNode, ModelPosition, ModelElement, ModelTextProxy, WatchdogConfig, WatchdogState } from "ckeditor5"; export type { TemplateDefinition } from "ckeditor5-premium-features"; export { default as buildExtraCommands } from "./extra_slash_commands.js"; export { default as getCkLocale } from "./i18n.js"; From 7085e62cfc8280272514fd0ea76571f4c923c1fc Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 13:09:53 +0200 Subject: [PATCH 597/873] chore(highlights_list): reintroduce navigation --- .../src/widgets/sidebar/HighlightsList.tsx | 30 ++++++++++++++----- packages/ckeditor5/src/index.ts | 2 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 1755230f8c..e34e39ff42 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -1,5 +1,5 @@ -import { CKTextEditor, ModelTextProxy } from "@triliumnext/ckeditor5"; -import { useEffect, useState } from "preact/hooks"; +import { CKTextEditor, ModelText } from "@triliumnext/ckeditor5"; +import { useCallback, useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; @@ -24,14 +24,15 @@ export default function HighlightsList() { ); } -function AbstractHighlightsList({ highlights }: { - highlights: RawHighlight[] +function AbstractHighlightsList({ highlights, scrollToHighlight }: { + highlights: T[], + scrollToHighlight(highlight: T): void; }) { return (
          {highlights.map(highlight => ( -
        1. +
        2. scrollToHighlight(highlight)}> {highlight.text}
        3. ))} @@ -42,7 +43,8 @@ function AbstractHighlightsList({ highlights }: { //#region Editable text (CKEditor) interface CKHighlight extends RawHighlight { - element: ModelTextProxy; + textNode: ModelText; + offset: number | null; } function EditableTextHighlightsList() { @@ -57,8 +59,21 @@ function EditableTextHighlightsList() { setHighlights(highlights); }, [ textEditor, note ]); + const scrollToHeading = useCallback((highlight: CKHighlight) => { + if (!textEditor) return; + + const modelPos = textEditor.model.createPositionAt(highlight.textNode, "before"); + const viewPos = textEditor.editing.mapper.toViewPosition(modelPos); + const domConverter = textEditor.editing.view.domConverter; + const domPos = domConverter.viewPositionToDom(viewPos); + + if (!domPos) return; + (domPos.parent as HTMLElement).scrollIntoView(); + }, [ textEditor ]); + return ; } @@ -85,7 +100,8 @@ function extractHighlightsFromTextEditor(editor: CKTextEditor) { id: crypto.randomUUID(), text: item.data, attrs, - element: item + textNode: item.textNode, + offset: item.startOffset }); } } diff --git a/packages/ckeditor5/src/index.ts b/packages/ckeditor5/src/index.ts index 745c428cfe..7dff348037 100644 --- a/packages/ckeditor5/src/index.ts +++ b/packages/ckeditor5/src/index.ts @@ -6,7 +6,7 @@ import { BalloonEditor, DecoupledEditor, FindAndReplaceEditing, FindCommand } fr import "./translation_overrides.js"; export { default as EditorWatchdog } from "./custom_watchdog"; export { PREMIUM_PLUGINS } from "./plugins.js"; -export type { EditorConfig, MentionFeed, MentionFeedObjectItem, ModelNode, ModelPosition, ModelElement, ModelTextProxy, WatchdogConfig, WatchdogState } from "ckeditor5"; +export type { EditorConfig, MentionFeed, MentionFeedObjectItem, ModelNode, ModelPosition, ModelElement, ModelText, WatchdogConfig, WatchdogState } from "ckeditor5"; export type { TemplateDefinition } from "ckeditor5-premium-features"; export { default as buildExtraCommands } from "./extra_slash_commands.js"; export { default as getCkLocale } from "./i18n.js"; From b42a4dcb3641083662b10757cc24eff5d5c80753 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 13:12:58 +0200 Subject: [PATCH 598/873] chore(highlights_list): react to changes --- .../src/widgets/sidebar/HighlightsList.tsx | 37 +++++++++++++++++-- .../src/widgets/sidebar/TableOfContents.tsx | 1 - 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index e34e39ff42..95ecea6d49 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -54,9 +54,40 @@ function EditableTextHighlightsList() { useEffect(() => { if (!textEditor) return; + setHighlights(extractHighlightsFromTextEditor(textEditor)); - const highlights = extractHighlightsFromTextEditor(textEditor); - setHighlights(highlights); + // React to changes. + const changeCallback = () => { + const changes = textEditor.model.document.differ.getChanges(); + const affectsHighlights = changes.some(change => { + // Text inserted or removed + if (change.type === 'insert' || change.type === 'remove') { + return true; + } + + // Formatting attribute changed + if (change.type === 'attribute' && + ( + change.attributeKey === 'bold' || + change.attributeKey === 'italic' || + change.attributeKey === 'underline' || + change.attributeKey === 'fontColor' || + change.attributeKey === 'fontBackgroundColor' + ) + ) { + return true; + } + + return false; + }); + + if (affectsHighlights) { + setHighlights(extractHighlightsFromTextEditor(textEditor)); + } + }; + + textEditor.model.document.on("change:data", changeCallback); + return () => textEditor.model.document.off("change:data", changeCallback); }, [ textEditor, note ]); const scrollToHeading = useCallback((highlight: CKHighlight) => { @@ -84,7 +115,6 @@ function extractHighlightsFromTextEditor(editor: CKTextEditor) { for (const { item } of editor.model.createRangeIn(root).getWalker({ ignoreElementEnd: true })) { if (!item.is('$textProxy')) continue; - console.log("Got ", item); const attrs = { bold: item.hasAttribute('bold'), @@ -93,7 +123,6 @@ function extractHighlightsFromTextEditor(editor: CKTextEditor) { color: item.getAttribute('fontColor'), background: item.getAttribute('fontBackgroundColor') }; - console.log("Got ", attrs); if (Object.values(attrs).some(Boolean)) { result.push({ diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index d60d4e8dc2..ccc623ab84 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -131,7 +131,6 @@ function EditableTextTableOfContents() { }; textEditor.model.document.on("change:data", changeCallback); - return () => textEditor.model.document.off("change:data", changeCallback); }, [ textEditor, note ]); From d920da9e6f709e9a1a5490a2994e1eb775217738 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 13:17:41 +0200 Subject: [PATCH 599/873] chore(highlights_list): render highlights --- .../src/widgets/sidebar/HighlightsList.tsx | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 95ecea6d49..6f09ddd037 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -8,7 +8,13 @@ import RightPanelWidget from "./RightPanelWidget"; interface RawHighlight { id: string; text: string; - attrs: Record; + attrs: { + bold: boolean; + italic: boolean; + underline: boolean; + color: string | undefined; + background: string | undefined; + } } export default function HighlightsList() { @@ -33,7 +39,15 @@ function AbstractHighlightsList({ highlights, scrollToHi
            {highlights.map(highlight => (
          1. scrollToHighlight(highlight)}> - {highlight.text} + {highlight.text}
          2. ))}
          @@ -116,12 +130,12 @@ function extractHighlightsFromTextEditor(editor: CKTextEditor) { for (const { item } of editor.model.createRangeIn(root).getWalker({ ignoreElementEnd: true })) { if (!item.is('$textProxy')) continue; - const attrs = { + const attrs: RawHighlight["attrs"] = { bold: item.hasAttribute('bold'), italic: item.hasAttribute('italic'), underline: item.hasAttribute('underline'), - color: item.getAttribute('fontColor'), - background: item.getAttribute('fontBackgroundColor') + color: item.getAttribute('fontColor') as string | undefined, + background: item.getAttribute('fontBackgroundColor') as string | undefined }; if (Object.values(attrs).some(Boolean)) { From 925049357aed19af38a85b28fe6282944865d17b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 13:20:04 +0200 Subject: [PATCH 600/873] fix(highlights_list): missing key --- apps/client/src/widgets/sidebar/HighlightsList.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 6f09ddd037..523359da37 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -38,7 +38,10 @@ function AbstractHighlightsList({ highlights, scrollToHi
            {highlights.map(highlight => ( -
          1. scrollToHighlight(highlight)}> +
          2. scrollToHighlight(highlight)} + > Date: Thu, 18 Dec 2025 13:29:36 +0200 Subject: [PATCH 601/873] chore(highlights_list): reintroduce support for read-only notes --- .../src/widgets/sidebar/HighlightsList.tsx | 60 ++++++++++++++++++- .../src/widgets/sidebar/TableOfContents.tsx | 3 + 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 523359da37..a575f2f1a3 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -1,8 +1,9 @@ +import { headingIsHorizontal } from "@excalidraw/excalidraw/element/heading"; import { CKTextEditor, ModelText } from "@triliumnext/ckeditor5"; import { useCallback, useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; -import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; +import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; interface RawHighlight { @@ -157,7 +158,62 @@ function extractHighlightsFromTextEditor(editor: CKTextEditor) { //#endregion //#region Read-only text +interface DomHighlight extends RawHighlight { + element: HTMLElement; +} + function ReadOnlyTextHighlightsList() { - return "Read-only"; + const { noteContext } = useActiveNoteContext(); + const contentEl = useContentElement(noteContext); + const highlights = extractHeadingsFromStaticHtml(contentEl); + + const scrollToHighlight = useCallback((highlight: DomHighlight) => { + highlight.element.scrollIntoView(); + }, []); + + return ; +} + +function extractHeadingsFromStaticHtml(el: HTMLElement | null) { + if (!el) return []; + + const walker = document.createTreeWalker( + el, + NodeFilter.SHOW_TEXT, + null + ); + + const highlights: DomHighlight[] = []; + + let node: Node | null; + while ((node = walker.nextNode())) { + const el = node.parentElement; + if (!el || !node.textContent?.trim()) continue; + + const style = getComputedStyle(el); + + if ( + el.closest('strong, em, u') || + style.color || style.backgroundColor + ) { + highlights.push({ + id: crypto.randomUUID(), + text: node.textContent, + element: el, + attrs: { + bold: !!el.closest("strong"), + italic: !!el.closest("em"), + underline: !!el.closest("u"), + background: el.style.backgroundColor, + color: el.style.color + } + }); + } + } + + return highlights; } //#endregion diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index ccc623ab84..46967655f6 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -179,6 +179,8 @@ function extractTocFromTextEditor(editor: CKTextEditor) { return headings; } //#endregion + +//#region Read-only text interface DomHeading extends RawHeading { element: HTMLHeadingElement; } @@ -213,3 +215,4 @@ function extractTocFromStaticHtml(el: HTMLElement | null) { return headings; } +//#endregion From d18ac0c6139f8a241ee49282e7a5f16602486bc4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 13:33:08 +0200 Subject: [PATCH 602/873] fix(highlights_list): displaying non-highlighted attributes --- .../src/widgets/sidebar/HighlightsList.tsx | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index a575f2f1a3..755071cab3 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -191,7 +191,7 @@ function extractHeadingsFromStaticHtml(el: HTMLElement | null) { let node: Node | null; while ((node = walker.nextNode())) { const el = node.parentElement; - if (!el || !node.textContent?.trim()) continue; + if (!el || !node.textContent) continue; const style = getComputedStyle(el); @@ -199,18 +199,22 @@ function extractHeadingsFromStaticHtml(el: HTMLElement | null) { el.closest('strong, em, u') || style.color || style.backgroundColor ) { - highlights.push({ - id: crypto.randomUUID(), - text: node.textContent, - element: el, - attrs: { - bold: !!el.closest("strong"), - italic: !!el.closest("em"), - underline: !!el.closest("u"), - background: el.style.backgroundColor, - color: el.style.color - } - }); + const attrs: RawHighlight["attrs"] = { + bold: !!el.closest("strong"), + italic: !!el.closest("em"), + underline: !!el.closest("u"), + background: el.style.backgroundColor, + color: el.style.color + }; + + if (Object.values(attrs).some(Boolean)) { + highlights.push({ + id: crypto.randomUUID(), + text: node.textContent, + element: el, + attrs + }); + } } } From 751a874c5119fc7e209f6309a2c6235d146f5222 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 13:38:52 +0200 Subject: [PATCH 603/873] chore(highlights_list): improve performance by matching fewer elements --- apps/client/src/widgets/sidebar/HighlightsList.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 755071cab3..4007cc91f8 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -180,6 +180,8 @@ function ReadOnlyTextHighlightsList() { function extractHeadingsFromStaticHtml(el: HTMLElement | null) { if (!el) return []; + const { color: defaultColor, backgroundColor: defaultBackgroundColor } = getComputedStyle(el); + const walker = document.createTreeWalker( el, NodeFilter.SHOW_TEXT, @@ -197,7 +199,8 @@ function extractHeadingsFromStaticHtml(el: HTMLElement | null) { if ( el.closest('strong, em, u') || - style.color || style.backgroundColor + style.color !== defaultColor || + style.backgroundColor !== defaultBackgroundColor ) { const attrs: RawHighlight["attrs"] = { bold: !!el.closest("strong"), From 28d9d98964f907f80b07e10f45427d8df61c51d3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 13:45:45 +0200 Subject: [PATCH 604/873] fix(highlights_list): unable to scroll to text fragments --- apps/client/src/widgets/sidebar/HighlightsList.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 4007cc91f8..be1e170e16 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -117,7 +117,12 @@ function EditableTextHighlightsList() { const domPos = domConverter.viewPositionToDom(viewPos); if (!domPos) return; - (domPos.parent as HTMLElement).scrollIntoView(); + if (domPos.parent instanceof HTMLElement) { + domPos.parent.scrollIntoView(); + } else if (domPos.parent instanceof Text) { + domPos.parent.parentElement?.scrollIntoView(); + } + }, [ textEditor ]); return Date: Thu, 18 Dec 2025 14:10:16 +0200 Subject: [PATCH 605/873] chore(right_pane): improve style slightly --- .../src/widgets/sidebar/RightPanelContainer.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 65cff510e6..631480c1bc 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -1,3 +1,17 @@ body.experimental-feature-new-layout #right-pane { width: 300px; + + .card { + margin-inline: 0; + border-bottom: 1px solid var(--main-border-color); + border-radius: 0; + + .card-header-title { + padding-inline: 0.5em; + } + + &:last-of-type { + border-bottom: 0; + } + } } From b5bfb02d967ac728fe2944087815722e9c3aad89 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 14:12:28 +0200 Subject: [PATCH 606/873] chore(right_pane): experiment with resizable sections --- .../widgets/sidebar/RightPanelContainer.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index b5ad216fd8..d3ec7e9120 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -6,14 +6,13 @@ import { useEffect } from "preact/hooks"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; -import { useActiveNoteContext } from "../react/hooks"; import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; export default function RightPanelContainer() { - const { note } = useActiveNoteContext(); + // Split between right pane and the content pane. useEffect(() => { // We are intentionally omitting useTriliumOption to avoid re-render due to size change. const rightPaneWidth = Math.max(MIN_WIDTH_PERCENT, options.getInt("rightPaneWidth") ?? MIN_WIDTH_PERCENT); @@ -27,12 +26,25 @@ export default function RightPanelContainer() { return () => splitInstance.destroy(); }, []); + const items = [ + , + + ]; + + // Split between items. + useEffect(() => { + const rightPaneContainer = document.getElementById("right-pane"); + const elements = Array.from(rightPaneContainer?.children ?? []) as HTMLElement[]; + console.log("Got ", elements); + const splitInstance = Split(elements, { + direction: "vertical" + }); + return () => splitInstance.destroy(); + }, [ items ]); + return (
            - {note && <> - - - } + {items}
            ); } From 682c61305c9e850928233993e92526a0c74a06e8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 14:42:21 +0200 Subject: [PATCH 607/873] chore(right_pane): basic collapse support --- .../widgets/sidebar/RightPanelContainer.css | 4 ++ .../widgets/sidebar/RightPanelContainer.tsx | 41 +++++++++++++++++-- .../src/widgets/sidebar/RightPanelWidget.tsx | 26 ++++++++++-- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 631480c1bc..d737e9d526 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -13,5 +13,9 @@ body.experimental-feature-new-layout #right-pane { &:last-of-type { border-bottom: 0; } + + &.collapsed .card-header > .bx { + transform: rotate(-90deg); + } } } diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index d3ec7e9120..ef0af28f08 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -2,7 +2,8 @@ import "./RightPanelContainer.css"; import Split from "@triliumnext/split.js"; -import { useEffect } from "preact/hooks"; +import { createContext } from "preact"; +import { useEffect, useRef } from "preact/hooks"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; @@ -10,6 +11,11 @@ import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; +const COLLAPSED_SIZE = 32; + +export const RightPanelContext = createContext({ + setExpanded(cardEl: HTMLElement, expanded: boolean) {} +}); export default function RightPanelContainer() { // Split between right pane and the content pane. @@ -32,19 +38,46 @@ export default function RightPanelContainer() { ]; // Split between items. + const innerSplitRef = useRef(null); useEffect(() => { const rightPaneContainer = document.getElementById("right-pane"); const elements = Array.from(rightPaneContainer?.children ?? []) as HTMLElement[]; - console.log("Got ", elements); const splitInstance = Split(elements, { - direction: "vertical" + direction: "vertical", + minSize: COLLAPSED_SIZE, + gutterSize: 1 }); + innerSplitRef.current = splitInstance; return () => splitInstance.destroy(); }, [ items ]); return (
            - {items} + .card") ?? []); + const pos = children.indexOf(cardEl); + if (pos === -1) return; + const sizes = splitInstance.getSizes(); + if (!expanded) { + const sizeBeforeCollapse = sizes[pos]; + sizes[pos] = 0; + const itemToExpand = pos > 0 ? pos - 1 : pos + 1; + + if (sizes[itemToExpand] > COLLAPSED_SIZE) { + sizes[itemToExpand] += sizeBeforeCollapse; + } + } + console.log("Set sizes to ", sizes); + splitInstance.setSizes(sizes); + }, + }}> + {items} +
            ); } diff --git a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx index 94a5cf04c4..7f4391d6cd 100644 --- a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx @@ -1,6 +1,10 @@ -import { useContext, useRef } from "preact/hooks"; -import { ParentComponent } from "../react/react_utils"; +import clsx from "clsx"; import { ComponentChildren } from "preact"; +import { useContext, useRef, useState } from "preact/hooks"; + +import Icon from "../react/Icon"; +import { ParentComponent } from "../react/react_utils"; +import { RightPanelContext } from "./RightPanelContainer"; interface RightPanelWidgetProps { title: string; @@ -9,6 +13,8 @@ interface RightPanelWidgetProps { } export default function RightPanelWidget({ title, buttons, children }: RightPanelWidgetProps) { + const rightPanelContext = useContext(RightPanelContext); + const [ expanded, setExpanded ] = useState(true); const containerRef = useRef(null); const parentComponent = useContext(ParentComponent); @@ -17,15 +23,27 @@ export default function RightPanelWidget({ title, buttons, children }: RightPane } return ( -
            +
            + { + if (containerRef.current) { + rightPanelContext.setExpanded(containerRef.current, !expanded); + } + setExpanded(!expanded); + }} + />
            {title}
            {buttons}
            - {children} + {expanded && children}
            From 5dacfd3ac68aa68ee6d70f35dba2af5740ea496f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 14:52:22 +0200 Subject: [PATCH 608/873] chore(right_pane): basic expand support --- .../src/widgets/sidebar/RightPanelContainer.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index ef0af28f08..8d41929ccd 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -51,6 +51,8 @@ export default function RightPanelContainer() { return () => splitInstance.destroy(); }, [ items ]); + const sizesBeforeCollapse = useRef(new WeakMap()); + return (
            0 ? pos - 1 : pos + 1; if (sizes[itemToExpand] > COLLAPSED_SIZE) { sizes[itemToExpand] += sizeBeforeCollapse; } + } else { + const itemToExpand = pos > 0 ? pos - 1 : pos + 1; + const sizeBeforeCollapse = sizesBeforeCollapse.current.get(cardEl) ?? 50; + + if (sizes[itemToExpand] > COLLAPSED_SIZE) { + sizes[itemToExpand] -= sizeBeforeCollapse; + } + sizes[pos] = sizeBeforeCollapse; } console.log("Set sizes to ", sizes); splitInstance.setSizes(sizes); From ea3222cf127120b642d4901bc4a94cbca502809a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 14:57:42 +0200 Subject: [PATCH 609/873] chore(right_pane): more advanced expand/collapse --- apps/client/src/services/utils.ts | 99 ++++++++-------- .../widgets/sidebar/RightPanelContainer.tsx | 111 +++++++++++++++--- 2 files changed, 149 insertions(+), 61 deletions(-) diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 8c2a12c6ac..36277bbb15 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -1,8 +1,9 @@ import { dayjs } from "@triliumnext/commons"; -import type { ViewMode, ViewScope } from "./link.js"; -import FNote from "../entities/fnote"; import { snapdom } from "@zumer/snapdom"; +import FNote from "../entities/fnote"; +import type { ViewMode, ViewScope } from "./link.js"; + const SVG_MIME = "image/svg+xml"; export const isShare = !window.glob; @@ -113,9 +114,9 @@ function formatDateISO(date: Date) { export function formatDateTime(date: Date, userSuppliedFormat?: string): string { if (userSuppliedFormat?.trim()) { return dayjs(date).format(userSuppliedFormat); - } else { - return `${formatDate(date)} ${formatTime(date)}`; - } + } + return `${formatDate(date)} ${formatTime(date)}`; + } function localNowDateTime() { @@ -191,9 +192,9 @@ export function formatSize(size: number | null | undefined) { if (size < 1024) { return `${size} KiB`; - } else { - return `${Math.round(size / 102.4) / 10} MiB`; - } + } + return `${Math.round(size / 102.4) / 10} MiB`; + } function toObject(array: T[], fn: (arg0: T) => [key: string, value: R]) { @@ -297,18 +298,18 @@ function formatHtml(html: string) { let indent = "\n"; const tab = "\t"; let i = 0; - let pre: { indent: string; tag: string }[] = []; + const pre: { indent: string; tag: string }[] = []; html = html - .replace(new RegExp("
            ([\\s\\S]+?)?
            "), function (x) { + .replace(new RegExp("
            ([\\s\\S]+?)?
            "), (x) => { pre.push({ indent: "", tag: x }); - return "<--TEMPPRE" + i++ + "/-->"; + return `<--TEMPPRE${ i++ }/-->`; }) - .replace(new RegExp("<[^<>]+>[^<]?", "g"), function (x) { + .replace(new RegExp("<[^<>]+>[^<]?", "g"), (x) => { let ret; const tagRegEx = /<\/?([^\s/>]+)/.exec(x); - let tag = tagRegEx ? tagRegEx[1] : ""; - let p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x); + const tag = tagRegEx ? tagRegEx[1] : ""; + const p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x); if (p) { const pInd = parseInt(p[1]); @@ -318,24 +319,22 @@ function formatHtml(html: string) { if (["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"].indexOf(tag) >= 0) { // self closing tag ret = indent + x; + } else if (x.indexOf("") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length); + else ret = indent + x; + !p && (indent += tab); } else { - if (x.indexOf("") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length); - else ret = indent + x; - !p && (indent += tab); - } else { - //close tag - indent = indent.substr(0, indent.length - 1); - if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length); - else ret = indent + x; - } + //close tag + indent = indent.substr(0, indent.length - 1); + if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length); + else ret = indent + x; } return ret; }); for (i = pre.length; i--;) { - html = html.replace("<--TEMPPRE" + i + "/-->", pre[i].tag.replace("
            ", "
            \n").replace("
            ", pre[i].indent + "
            ")); + html = html.replace(`<--TEMPPRE${ i }/-->`, pre[i].tag.replace("
            ", "
            \n").replace("
            ", `${pre[i].indent }
            `)); } return html.charAt(0) === "\n" ? html.substr(1, html.length - 1) : html; @@ -364,11 +363,11 @@ type dynamicRequireMappings = { export function dynamicRequire(moduleName: T): Awaited{ if (typeof __non_webpack_require__ !== "undefined") { return __non_webpack_require__(moduleName); - } else { - // explicitly pass as string and not as expression to suppress webpack warning - // 'Critical dependency: the request of a dependency is an expression' - return require(`${moduleName}`); - } + } + // explicitly pass as string and not as expression to suppress webpack warning + // 'Critical dependency: the request of a dependency is an expression' + return require(`${moduleName}`); + } function timeLimit(promise: Promise, limitMs: number, errorMessage?: string) { @@ -509,8 +508,8 @@ export function escapeRegExp(str: string) { function areObjectsEqual(...args: unknown[]) { let i; let l; - let leftChain: Object[]; - let rightChain: Object[]; + let leftChain: object[]; + let rightChain: object[]; function compare2Objects(x: unknown, y: unknown) { let p; @@ -695,9 +694,9 @@ async function downloadAsSvg(nameWithoutExtension: string, svgSource: string | S try { const result = await snapdom(element, { - backgroundColor: "transparent", - scale: 2 - }); + backgroundColor: "transparent", + scale: 2 + }); triggerDownload(`${nameWithoutExtension}.svg`, result.url); } finally { cleanup(); @@ -733,9 +732,9 @@ async function downloadAsPng(nameWithoutExtension: string, svgSource: string | S try { const result = await snapdom(element, { - backgroundColor: "transparent", - scale: 2 - }); + backgroundColor: "transparent", + scale: 2 + }); const pngImg = await result.toPng(); await triggerDownload(`${nameWithoutExtension}.png`, pngImg.src); } finally { @@ -763,11 +762,11 @@ export function getSizeFromSvg(svgContent: string) { return { width: parseFloat(width), height: parseFloat(height) - } - } else { - console.warn("SVG export error", svgDocument.documentElement); - return null; - } + }; + } + console.warn("SVG export error", svgDocument.documentElement); + return null; + } /** @@ -896,9 +895,9 @@ export function mapToKeyValueArray(map: R export function getErrorMessage(e: unknown) { if (e && typeof e === "object" && "message" in e && typeof e.message === "string") { return e.message; - } else { - return "Unknown error"; - } + } + return "Unknown error"; + } /** @@ -913,6 +912,12 @@ export function handleRightToLeftPlacement(placement: T) { return placement; } +export function clamp(value: number, min: number, max: number) { + if (value < min) return min; + if (value > max) return max; + return value; +} + export default { reloadFrontendApp, restartDesktopApp, diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 8d41929ccd..8a708ea7ac 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -7,6 +7,7 @@ import { useEffect, useRef } from "preact/hooks"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; +import { clamp } from "../../services/utils"; import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; @@ -64,31 +65,113 @@ export default function RightPanelContainer() { const children = Array.from(rightPaneEl?.querySelectorAll(":scope > .card") ?? []); const pos = children.indexOf(cardEl); if (pos === -1) return; - const sizes = splitInstance.getSizes(); + + const sizes = splitInstance.getSizes(); // percentages + const COLLAPSED_SIZE = 0; // keep your current behavior; consider a small min later + + // Choose recipients/donors: nearest expanded panes first; if none, all except pos. + const recipients = getRecipientsByDistance(sizes, pos, COLLAPSED_SIZE); + const fallback = getExpandedIndices(sizes, pos, -Infinity); // all other panes + const targets = recipients.length ? recipients : fallback; + if (!expanded) { const sizeBeforeCollapse = sizes[pos]; sizesBeforeCollapse.current.set(cardEl, sizeBeforeCollapse); - sizes[pos] = 0; - const itemToExpand = pos > 0 ? pos - 1 : pos + 1; - if (sizes[itemToExpand] > COLLAPSED_SIZE) { - sizes[itemToExpand] += sizeBeforeCollapse; - } + // Collapse + sizes[pos] = COLLAPSED_SIZE; + + // Give freed space to other panes + const freed = sizeBeforeCollapse - COLLAPSED_SIZE; + distributeInto(sizes, targets, freed); } else { - const itemToExpand = pos > 0 ? pos - 1 : pos + 1; - const sizeBeforeCollapse = sizesBeforeCollapse.current.get(cardEl) ?? 50; + const want = sizesBeforeCollapse.current.get(cardEl) ?? 50; - if (sizes[itemToExpand] > COLLAPSED_SIZE) { - sizes[itemToExpand] -= sizeBeforeCollapse; - } - sizes[pos] = sizeBeforeCollapse; + // Take space back from other panes to expand this one + const took = takeFrom(sizes, targets, want); + + sizes[pos] = COLLAPSED_SIZE + took; // if donors couldn't provide all, expand partially } - console.log("Set sizes to ", sizes); + + // Optional: tiny cleanup to avoid negatives / floating drift + for (let i = 0; i < sizes.length; i++) sizes[i] = clamp(sizes[i], 0, 100); + + // Normalize to sum to 100 (Split.js likes this) + const sum = sizes.reduce((a, b) => a + b, 0); + if (sum > 0) { + for (let i = 0; i < sizes.length; i++) sizes[i] = (sizes[i] / sum) * 100; + } + splitInstance.setSizes(sizes); - }, + } }}> {items}
            ); } + +function getExpandedIndices(sizes, skipIndex, COLLAPSED_SIZE) { + const idxs = []; + for (let i = 0; i < sizes.length; i++) { + if (i === skipIndex) continue; + if (sizes[i] > COLLAPSED_SIZE) idxs.push(i); + } + return idxs; +} + +// Prefer nearby panes (VS Code-ish). Falls back to "all expanded panes". +function getRecipientsByDistance(sizes, pos, COLLAPSED_SIZE) { + const recipients = []; + for (let d = 1; d < sizes.length; d++) { + const left = pos - d; + const right = pos + d; + if (left >= 0 && sizes[left] > COLLAPSED_SIZE) recipients.push(left); + if (right < sizes.length && sizes[right] > COLLAPSED_SIZE) recipients.push(right); + } + return recipients; +} + +// Distribute `amount` into `recipients` proportionally to their current sizes. +function distributeInto(sizes, recipients, amount) { + if (amount === 0 || recipients.length === 0) return; + const total = recipients.reduce((sum, i) => sum + sizes[i], 0); + if (total <= 0) { + // equal split fallback + const delta = amount / recipients.length; + recipients.forEach(i => (sizes[i] += delta)); + return; + } + recipients.forEach(i => { + const share = (sizes[i] / total) * amount; + sizes[i] += share; + }); +} + +// Take `amount` out of `donors` proportionally, without driving anyone below 0. +// Returns how much was actually taken. +function takeFrom(sizes, donors, amount) { + if (amount <= 0 || donors.length === 0) return 0; + + // max each donor can contribute (don’t go below 0 here; you can change min if you want) + const caps = donors.map(i => ({ i, cap: Math.max(0, sizes[i]) })); + let remaining = amount; + + // iterative proportional take with caps + for (let iter = 0; iter < 5 && remaining > 1e-9; iter++) { + const active = caps.filter(x => x.cap > 1e-9); + if (active.length === 0) break; + + const total = active.reduce((s, x) => s + sizes[x.i], 0) || active.length; + for (const x of active) { + const weight = total === active.length ? 1 / active.length : (sizes[x.i] / total); + const want = remaining * weight; + const took = Math.min(x.cap, want); + sizes[x.i] -= took; + x.cap -= took; + remaining -= took; + if (remaining <= 1e-9) break; + } + } + return amount - remaining; +} From 7f7ec5d858b24a6b2e99c8346786c402fad67f91 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 15:40:09 +0200 Subject: [PATCH 610/873] chore(right_pane): make the gutter slightly bigger --- apps/client/src/widgets/sidebar/RightPanelContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 8a708ea7ac..f4f0984a3e 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -46,7 +46,7 @@ export default function RightPanelContainer() { const splitInstance = Split(elements, { direction: "vertical", minSize: COLLAPSED_SIZE, - gutterSize: 1 + gutterSize: 4 }); innerSplitRef.current = splitInstance; return () => splitInstance.destroy(); From 02294206ecd114b721e1b0b477d2378a06368b23 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 15:42:44 +0200 Subject: [PATCH 611/873] chore(right_pane): revert note data store --- apps/client/src/widgets/react/NoteStore.ts | 33 ---------------------- apps/client/src/widgets/react/hooks.tsx | 14 +-------- 2 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 apps/client/src/widgets/react/NoteStore.ts diff --git a/apps/client/src/widgets/react/NoteStore.ts b/apps/client/src/widgets/react/NoteStore.ts deleted file mode 100644 index 6f34c6d23f..0000000000 --- a/apps/client/src/widgets/react/NoteStore.ts +++ /dev/null @@ -1,33 +0,0 @@ -type Listener = () => void; - -class NoteSavedDataStore { - private data = new Map(); - private listeners = new Map>(); - - get(noteId: string) { - return this.data.get(noteId); - } - - set(noteId: string, value: string) { - this.data.set(noteId, value); - this.listeners.get(noteId)?.forEach(l => l()); - } - - subscribe(noteId: string, listener: Listener) { - let set = this.listeners.get(noteId); - if (!set) { - set = new Set(); - this.listeners.set(noteId, set); - } - set.add(listener); - - return () => { - set!.delete(listener); - if (set!.size === 0) { - this.listeners.delete(noteId); - } - }; - } -} - -export const noteSavedDataStore = new NoteSavedDataStore(); diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 73fbfc1016..eadbf12a67 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -3,7 +3,7 @@ import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } f import { Tooltip } from "bootstrap"; import Mark from "mark.js"; import { RefObject, VNode } from "preact"; -import { CSSProperties, useSyncExternalStore } from "preact/compat"; +import { CSSProperties } from "preact/compat"; import { MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; import appContext, { EventData, EventNames } from "../../components/app_context"; @@ -26,8 +26,6 @@ import utils, { escapeRegExp, randomString, reloadFrontendApp } from "../../serv import BasicWidget, { ReactWrappedWidget } from "../basic_widget"; import NoteContextAwareWidget from "../note_context_aware_widget"; import { DragData } from "../note_tree"; -import CKEditor from "./CKEditor"; -import { noteSavedDataStore } from "./NoteStore"; import { NoteContextContext, ParentComponent, refToJQuerySelector } from "./react_utils"; export function useTriliumEvent(eventName: T, handler: (data: EventData) => void) { @@ -115,7 +113,6 @@ export function useEditorSpacedUpdate({ note, noteContext, getData, onContentCha protected_session_holder.touchProtectedSessionIfNecessary(note); await server.put(`notes/${note.noteId}/data`, data, parentComponent?.componentId); - noteSavedDataStore.set(note.noteId, data.content); dataSaved?.(data); }; }, [ note, getData, dataSaved ]); @@ -124,7 +121,6 @@ export function useEditorSpacedUpdate({ note, noteContext, getData, onContentCha // React to note/blob changes. useEffect(() => { if (!blob) return; - noteSavedDataStore.set(note.noteId, blob.content); spacedUpdate.allowUpdateWithoutChange(() => onContentChange(blob.content)); }, [ blob ]); @@ -156,14 +152,6 @@ export function useEditorSpacedUpdate({ note, noteContext, getData, onContentCha return spacedUpdate; } -export function useNoteSavedData(noteId: string | undefined) { - return useSyncExternalStore( - (cb) => noteId ? noteSavedDataStore.subscribe(noteId, cb) : () => {}, - () => noteId ? noteSavedDataStore.get(noteId) : undefined - ); -} - - /** * Allows a React component to read and write a Trilium option, while also watching for external changes. * From 7b04ca8cc7ca625b15368cc61ace163f5a4d2af4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 15:57:48 +0200 Subject: [PATCH 612/873] style(right_pane): improve header space slightly --- .../src/widgets/sidebar/RightPanelContainer.css | 12 ++++++++++-- .../src/widgets/sidebar/RightPanelContainer.tsx | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index d737e9d526..40d0cb6c0d 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -6,8 +6,12 @@ body.experimental-feature-new-layout #right-pane { border-bottom: 1px solid var(--main-border-color); border-radius: 0; - .card-header-title { - padding-inline: 0.5em; + .card-header { + padding-block: 0.2em; + + .card-header-title { + padding-inline: 0.5em; + } } &:last-of-type { @@ -18,4 +22,8 @@ body.experimental-feature-new-layout #right-pane { transform: rotate(-90deg); } } + + .gutter-vertical + .card .card-header { + padding-top: 0; + } } diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index f4f0984a3e..41ca58fc29 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -12,7 +12,7 @@ import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; -const COLLAPSED_SIZE = 32; +const COLLAPSED_SIZE = 25; export const RightPanelContext = createContext({ setExpanded(cardEl: HTMLElement, expanded: boolean) {} From 7af063e7cd08d8db6165fc1fd9a22fbb19e7da16 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 16:15:21 +0200 Subject: [PATCH 613/873] feat(right_pane): simplify collapsing mechanism --- .../widgets/sidebar/RightPanelContainer.css | 6 + .../widgets/sidebar/RightPanelContainer.tsx | 136 +----------------- .../src/widgets/sidebar/RightPanelWidget.tsx | 11 +- 3 files changed, 10 insertions(+), 143 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 40d0cb6c0d..424c345010 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -1,5 +1,7 @@ body.experimental-feature-new-layout #right-pane { width: 300px; + display: flex; + flex-direction: column; .card { margin-inline: 0; @@ -23,6 +25,10 @@ body.experimental-feature-new-layout #right-pane { } } + .card:not(.collapsed) { + flex-grow: 1; + } + .gutter-vertical + .card .card-header { padding-top: 0; } diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 41ca58fc29..1a837bd1d5 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -12,11 +12,6 @@ import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; -const COLLAPSED_SIZE = 25; - -export const RightPanelContext = createContext({ - setExpanded(cardEl: HTMLElement, expanded: boolean) {} -}); export default function RightPanelContainer() { // Split between right pane and the content pane. @@ -38,140 +33,11 @@ export default function RightPanelContainer() { ]; - // Split between items. - const innerSplitRef = useRef(null); - useEffect(() => { - const rightPaneContainer = document.getElementById("right-pane"); - const elements = Array.from(rightPaneContainer?.children ?? []) as HTMLElement[]; - const splitInstance = Split(elements, { - direction: "vertical", - minSize: COLLAPSED_SIZE, - gutterSize: 4 - }); - innerSplitRef.current = splitInstance; - return () => splitInstance.destroy(); - }, [ items ]); - const sizesBeforeCollapse = useRef(new WeakMap()); return (
            - .card") ?? []); - const pos = children.indexOf(cardEl); - if (pos === -1) return; - - const sizes = splitInstance.getSizes(); // percentages - const COLLAPSED_SIZE = 0; // keep your current behavior; consider a small min later - - // Choose recipients/donors: nearest expanded panes first; if none, all except pos. - const recipients = getRecipientsByDistance(sizes, pos, COLLAPSED_SIZE); - const fallback = getExpandedIndices(sizes, pos, -Infinity); // all other panes - const targets = recipients.length ? recipients : fallback; - - if (!expanded) { - const sizeBeforeCollapse = sizes[pos]; - sizesBeforeCollapse.current.set(cardEl, sizeBeforeCollapse); - - // Collapse - sizes[pos] = COLLAPSED_SIZE; - - // Give freed space to other panes - const freed = sizeBeforeCollapse - COLLAPSED_SIZE; - distributeInto(sizes, targets, freed); - } else { - const want = sizesBeforeCollapse.current.get(cardEl) ?? 50; - - // Take space back from other panes to expand this one - const took = takeFrom(sizes, targets, want); - - sizes[pos] = COLLAPSED_SIZE + took; // if donors couldn't provide all, expand partially - } - - // Optional: tiny cleanup to avoid negatives / floating drift - for (let i = 0; i < sizes.length; i++) sizes[i] = clamp(sizes[i], 0, 100); - - // Normalize to sum to 100 (Split.js likes this) - const sum = sizes.reduce((a, b) => a + b, 0); - if (sum > 0) { - for (let i = 0; i < sizes.length; i++) sizes[i] = (sizes[i] / sum) * 100; - } - - splitInstance.setSizes(sizes); - } - }}> - {items} - + {items}
            ); } - -function getExpandedIndices(sizes, skipIndex, COLLAPSED_SIZE) { - const idxs = []; - for (let i = 0; i < sizes.length; i++) { - if (i === skipIndex) continue; - if (sizes[i] > COLLAPSED_SIZE) idxs.push(i); - } - return idxs; -} - -// Prefer nearby panes (VS Code-ish). Falls back to "all expanded panes". -function getRecipientsByDistance(sizes, pos, COLLAPSED_SIZE) { - const recipients = []; - for (let d = 1; d < sizes.length; d++) { - const left = pos - d; - const right = pos + d; - if (left >= 0 && sizes[left] > COLLAPSED_SIZE) recipients.push(left); - if (right < sizes.length && sizes[right] > COLLAPSED_SIZE) recipients.push(right); - } - return recipients; -} - -// Distribute `amount` into `recipients` proportionally to their current sizes. -function distributeInto(sizes, recipients, amount) { - if (amount === 0 || recipients.length === 0) return; - const total = recipients.reduce((sum, i) => sum + sizes[i], 0); - if (total <= 0) { - // equal split fallback - const delta = amount / recipients.length; - recipients.forEach(i => (sizes[i] += delta)); - return; - } - recipients.forEach(i => { - const share = (sizes[i] / total) * amount; - sizes[i] += share; - }); -} - -// Take `amount` out of `donors` proportionally, without driving anyone below 0. -// Returns how much was actually taken. -function takeFrom(sizes, donors, amount) { - if (amount <= 0 || donors.length === 0) return 0; - - // max each donor can contribute (don’t go below 0 here; you can change min if you want) - const caps = donors.map(i => ({ i, cap: Math.max(0, sizes[i]) })); - let remaining = amount; - - // iterative proportional take with caps - for (let iter = 0; iter < 5 && remaining > 1e-9; iter++) { - const active = caps.filter(x => x.cap > 1e-9); - if (active.length === 0) break; - - const total = active.reduce((s, x) => s + sizes[x.i], 0) || active.length; - for (const x of active) { - const weight = total === active.length ? 1 / active.length : (sizes[x.i] / total); - const want = remaining * weight; - const took = Math.min(x.cap, want); - sizes[x.i] -= took; - x.cap -= took; - remaining -= took; - if (remaining <= 1e-9) break; - } - } - return amount - remaining; -} diff --git a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx index 7f4391d6cd..b421ba3c3e 100644 --- a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx @@ -4,7 +4,6 @@ import { useContext, useRef, useState } from "preact/hooks"; import Icon from "../react/Icon"; import { ParentComponent } from "../react/react_utils"; -import { RightPanelContext } from "./RightPanelContainer"; interface RightPanelWidgetProps { title: string; @@ -13,7 +12,6 @@ interface RightPanelWidgetProps { } export default function RightPanelWidget({ title, buttons, children }: RightPanelWidgetProps) { - const rightPanelContext = useContext(RightPanelContext); const [ expanded, setExpanded ] = useState(true); const containerRef = useRef(null); const parentComponent = useContext(ParentComponent); @@ -31,9 +29,6 @@ export default function RightPanelWidget({ title, buttons, children }: RightPane { - if (containerRef.current) { - rightPanelContext.setExpanded(containerRef.current, !expanded); - } setExpanded(!expanded); }} /> @@ -42,9 +37,9 @@ export default function RightPanelWidget({ title, buttons, children }: RightPane
            -
            - {expanded && children} -
            + {expanded &&
            + {children} +
            }
    ); From 57081a1bfb448a892436c2e62e1e4f84e6b0cc60 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 16:16:52 +0200 Subject: [PATCH 614/873] feat(right_pane): make whole title clickable --- apps/client/src/widgets/sidebar/RightPanelContainer.css | 1 + apps/client/src/widgets/sidebar/RightPanelWidget.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 424c345010..4153db7c11 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -10,6 +10,7 @@ body.experimental-feature-new-layout #right-pane { .card-header { padding-block: 0.2em; + cursor: pointer; .card-header-title { padding-inline: 0.5em; diff --git a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx index b421ba3c3e..7f2aceedf0 100644 --- a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx @@ -25,12 +25,12 @@ export default function RightPanelWidget({ title, buttons, children }: RightPane ref={containerRef} class={clsx("card widget", !expanded && "collapsed")} > -
    +
    setExpanded(!expanded)} + > { - setExpanded(!expanded); - }} />
    {title}
    {buttons}
    From a4024d17baa1b81cf1c2b22be23c3bebe9f08e61 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 16:18:31 +0200 Subject: [PATCH 615/873] fix(highlights_list): empty results --- apps/client/src/widgets/sidebar/HighlightsList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index be1e170e16..ad634935fd 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -137,7 +137,7 @@ function extractHighlightsFromTextEditor(editor: CKTextEditor) { if (!root) return []; for (const { item } of editor.model.createRangeIn(root).getWalker({ ignoreElementEnd: true })) { - if (!item.is('$textProxy')) continue; + if (!item.is('$textProxy') || !item.data.trim()) continue; const attrs: RawHighlight["attrs"] = { bold: item.hasAttribute('bold'), @@ -198,7 +198,7 @@ function extractHeadingsFromStaticHtml(el: HTMLElement | null) { let node: Node | null; while ((node = walker.nextNode())) { const el = node.parentElement; - if (!el || !node.textContent) continue; + if (!el || !node.textContent?.trim()) continue; const style = getComputedStyle(el); From ddb6b3ea8ae3129efe7c68523af5cc211d9ada4b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 16:29:35 +0200 Subject: [PATCH 616/873] feat(right_pane): store expansion state --- .../src/widgets/sidebar/HighlightsList.tsx | 2 +- .../widgets/sidebar/RightPanelContainer.tsx | 10 ++------- .../src/widgets/sidebar/RightPanelWidget.tsx | 21 ++++++++++++++++--- .../src/widgets/sidebar/TableOfContents.tsx | 2 +- apps/server/src/routes/api/options.ts | 3 ++- apps/server/src/services/options_init.ts | 1 + packages/commons/src/lib/options_interface.ts | 1 + 7 files changed, 26 insertions(+), 14 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index ad634935fd..ed73bac606 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -24,7 +24,7 @@ export default function HighlightsList() { const { isReadOnly } = useIsNoteReadOnly(note, noteContext); return ( - + {((noteType === "text" && isReadOnly) || (noteType === "doc")) && } {noteType === "text" && !isReadOnly && } diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 1a837bd1d5..a12bb447cc 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -28,16 +28,10 @@ export default function RightPanelContainer() { return () => splitInstance.destroy(); }, []); - const items = [ - , - - ]; - - const sizesBeforeCollapse = useRef(new WeakMap()); - return (
    - {items} + +
    ); } diff --git a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx index 7f2aceedf0..6b3a56925f 100644 --- a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx @@ -2,17 +2,20 @@ import clsx from "clsx"; import { ComponentChildren } from "preact"; import { useContext, useRef, useState } from "preact/hooks"; +import { useTriliumOptionJson } from "../react/hooks"; import Icon from "../react/Icon"; import { ParentComponent } from "../react/react_utils"; interface RightPanelWidgetProps { + id: string; title: string; children: ComponentChildren; buttons?: ComponentChildren; } -export default function RightPanelWidget({ title, buttons, children }: RightPanelWidgetProps) { - const [ expanded, setExpanded ] = useState(true); +export default function RightPanelWidget({ id, title, buttons, children }: RightPanelWidgetProps) { + const [ rightPaneCollapsedItems, setRightPaneCollapsedItems ] = useTriliumOptionJson("rightPaneCollapsedItems"); + const [ expanded, setExpanded ] = useState(!rightPaneCollapsedItems.includes(id)); const containerRef = useRef(null); const parentComponent = useContext(ParentComponent); @@ -27,7 +30,19 @@ export default function RightPanelWidget({ title, buttons, children }: RightPane >
    setExpanded(!expanded)} + onClick={() => { + const newExpanded = !expanded; + setExpanded(newExpanded); + const rightPaneCollapsedItemsSet = new Set(rightPaneCollapsedItems); + if (newExpanded) { + rightPaneCollapsedItemsSet.delete(id); + } else { + rightPaneCollapsedItemsSet.add(id); + } + if (rightPaneCollapsedItemsSet.size !== rightPaneCollapsedItems.length) { + setRightPaneCollapsedItems(Array.from(rightPaneCollapsedItemsSet)); + } + }} > + {((noteType === "text" && isReadOnly) || (noteType === "doc")) && } {noteType === "text" && !isReadOnly && } diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index 0a6940cb7b..e7377bdfdb 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -51,8 +51,9 @@ const ALLOWED_OPTIONS = new Set([ "imageMaxWidthHeight", "imageJpegQuality", "leftPaneWidth", - "rightPaneWidth", "leftPaneVisible", + "rightPaneWidth", + "rightPaneCollapsedItems", "rightPaneVisible", "nativeTitleBarVisible", "headingStyle", diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index 30754ab114..e8ef3c6947 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -105,6 +105,7 @@ const defaultOptions: DefaultOption[] = [ { name: "leftPaneVisible", value: "true", isSynced: false }, { name: "rightPaneWidth", value: "25", isSynced: false }, { name: "rightPaneVisible", value: "true", isSynced: false }, + { name: "rightPaneCollapsedItems", value: "[]", isSynced: false }, { name: "nativeTitleBarVisible", value: "false", isSynced: false }, { name: "eraseEntitiesAfterTimeInSeconds", value: "604800", isSynced: true }, // default is 7 days { name: "eraseEntitiesAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index 4bf445c12b..fc1564e339 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -77,6 +77,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Thu, 18 Dec 2025 16:31:43 +0200 Subject: [PATCH 617/873] chore(floating_buttons): revert changes due to new layout --- apps/client/src/layouts/desktop_layout.tsx | 2 +- .../widgets/FloatingButtonsDefinitions.tsx | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 013460723f..af47fd4788 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -146,7 +146,7 @@ export default class DesktopLayout { .optChild(isNewLayout, )) .optChild(!isNewLayout, ) .child(new WatchedFileUpdateStatusWidget()) - .child() + .optChild(!isNewLayout, ) .child( new ScrollingContainer() .filling() diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 4cd6e1c5f6..35dbc92ae8 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -78,10 +78,8 @@ export const POPUP_HIDDEN_FLOATING_BUTTONS: FloatingButtonsList = [ ToggleReadOnlyButton ]; -const isNewLayout = isExperimentalFeatureEnabled("new-layout"); - function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefaultViewMode }: FloatingButtonContext) { - const isEnabled = !isNewLayout && (note.noteId === "_backendLog" || note.type === "render") && isDefaultViewMode; + const isEnabled = (note.noteId === "_backendLog" || note.type === "render") && isDefaultViewMode; return isEnabled && (null); const isEnabled = ( - !isNewLayout - && ["mermaid", "canvas", "mindMap", "image"].includes(note?.type ?? "") + ["mermaid", "canvas", "mindMap", "image"].includes(note?.type ?? "") && note?.isContentAvailable() && isDefaultViewMode ); @@ -287,7 +284,7 @@ function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonCon } function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingButtonContext) { - const isEnabled = !isNewLayout && ["mermaid", "mindMap"].includes(note?.type ?? "") + const isEnabled = ["mermaid", "mindMap"].includes(note?.type ?? "") && note?.isContentAvailable() && isDefaultViewMode; return isEnabled && ( <> @@ -308,7 +305,7 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB function InAppHelpButton({ note }: FloatingButtonContext) { const helpUrl = getHelpUrlForNote(note); - const isEnabled = !!helpUrl && !isNewLayout; + const isEnabled = !!helpUrl; return isEnabled && ( 0; + const isEnabled = isDefaultViewMode && backlinkCount > 0; return (isEnabled &&
    Date: Thu, 18 Dec 2025 16:58:15 +0200 Subject: [PATCH 618/873] feat(floating_buttons): handle case when empty --- .../src/translations/en/translation.json | 4 ++ .../src/widgets/sidebar/HighlightsList.tsx | 5 +-- .../widgets/sidebar/RightPanelContainer.css | 17 +++++++ .../widgets/sidebar/RightPanelContainer.tsx | 44 ++++++++++++++----- .../src/widgets/sidebar/TableOfContents.tsx | 2 +- 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 0f1e12781d..c7aeaf50c4 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2196,5 +2196,9 @@ "note_paths_other": "{{count}} paths", "note_paths_title": "Note paths", "code_note_switcher": "Change language mode" + }, + "right_pane": { + "empty_message": "Nothing to show for this note", + "empty_button": "Hide the panel" } } diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index ed73bac606..fa9919a32d 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -1,4 +1,3 @@ -import { headingIsHorizontal } from "@excalidraw/excalidraw/element/heading"; import { CKTextEditor, ModelText } from "@triliumnext/ckeditor5"; import { useCallback, useEffect, useState } from "preact/hooks"; @@ -23,9 +22,9 @@ export default function HighlightsList() { const noteType = useNoteProperty(note, "type"); const { isReadOnly } = useIsNoteReadOnly(note, noteContext); - return ( + return (noteType === "text") && ( - {((noteType === "text" && isReadOnly) || (noteType === "doc")) && } + {noteType === "text" && isReadOnly && } {noteType === "text" && !isReadOnly && } ); diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 4153db7c11..443fc35b87 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -33,4 +33,21 @@ body.experimental-feature-new-layout #right-pane { .gutter-vertical + .card .card-header { padding-top: 0; } + + .no-items { + display: flex; + align-items: center; + justify-content: center; + flex-grow: 1; + flex-direction: column; + color: var(--muted-text-color); + + .bx { + font-size: 3em; + } + + button { + margin-top: 1em; + } + } } diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index a12bb447cc..83a8d957f1 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -2,18 +2,49 @@ import "./RightPanelContainer.css"; import Split from "@triliumnext/split.js"; -import { createContext } from "preact"; -import { useEffect, useRef } from "preact/hooks"; +import { useEffect } from "preact/hooks"; +import { t } from "../../services/i18n"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; -import { clamp } from "../../services/utils"; +import Button from "../react/Button"; +import { useActiveNoteContext, useNoteProperty, useTriliumOptionBool } from "../react/hooks"; +import Icon from "../react/Icon"; import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; export default function RightPanelContainer() { + useSplit(); + + const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible"); + const { note } = useActiveNoteContext(); + const noteType = useNoteProperty(note, "type"); + const items = [ + noteType === "text" || noteType === "doc" && , + noteType === "text" && + ].filter(Boolean); + + return ( +
    + {items.length > 0 ? ( + items + ) : ( +
    + + {t("right_pane.empty_message")} +
    + )} +
    + ); +} + +function useSplit() { // Split between right pane and the content pane. useEffect(() => { // We are intentionally omitting useTriliumOption to avoid re-render due to size change. @@ -27,11 +58,4 @@ export default function RightPanelContainer() { }); return () => splitInstance.destroy(); }, []); - - return ( -
    - - -
    - ); } diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 48c5217e99..bbdde25f8d 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -25,7 +25,7 @@ export default function TableOfContents() { const noteType = useNoteProperty(note, "type"); const { isReadOnly } = useIsNoteReadOnly(note, noteContext); - return ( + return (noteType === "text" || noteType === "doc") && ( {((noteType === "text" && isReadOnly) || (noteType === "doc")) && } {noteType === "text" && !isReadOnly && } From a986c84ce7586b76239ae45409eaf849ebb6b96e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 16:59:40 +0200 Subject: [PATCH 619/873] chore(right_pane): remove redundant check for note type --- apps/client/src/widgets/sidebar/HighlightsList.tsx | 2 +- apps/client/src/widgets/sidebar/TableOfContents.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index fa9919a32d..c3621d6f5f 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -22,7 +22,7 @@ export default function HighlightsList() { const noteType = useNoteProperty(note, "type"); const { isReadOnly } = useIsNoteReadOnly(note, noteContext); - return (noteType === "text") && ( + return ( {noteType === "text" && isReadOnly && } {noteType === "text" && !isReadOnly && } diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index bbdde25f8d..48c5217e99 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -25,7 +25,7 @@ export default function TableOfContents() { const noteType = useNoteProperty(note, "type"); const { isReadOnly } = useIsNoteReadOnly(note, noteContext); - return (noteType === "text" || noteType === "doc") && ( + return ( {((noteType === "text" && isReadOnly) || (noteType === "doc")) && } {noteType === "text" && !isReadOnly && } From 334c31e79da2a0845b04f29a38d0b5efb373b1cb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 17:00:11 +0200 Subject: [PATCH 620/873] fix(right_pane): table of contents no longer visible --- apps/client/src/widgets/sidebar/RightPanelContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 83a8d957f1..e10fae7492 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -22,7 +22,7 @@ export default function RightPanelContainer() { const { note } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); const items = [ - noteType === "text" || noteType === "doc" && , + (noteType === "text" || noteType === "doc") && , noteType === "text" && ].filter(Boolean); From 8eb6bf402d88fe2af3a651fabab215bd4dc2c995 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 16 Dec 2025 23:59:15 +0100 Subject: [PATCH 621/873] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ --- docs/README-nl.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/README-nl.md b/docs/README-nl.md index 1f1bcea0ee..2b9783ed91 100644 --- a/docs/README-nl.md +++ b/docs/README-nl.md @@ -57,11 +57,10 @@ Onze documentatie is beschikbaar in meerdere formaten: ### Snelkoppelingen - [Getting Started Gids](https://docs.triliumnotes.org/) -- [Vertaal Instructies](https://docs.triliumnotes.org/user-guide/setup) +- [Installatie Instructies] - [Docker Installatie](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker) -- [TriliumNext - Upgraden](https://docs.triliumnotes.org/user-guide/setup/upgrading) +- [Upgrading TriliumNext] - [Basis Concepten en Features](https://docs.triliumnotes.org/user-guide/concepts/notes) - [Patronen van Personal Knowledge @@ -87,14 +86,14 @@ Onze documentatie is beschikbaar in meerdere formaten: * Notitie[-attributen](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes) kunnen worden ingezet voor notitie-organisatie, queries en geavanceerd [scripten](https://docs.triliumnotes.org/user-guide/scripts) -* Gebruikersinterfacevariabele in het Engels, Duits, Spaans, Frans, Roemeens en - Chinees (versimpeld en traditioneel) +* Gebruikersinterface verkrijgbaar in het Engels, Duits, Spaans, Frans, Roemeens + en Chinees (versimpeld en traditioneel) * Directe [OpenID en TOTP integratie](https://docs.triliumnotes.org/user-guide/setup/server/mfa) voor beter beveiligde aanmelding * [Synchronisatie](https://docs.triliumnotes.org/user-guide/setup/synchronization) met zelfgehoste synchronisatieserver - * there are [3rd party services for hosting synchronisation + * er zijn [3rd party services for hosting synchronisation server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) * [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) (publishing) notes to public internet From 77c1a00831fcdb57233c65f814177c6689d0c3b1 Mon Sep 17 00:00:00 2001 From: green Date: Wed, 17 Dec 2025 03:20:25 +0100 Subject: [PATCH 622/873] Translated using Weblate (Japanese) Currently translated at 100.0% (1703 of 1703 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- apps/client/src/translations/ja/translation.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 8ba8acffda..13a7bbb092 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -607,7 +607,8 @@ "subtree_size": "(サブツリーサイズ: {{size}}、ノード数: {{count}})", "title": "ノート情報", "note_size_info": "ノートのサイズは、このノートに必要なストレージの概算を示します。これは、ノートの内容とそのノートの編集履歴の内容を考慮したものです。", - "show_similar_notes": "類似のノートを表示" + "show_similar_notes": "類似のノートを表示", + "mime": "MIME タイプ" }, "image_properties": { "file_type": "ファイルタイプ", @@ -2179,6 +2180,8 @@ "hoisted_badge": "ホイスト", "hoisted_badge_title": "ホイスト解除", "workspace_badge": "ワークスペース", - "scroll_to_top_title": "ノートの先頭にジャンプ" + "scroll_to_top_title": "ノートの先頭にジャンプ", + "create_new_note": "新しい子ノートを作成", + "empty_hide_archived_notes": "アーカイブされたノートを非表示" } } From 65ebbc71f59d75f1b820789733cbe39f54493fa8 Mon Sep 17 00:00:00 2001 From: noobhjy Date: Wed, 17 Dec 2025 02:37:38 +0100 Subject: [PATCH 623/873] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (1703 of 1703 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/ --- apps/client/src/translations/cn/translation.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index eed8421467..1cccca089c 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -829,7 +829,8 @@ "calculate": "计算", "subtree_size": "(子树大小: {{size}}, 共计 {{count}} 个笔记)", "title": "笔记信息", - "show_similar_notes": "显示相似的笔记" + "show_similar_notes": "显示相似的笔记", + "mime": "文件类型" }, "note_map": { "open_full": "展开显示", @@ -2179,6 +2180,8 @@ "workspace_badge": "工作空间", "scroll_to_top_title": "跳转到笔记开始", "hoisted_badge_title": "取消聚焦", - "hoisted_badge": "聚焦" + "hoisted_badge": "聚焦", + "create_new_note": "新建子笔记", + "empty_hide_archived_notes": "隐藏已存档的笔记" } } From a82b12a599431e7ce777dc92702fa446f7041959 Mon Sep 17 00:00:00 2001 From: Luk On Date: Wed, 17 Dec 2025 12:10:07 +0100 Subject: [PATCH 624/873] Translated using Weblate (Polish) Currently translated at 100.0% (1703 of 1703 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/ --- apps/client/src/translations/pl/translation.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/pl/translation.json b/apps/client/src/translations/pl/translation.json index a4db9fff2c..af11341ade 100644 --- a/apps/client/src/translations/pl/translation.json +++ b/apps/client/src/translations/pl/translation.json @@ -440,7 +440,8 @@ "calculate": "oblicz", "subtree_size": "(rozmiar poddrzewa: {{size}} w {{count}} notatkach)", "title": "Info o notatce", - "show_similar_notes": "Pokaż podobne notatki" + "show_similar_notes": "Pokaż podobne notatki", + "mime": "Typ MIME" }, "note_map": { "open_full": "Rozwiń do pełnego", @@ -2193,6 +2194,8 @@ "hoisted_badge": "Wyróżniony", "hoisted_badge_title": "Usuń wyróżnienie", "workspace_badge": "Obszar roboczy", - "scroll_to_top_title": "Przejdź na początek notatki" + "scroll_to_top_title": "Przejdź na początek notatki", + "create_new_note": "Utwórz nową notatkę podrzędną", + "empty_hide_archived_notes": "Ukryj zarchiwizowane notatki" } } From cc84d092301292e239e60833e519328e64967cca Mon Sep 17 00:00:00 2001 From: Luk On Date: Thu, 18 Dec 2025 09:49:01 +0100 Subject: [PATCH 625/873] Translated using Weblate (Polish) Currently translated at 100.0% (1709 of 1709 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/ --- apps/client/src/translations/pl/translation.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/pl/translation.json b/apps/client/src/translations/pl/translation.json index af11341ade..79d6687a51 100644 --- a/apps/client/src/translations/pl/translation.json +++ b/apps/client/src/translations/pl/translation.json @@ -232,14 +232,20 @@ "background_effects_title": "Efekty tła są teraz stabilne", "dismiss": "Odrzuć", "background_effects_button": "Włącz efekty tła", - "background_effects_message": "Na urządzeniach z systemem Windows efekty tła są teraz w pełni stabilne. Efekty tła dodają odrobinę koloru do interfejsu użytkownika poprzez rozmycie tła za nim. Ta technika jest również stosowana w innych aplikacjach, takich jak Eksplorator Windows." + "background_effects_message": "Na urządzeniach z systemem Windows efekty tła są teraz w pełni stabilne. Efekty tła dodają odrobinę koloru do interfejsu użytkownika poprzez rozmycie tła za nim. Ta technika jest również stosowana w innych aplikacjach, takich jak Eksplorator Windows.", + "new_layout_title": "Nowy układ", + "new_layout_message": "Wprowadziliśmy zmodernizowany układ interfejsu dla Trilium. Wstążka została usunięta i płynnie zintegrowana z głównym interfejsem, a jej kluczowe funkcje przejęły nowy pasek stanu i rozwijane sekcje (takie jak promowane atrybuty).\n\nNowy układ jest domyślnie włączony i można go tymczasowo wyłączyć w Ustawienia → Wygląd.", + "new_layout_button": "Szczegóły" }, "settings": { "related_settings": "Powiązane ustawienia" }, "settings_appearance": { "related_code_blocks": "Schemat kolorów dla bloków kodu w notatkach tekstowych", - "related_code_notes": "Schemat kolorów dla notatek kodu" + "related_code_notes": "Schemat kolorów dla notatek kodu", + "ui": "Interfejs użytkownika", + "ui_old_layout": "Stary układ", + "ui_new_layout": "Nowy układ" }, "units": { "percentage": "%" From b9c6cae5b4a2010bf3cf7f35a5dde8dd754247ac Mon Sep 17 00:00:00 2001 From: green Date: Thu, 18 Dec 2025 08:28:35 +0100 Subject: [PATCH 626/873] Translated using Weblate (Japanese) Currently translated at 100.0% (1709 of 1709 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- apps/client/src/translations/ja/translation.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 13a7bbb092..fd47a43472 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -2005,14 +2005,20 @@ "background_effects_title": "背景効果が安定しました", "background_effects_message": "Windowsデバイスでは、背景効果が完全に安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。この技術は、Windowsエクスプローラーなどの他のアプリケーションでも使用されています。", "background_effects_button": "背景効果を有効にする", - "dismiss": "却下" + "dismiss": "却下", + "new_layout_title": "新しいレイアウト", + "new_layout_message": "Trilium のレイアウトを刷新しました。リボンは廃止され、メインインターフェースにシームレスに統合されました。主要な機能は、新しいステータスバーと展開可能なセクション(プロモート属性など)に集約されています。\n\n新しいレイアウトはデフォルトで有効になっていますが、「オプション」→「外観」から一時的に無効にすることもできます。", + "new_layout_button": "詳細情報" }, "settings": { "related_settings": "関連設定" }, "settings_appearance": { "related_code_blocks": "テキストノート内のコードブロックの配色", - "related_code_notes": "コードノートの配色" + "related_code_notes": "コードノートの配色", + "ui": "ユーザーインターフェース", + "ui_old_layout": "旧レイアウト", + "ui_new_layout": "新しいレイアウト" }, "units": { "percentage": "%" From 3411ed79d8a225e7bacc761558e4b0c408cb7f88 Mon Sep 17 00:00:00 2001 From: noobhjy Date: Thu, 18 Dec 2025 02:27:28 +0100 Subject: [PATCH 627/873] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (1709 of 1709 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/ --- apps/client/src/translations/cn/translation.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index 1cccca089c..47ad9e2d41 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -2081,14 +2081,20 @@ "next_theme_title": "试用新 Trilium 主题", "next_theme_message": "当前使用旧版主题,要试用新主题吗?", "next_theme_button": "试用新主题", - "dismiss": "关闭" + "dismiss": "关闭", + "new_layout_message": "我们为 Trilium 引入了现代化的布局。Ribbon 界面已被移除并无缝集成到主界面中,新的状态栏和可展开部分(例如“已提升属性”)取代了其主要功能。\n\n新布局默认启用,您可以通过“选项”→“外观”暂时禁用它。", + "new_layout_button": "更多信息", + "new_layout_title": "新布局" }, "settings": { "related_settings": "相关设置" }, "settings_appearance": { "related_code_blocks": "文本笔记中代码块的色彩方案", - "related_code_notes": "代码笔记的色彩方案" + "related_code_notes": "代码笔记的色彩方案", + "ui": "用户界面", + "ui_old_layout": "旧布局", + "ui_new_layout": "新布局" }, "units": { "percentage": "%" From 29115f5e613402aaa9603c21bb86777fbb5c5331 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Dec 2025 23:19:21 +0100 Subject: [PATCH 628/873] Translated using Weblate (Dutch) Currently translated at 25.8% (30 of 116 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/nl/ --- docs/README-nl.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/README-nl.md b/docs/README-nl.md index 2b9783ed91..093ceaf4cf 100644 --- a/docs/README-nl.md +++ b/docs/README-nl.md @@ -57,10 +57,11 @@ Onze documentatie is beschikbaar in meerdere formaten: ### Snelkoppelingen - [Getting Started Gids](https://docs.triliumnotes.org/) -- [Installatie Instructies] +- [Installatie Instructies](https://docs.triliumnotes.org/user-guide/setup) - [Docker Installatie](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker) -- [Upgrading TriliumNext] +- [Upgrading + TriliumNext](https://docs.triliumnotes.org/user-guide/setup/upgrading) - [Basis Concepten en Features](https://docs.triliumnotes.org/user-guide/concepts/notes) - [Patronen van Personal Knowledge @@ -93,8 +94,8 @@ Onze documentatie is beschikbaar in meerdere formaten: beter beveiligde aanmelding * [Synchronisatie](https://docs.triliumnotes.org/user-guide/setup/synchronization) met zelfgehoste synchronisatieserver - * er zijn [3rd party services for hosting synchronisation - server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) + * er zijn [diensten van derden voor het hosten van de + synchronisatieserver](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) * [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) (publishing) notes to public internet * Strong [note From 5bcec9fcfdb95171cdd443d5d07ae781c815df19 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 01:21:52 +0200 Subject: [PATCH 629/873] style/note icon: add hover effect for custom colors --- apps/client/src/stylesheets/theme-next-dark.css | 1 + apps/client/src/stylesheets/theme-next-light.css | 2 +- apps/client/src/widgets/note_icon.css | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 574476806a..ff1482399e 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -329,4 +329,5 @@ body .todo-list input[type="checkbox"]:not(:checked):before { .note-split.with-hue { --note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%); --note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%); + --note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%); } \ No newline at end of file diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 30396e8544..1d819576ad 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -304,5 +304,5 @@ */ --note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%); --note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%); - + --note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%); } \ No newline at end of file diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index 7bcb794a43..80e0732f95 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -107,7 +107,7 @@ body.experimental-feature-new-layout { } &:hover:not(.bx-empty:disabled)::after { - background: var(--note-icon-button-hover-background-color); + background: var(--note-icon-hover-custom-background-color, --note-icon-button-hover-background-color); transition: background 200ms ease-out; } } From b8af9616900d127ea05b599a110c0dbed75cab02 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 01:23:56 +0200 Subject: [PATCH 630/873] style/note icon: refactor variable names --- apps/client/src/stylesheets/theme-next-dark.css | 6 +++--- apps/client/src/stylesheets/theme-next-light.css | 6 +++--- apps/client/src/widgets/note_icon.css | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index ff1482399e..419dd59c5d 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -199,9 +199,9 @@ --badge-background-color: #ffffff1a; --badge-text-color: var(--muted-text-color); - --note-icon-button-background-color: #a6a6a6; - --note-icon-button-hover-background-color: #d0d0d0; - --note-icon-button-color: black; + --note-icon-background-color: #a6a6a6; + --note-icon-color: black; + --note-icon-hover-background-color: #d0d0d0; --promoted-attribute-card-background-color: #ffffff21; --promoted-attribute-card-shadow: none; diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 1d819576ad..a5cbfae80c 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -191,9 +191,9 @@ --badge-background-color: #00000011; --badge-text-color: var(--muted-text-color); - --note-icon-button-background-color: #4f4f4f; - --note-icon-button-hover-background-color: #737373; - --note-icon-button-color: white; + --note-icon-background-color: #4f4f4f; + --note-icon-color: white; + --note-icon-hover-background-color: #737373; --promoted-attribute-card-background-color: #00000014; --promoted-attribute-card-shadow: none; diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index 80e0732f95..5277e22f91 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -77,7 +77,7 @@ div.note-icon-widget { body.experimental-feature-new-layout { .note-icon-widget button.note-icon { - --input-focus-outline-color: var(--note-icon-button-hover-background-color); + --input-focus-outline-color: var(--note-icon-hover-background-color); position: relative; background: transparent !important; @@ -90,7 +90,7 @@ body.experimental-feature-new-layout { &::before { position: relative; z-index: 1; - color: var(--note-icon-custom-color, var(--note-icon-button-color)); + color: var(--note-icon-custom-color, var(--note-icon-color)); } /* The background circle */ @@ -99,15 +99,15 @@ body.experimental-feature-new-layout { position: absolute; inset: 0; border-radius: 50%; - background: var(--note-icon-custom-background-color, var(--note-icon-button-background-color)); + background: var(--note-icon-custom-background-color, var(--note-icon-background-color)); } &:focus-visible { - outline: 2px solid var(--note-icon-custom-color, var(--note-icon-button-color)); + outline: 2px solid var(--note-icon-custom-color, var(--note-icon-color)); } &:hover:not(.bx-empty:disabled)::after { - background: var(--note-icon-hover-custom-background-color, --note-icon-button-hover-background-color); + background: var(--note-icon-hover-custom-background-color, --note-icon-hover-background-color); transition: background 200ms ease-out; } } From 5123f7b67889bfed1dc386d720eed62a16b869d9 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 01:38:34 +0200 Subject: [PATCH 631/873] style/note icon: fix broken hover color for monochrome icons --- apps/client/src/widgets/note_icon.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_icon.css b/apps/client/src/widgets/note_icon.css index 5277e22f91..e715d56990 100644 --- a/apps/client/src/widgets/note_icon.css +++ b/apps/client/src/widgets/note_icon.css @@ -107,7 +107,7 @@ body.experimental-feature-new-layout { } &:hover:not(.bx-empty:disabled)::after { - background: var(--note-icon-hover-custom-background-color, --note-icon-hover-background-color); + background: var(--note-icon-hover-custom-background-color, var(--note-icon-hover-background-color)); transition: background 200ms ease-out; } } From 3f7514c9c76ca0c32b67c587ccd2a6ee22daecb7 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 01:39:36 +0200 Subject: [PATCH 632/873] style/note icon: tweak dark mode colors --- apps/client/src/stylesheets/theme-next-dark.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 419dd59c5d..cd39b569af 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -199,9 +199,9 @@ --badge-background-color: #ffffff1a; --badge-text-color: var(--muted-text-color); - --note-icon-background-color: #a6a6a6; - --note-icon-color: black; - --note-icon-hover-background-color: #d0d0d0; + --note-icon-background-color: #444444; + --note-icon-color: #d4d4d4; + --note-icon-hover-background-color: #555555; --promoted-attribute-card-background-color: #ffffff21; --promoted-attribute-card-shadow: none; From 46da1187490aaf5489839726867313a26efa4d67 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 01:43:11 +0200 Subject: [PATCH 633/873] style/note icon: cleanup --- apps/client/src/stylesheets/theme-next-light.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index a5cbfae80c..8c69593a5e 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -298,10 +298,6 @@ } .note-split.with-hue { - /* - --note-icon-custom-background-color: hsl(var(--custom-color-hue), 53.8%, 87.3%); - --note-icon-custom-color: hsl(var(--custom-color-hue), 36%, 23%); - */ --note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%); --note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%); --note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%); From 58e24c98ed90d5a7ff4db79cf792e6e94ed448b0 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 01:55:52 +0200 Subject: [PATCH 634/873] style/note icon: fix the parent note color being applied over note links and board items --- apps/client/src/stylesheets/theme-light.css | 8 ++++---- apps/client/src/stylesheets/theme-next-dark.css | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/client/src/stylesheets/theme-light.css b/apps/client/src/stylesheets/theme-light.css index 777ccbd619..cad8d9b4bd 100644 --- a/apps/client/src/stylesheets/theme-light.css +++ b/apps/client/src/stylesheets/theme-light.css @@ -89,10 +89,10 @@ html { --custom-color: var(--light-theme-custom-color); } -:root .reference-link, -:root .reference-link:hover, -.ck-content a.reference-link > span, -.board-note { +:root .reference-link.use-note-color, +:root .reference-link.use-note-color:hover, +.ck-content a.reference-link.use-note-color > span, +.board-note.use-note-color { color: var(--light-theme-custom-color, inherit); } diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index cd39b569af..f7ab0d91b3 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -297,10 +297,10 @@ --custom-bg-color: hsl(var(--custom-color-hue), 20%, 33%, 0.4); } -:root .reference-link, -:root .reference-link:hover, -.ck-content a.reference-link > span, -.board-note { +:root .reference-link.use-note-color, +:root .reference-link.use-note-color:hover, +.ck-content a.reference-link.use-note-color > span, +.board-note.use-note-color { color: var(--dark-theme-custom-color, inherit); } From 0de05ed16e9b2bf2c1944d34e2344f0249541f3c Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 02:01:03 +0200 Subject: [PATCH 635/873] style/note icon: apply note custom colors over the icons of the quick edit dialog as well --- apps/client/src/stylesheets/theme-next-dark.css | 3 ++- apps/client/src/stylesheets/theme-next-light.css | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index f7ab0d91b3..f4848739e3 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -326,7 +326,8 @@ body .todo-list input[type="checkbox"]:not(:checked):before { --custom-color: var(--dark-theme-custom-color); } -.note-split.with-hue { +.note-split.with-hue, +.quick-edit-dialog-wrapper.with-hue { --note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%); --note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%); --note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%); diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 8c69593a5e..0f71df9c32 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -297,7 +297,8 @@ --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%); } -.note-split.with-hue { +.note-split.with-hue, +.quick-edit-dialog-wrapper.with-hue { --note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%); --note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%); --note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%); From 32f7ae1edd65ec6e3ebdcd5e968586b6330f8a16 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 02:12:00 +0200 Subject: [PATCH 636/873] client: refactor --- apps/client/src/widgets/note_wrapper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_wrapper.ts b/apps/client/src/widgets/note_wrapper.ts index 9eaed96c77..2b8959d113 100644 --- a/apps/client/src/widgets/note_wrapper.ts +++ b/apps/client/src/widgets/note_wrapper.ts @@ -90,11 +90,11 @@ export default class NoteWrapperWidget extends FlexContainer { async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { // listening on changes of note.type and CSS class - + const LABELS_CAUSING_REFRESH = ["cssClass", "language", "viewType", "color"]; const noteId = this.noteContext?.noteId; if ( loadResults.isNoteReloaded(noteId) || - loadResults.getAttributeRows().find((attr) => attr.type === "label" && ["cssClass", "language", "viewType", "color"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.noteContext?.note)) + loadResults.getAttributeRows().find((attr) => attr.type === "label" && LABELS_CAUSING_REFRESH.includes(attr.name ?? "") && attributeService.isAffecting(attr, this.noteContext?.note)) ) { this.refresh(); } From c94b5bc6c91651a9eaf9f304157370e1e2551d94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 00:14:36 +0000 Subject: [PATCH 637/873] chore(deps): update dependency @smithy/middleware-retry to v4.4.17 --- packages/ckeditor5/package.json | 2 +- pnpm-lock.yaml | 550 ++++++++++++++++++-------------- 2 files changed, 315 insertions(+), 237 deletions(-) diff --git a/packages/ckeditor5/package.json b/packages/ckeditor5/package.json index a108692222..f9769c4545 100644 --- a/packages/ckeditor5/package.json +++ b/packages/ckeditor5/package.json @@ -16,7 +16,7 @@ "ckeditor5-premium-features": "47.3.0" }, "devDependencies": { - "@smithy/middleware-retry": "4.4.16", + "@smithy/middleware-retry": "4.4.17", "@types/jquery": "3.5.33" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c586ef7428..9fc1e4b4b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -867,8 +867,8 @@ importers: version: 47.3.0(bufferutil@4.0.9)(ckeditor5@47.3.0)(utf-8-validate@6.0.5) devDependencies: '@smithy/middleware-retry': - specifier: 4.4.16 - version: 4.4.16 + specifier: 4.4.17 + version: 4.4.17 '@types/jquery': specifier: 3.5.33 version: 3.5.33 @@ -4641,18 +4641,22 @@ packages: resolution: {integrity: sha512-P7JD4J+wxHMpGxqIg6SHno2tPkZbBUBLbPpR5/T1DEUvw/mEaINBMaPFZNM7lA+ToSCZ36j6nMHa+5kej+fhGg==} engines: {node: '>=18.0.0'} + '@smithy/abort-controller@4.2.7': + resolution: {integrity: sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==} + engines: {node: '>=18.0.0'} + '@smithy/config-resolver@4.1.4': resolution: {integrity: sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==} engines: {node: '>=18.0.0'} - '@smithy/core@3.18.7': - resolution: {integrity: sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw==} - engines: {node: '>=18.0.0'} - '@smithy/core@3.19.0': resolution: {integrity: sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w==} engines: {node: '>=18.0.0'} + '@smithy/core@3.20.0': + resolution: {integrity: sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.0.6': resolution: {integrity: sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==} engines: {node: '>=18.0.0'} @@ -4685,6 +4689,10 @@ packages: resolution: {integrity: sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ==} engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.3.8': + resolution: {integrity: sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==} + engines: {node: '>=18.0.0'} + '@smithy/hash-node@4.0.4': resolution: {integrity: sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==} engines: {node: '>=18.0.0'} @@ -4705,16 +4713,16 @@ packages: resolution: {integrity: sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.3.14': - resolution: {integrity: sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg==} - engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.4.0': resolution: {integrity: sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.4.16': - resolution: {integrity: sha512-XPpNhNRzm3vhYm7YCsyw3AtmWggJbg1wNGAoqb7NBYr5XA5isMRv14jgbYyUV6IvbTBFZQdf2QpeW43LrRdStQ==} + '@smithy/middleware-endpoint@4.4.1': + resolution: {integrity: sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.17': + resolution: {integrity: sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==} engines: {node: '>=18.0.0'} '@smithy/middleware-serde@4.2.6': @@ -4725,22 +4733,26 @@ packages: resolution: {integrity: sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-stack@4.2.5': - resolution: {integrity: sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==} + '@smithy/middleware-serde@4.2.8': + resolution: {integrity: sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==} engines: {node: '>=18.0.0'} '@smithy/middleware-stack@4.2.6': resolution: {integrity: sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA==} engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@4.3.5': - resolution: {integrity: sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==} + '@smithy/middleware-stack@4.2.7': + resolution: {integrity: sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==} engines: {node: '>=18.0.0'} '@smithy/node-config-provider@4.3.6': resolution: {integrity: sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA==} engines: {node: '>=18.0.0'} + '@smithy/node-config-provider@4.3.7': + resolution: {integrity: sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==} + engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.4.5': resolution: {integrity: sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==} engines: {node: '>=18.0.0'} @@ -4749,22 +4761,26 @@ packages: resolution: {integrity: sha512-Gsb9jf4ido5BhPfani4ggyrKDd3ZK+vTFWmUaZeFg5G3E5nhFmqiTzAIbHqmPs1sARuJawDiGMGR/nY+Gw6+aQ==} engines: {node: '>=18.0.0'} - '@smithy/property-provider@4.2.5': - resolution: {integrity: sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==} + '@smithy/node-http-handler@4.4.7': + resolution: {integrity: sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==} engines: {node: '>=18.0.0'} '@smithy/property-provider@4.2.6': resolution: {integrity: sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA==} engines: {node: '>=18.0.0'} - '@smithy/protocol-http@5.3.5': - resolution: {integrity: sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==} + '@smithy/property-provider@4.2.7': + resolution: {integrity: sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==} engines: {node: '>=18.0.0'} '@smithy/protocol-http@5.3.6': resolution: {integrity: sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ==} engines: {node: '>=18.0.0'} + '@smithy/protocol-http@5.3.7': + resolution: {integrity: sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-builder@4.2.5': resolution: {integrity: sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==} engines: {node: '>=18.0.0'} @@ -4773,6 +4789,10 @@ packages: resolution: {integrity: sha512-MeM9fTAiD3HvoInK/aA8mgJaKQDvm8N0dKy6EiFaCfgpovQr4CaOkJC28XqlSRABM+sHdSQXbC8NZ0DShBMHqg==} engines: {node: '>=18.0.0'} + '@smithy/querystring-builder@4.2.7': + resolution: {integrity: sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-parser@4.2.5': resolution: {integrity: sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==} engines: {node: '>=18.0.0'} @@ -4781,22 +4801,26 @@ packages: resolution: {integrity: sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA==} engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@4.2.5': - resolution: {integrity: sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==} + '@smithy/querystring-parser@4.2.7': + resolution: {integrity: sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==} engines: {node: '>=18.0.0'} '@smithy/service-error-classification@4.2.6': resolution: {integrity: sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg==} engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@4.4.0': - resolution: {integrity: sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==} + '@smithy/service-error-classification@4.2.7': + resolution: {integrity: sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==} engines: {node: '>=18.0.0'} '@smithy/shared-ini-file-loader@4.4.1': resolution: {integrity: sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA==} engines: {node: '>=18.0.0'} + '@smithy/shared-ini-file-loader@4.4.2': + resolution: {integrity: sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==} + engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.1.2': resolution: {integrity: sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==} engines: {node: '>=18.0.0'} @@ -4805,16 +4829,16 @@ packages: resolution: {integrity: sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.9.10': - resolution: {integrity: sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ==} + '@smithy/smithy-client@4.10.2': + resolution: {integrity: sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==} engines: {node: '>=18.0.0'} '@smithy/types@4.10.0': resolution: {integrity: sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ==} engines: {node: '>=18.0.0'} - '@smithy/types@4.9.0': - resolution: {integrity: sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==} + '@smithy/types@4.11.0': + resolution: {integrity: sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==} engines: {node: '>=18.0.0'} '@smithy/url-parser@4.2.5': @@ -4825,6 +4849,10 @@ packages: resolution: {integrity: sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg==} engines: {node: '>=18.0.0'} + '@smithy/url-parser@4.2.7': + resolution: {integrity: sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==} + engines: {node: '>=18.0.0'} + '@smithy/util-base64@4.3.0': resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} engines: {node: '>=18.0.0'} @@ -4865,30 +4893,30 @@ packages: resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} engines: {node: '>=18.0.0'} - '@smithy/util-middleware@4.2.5': - resolution: {integrity: sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==} - engines: {node: '>=18.0.0'} - '@smithy/util-middleware@4.2.6': resolution: {integrity: sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg==} engines: {node: '>=18.0.0'} - '@smithy/util-retry@4.2.5': - resolution: {integrity: sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==} + '@smithy/util-middleware@4.2.7': + resolution: {integrity: sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==} engines: {node: '>=18.0.0'} '@smithy/util-retry@4.2.6': resolution: {integrity: sha512-x7CeDQLPQ9cb6xN7fRJEjlP9NyGW/YeXWc4j/RUhg4I+H60F0PEeRc2c/z3rm9zmsdiMFzpV/rT+4UHW6KM1SA==} engines: {node: '>=18.0.0'} - '@smithy/util-stream@4.5.6': - resolution: {integrity: sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==} + '@smithy/util-retry@4.2.7': + resolution: {integrity: sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==} engines: {node: '>=18.0.0'} '@smithy/util-stream@4.5.7': resolution: {integrity: sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A==} engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.5.8': + resolution: {integrity: sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==} + engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@4.2.0': resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} engines: {node: '>=18.0.0'} @@ -14321,7 +14349,7 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.821.0 '@aws-sdk/util-user-agent-node': 3.823.0 '@smithy/config-resolver': 4.1.4 - '@smithy/core': 3.18.7 + '@smithy/core': 3.19.0 '@smithy/eventstream-serde-browser': 4.0.4 '@smithy/eventstream-serde-config-resolver': 4.1.2 '@smithy/eventstream-serde-node': 4.0.4 @@ -14329,15 +14357,15 @@ snapshots: '@smithy/hash-node': 4.0.4 '@smithy/invalid-dependency': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.3.14 - '@smithy/middleware-retry': 4.4.16 + '@smithy/middleware-endpoint': 4.4.0 + '@smithy/middleware-retry': 4.4.17 '@smithy/middleware-serde': 4.2.6 - '@smithy/middleware-stack': 4.2.5 - '@smithy/node-config-provider': 4.3.5 + '@smithy/middleware-stack': 4.2.6 + '@smithy/node-config-provider': 4.3.6 '@smithy/node-http-handler': 4.4.5 - '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.10 - '@smithy/types': 4.9.0 + '@smithy/protocol-http': 5.3.6 + '@smithy/smithy-client': 4.10.1 + '@smithy/types': 4.10.0 '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 @@ -14345,9 +14373,9 @@ snapshots: '@smithy/util-defaults-mode-browser': 4.0.22 '@smithy/util-defaults-mode-node': 4.0.22 '@smithy/util-endpoints': 3.0.6 - '@smithy/util-middleware': 4.2.5 - '@smithy/util-retry': 4.2.5 - '@smithy/util-stream': 4.5.6 + '@smithy/util-middleware': 4.2.6 + '@smithy/util-retry': 4.2.6 + '@smithy/util-stream': 4.5.7 '@smithy/util-utf8': 4.2.0 '@types/uuid': 9.0.8 tslib: 2.8.1 @@ -14370,29 +14398,29 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.821.0 '@aws-sdk/util-user-agent-node': 3.823.0 '@smithy/config-resolver': 4.1.4 - '@smithy/core': 3.18.7 - '@smithy/fetch-http-handler': 5.3.6 + '@smithy/core': 3.19.0 + '@smithy/fetch-http-handler': 5.3.7 '@smithy/hash-node': 4.0.4 '@smithy/invalid-dependency': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.3.14 - '@smithy/middleware-retry': 4.4.16 - '@smithy/middleware-serde': 4.2.6 - '@smithy/middleware-stack': 4.2.5 - '@smithy/node-config-provider': 4.3.5 - '@smithy/node-http-handler': 4.4.5 - '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.10 - '@smithy/types': 4.9.0 - '@smithy/url-parser': 4.2.5 + '@smithy/middleware-endpoint': 4.4.0 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.7 + '@smithy/middleware-stack': 4.2.6 + '@smithy/node-config-provider': 4.3.6 + '@smithy/node-http-handler': 4.4.6 + '@smithy/protocol-http': 5.3.6 + '@smithy/smithy-client': 4.10.1 + '@smithy/types': 4.10.0 + '@smithy/url-parser': 4.2.6 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.0.0 '@smithy/util-defaults-mode-browser': 4.0.22 '@smithy/util-defaults-mode-node': 4.0.22 '@smithy/util-endpoints': 3.0.6 - '@smithy/util-middleware': 4.2.5 - '@smithy/util-retry': 4.2.5 + '@smithy/util-middleware': 4.2.6 + '@smithy/util-retry': 4.2.6 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: @@ -14402,16 +14430,16 @@ snapshots: dependencies: '@aws-sdk/types': 3.821.0 '@aws-sdk/xml-builder': 3.821.0 - '@smithy/core': 3.18.7 - '@smithy/node-config-provider': 4.3.5 - '@smithy/property-provider': 4.2.5 - '@smithy/protocol-http': 5.3.5 + '@smithy/core': 3.19.0 + '@smithy/node-config-provider': 4.3.6 + '@smithy/property-provider': 4.2.6 + '@smithy/protocol-http': 5.3.6 '@smithy/signature-v4': 5.1.2 - '@smithy/smithy-client': 4.9.10 - '@smithy/types': 4.9.0 + '@smithy/smithy-client': 4.10.1 + '@smithy/types': 4.10.0 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-middleware': 4.2.5 + '@smithy/util-middleware': 4.2.6 '@smithy/util-utf8': 4.2.0 fast-xml-parser: 4.4.1 tslib: 2.8.1 @@ -14420,21 +14448,21 @@ snapshots: dependencies: '@aws-sdk/core': 3.823.0 '@aws-sdk/types': 3.821.0 - '@smithy/property-provider': 4.2.5 - '@smithy/types': 4.9.0 + '@smithy/property-provider': 4.2.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/credential-provider-http@3.823.0': dependencies: '@aws-sdk/core': 3.823.0 '@aws-sdk/types': 3.821.0 - '@smithy/fetch-http-handler': 5.3.6 - '@smithy/node-http-handler': 4.4.5 - '@smithy/property-provider': 4.2.5 - '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.10 - '@smithy/types': 4.9.0 - '@smithy/util-stream': 4.5.6 + '@smithy/fetch-http-handler': 5.3.7 + '@smithy/node-http-handler': 4.4.6 + '@smithy/property-provider': 4.2.6 + '@smithy/protocol-http': 5.3.6 + '@smithy/smithy-client': 4.10.1 + '@smithy/types': 4.10.0 + '@smithy/util-stream': 4.5.7 tslib: 2.8.1 '@aws-sdk/credential-provider-ini@3.823.0': @@ -14448,9 +14476,9 @@ snapshots: '@aws-sdk/nested-clients': 3.823.0 '@aws-sdk/types': 3.821.0 '@smithy/credential-provider-imds': 4.0.6 - '@smithy/property-provider': 4.2.5 - '@smithy/shared-ini-file-loader': 4.4.0 - '@smithy/types': 4.9.0 + '@smithy/property-provider': 4.2.6 + '@smithy/shared-ini-file-loader': 4.4.1 + '@smithy/types': 4.10.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -14465,9 +14493,9 @@ snapshots: '@aws-sdk/credential-provider-web-identity': 3.823.0 '@aws-sdk/types': 3.821.0 '@smithy/credential-provider-imds': 4.0.6 - '@smithy/property-provider': 4.2.5 - '@smithy/shared-ini-file-loader': 4.4.0 - '@smithy/types': 4.9.0 + '@smithy/property-provider': 4.2.6 + '@smithy/shared-ini-file-loader': 4.4.1 + '@smithy/types': 4.10.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -14476,9 +14504,9 @@ snapshots: dependencies: '@aws-sdk/core': 3.823.0 '@aws-sdk/types': 3.821.0 - '@smithy/property-provider': 4.2.5 - '@smithy/shared-ini-file-loader': 4.4.0 - '@smithy/types': 4.9.0 + '@smithy/property-provider': 4.2.6 + '@smithy/shared-ini-file-loader': 4.4.1 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/credential-provider-sso@3.823.0': @@ -14487,9 +14515,9 @@ snapshots: '@aws-sdk/core': 3.823.0 '@aws-sdk/token-providers': 3.823.0 '@aws-sdk/types': 3.821.0 - '@smithy/property-provider': 4.2.5 - '@smithy/shared-ini-file-loader': 4.4.0 - '@smithy/types': 4.9.0 + '@smithy/property-provider': 4.2.6 + '@smithy/shared-ini-file-loader': 4.4.1 + '@smithy/types': 4.10.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -14499,8 +14527,8 @@ snapshots: '@aws-sdk/core': 3.823.0 '@aws-sdk/nested-clients': 3.823.0 '@aws-sdk/types': 3.821.0 - '@smithy/property-provider': 4.2.5 - '@smithy/types': 4.9.0 + '@smithy/property-provider': 4.2.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -14509,34 +14537,34 @@ snapshots: dependencies: '@aws-sdk/types': 3.821.0 '@smithy/eventstream-codec': 4.0.4 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/middleware-eventstream@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 + '@smithy/protocol-http': 5.3.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/middleware-host-header@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 + '@smithy/protocol-http': 5.3.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/middleware-logger@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/middleware-recursion-detection@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 + '@smithy/protocol-http': 5.3.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/middleware-user-agent@3.823.0': @@ -14544,9 +14572,9 @@ snapshots: '@aws-sdk/core': 3.823.0 '@aws-sdk/types': 3.821.0 '@aws-sdk/util-endpoints': 3.821.0 - '@smithy/core': 3.18.7 - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 + '@smithy/core': 3.19.0 + '@smithy/protocol-http': 5.3.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/nested-clients@3.823.0': @@ -14564,29 +14592,29 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.821.0 '@aws-sdk/util-user-agent-node': 3.823.0 '@smithy/config-resolver': 4.1.4 - '@smithy/core': 3.18.7 - '@smithy/fetch-http-handler': 5.3.6 + '@smithy/core': 3.19.0 + '@smithy/fetch-http-handler': 5.3.7 '@smithy/hash-node': 4.0.4 '@smithy/invalid-dependency': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.3.14 - '@smithy/middleware-retry': 4.4.16 - '@smithy/middleware-serde': 4.2.6 - '@smithy/middleware-stack': 4.2.5 - '@smithy/node-config-provider': 4.3.5 - '@smithy/node-http-handler': 4.4.5 - '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.10 - '@smithy/types': 4.9.0 - '@smithy/url-parser': 4.2.5 + '@smithy/middleware-endpoint': 4.4.0 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.7 + '@smithy/middleware-stack': 4.2.6 + '@smithy/node-config-provider': 4.3.6 + '@smithy/node-http-handler': 4.4.6 + '@smithy/protocol-http': 5.3.6 + '@smithy/smithy-client': 4.10.1 + '@smithy/types': 4.10.0 + '@smithy/url-parser': 4.2.6 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.0.0 '@smithy/util-defaults-mode-browser': 4.0.22 '@smithy/util-defaults-mode-node': 4.0.22 '@smithy/util-endpoints': 3.0.6 - '@smithy/util-middleware': 4.2.5 - '@smithy/util-retry': 4.2.5 + '@smithy/util-middleware': 4.2.6 + '@smithy/util-retry': 4.2.6 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: @@ -14595,10 +14623,10 @@ snapshots: '@aws-sdk/region-config-resolver@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 - '@smithy/node-config-provider': 4.3.5 - '@smithy/types': 4.9.0 + '@smithy/node-config-provider': 4.3.6 + '@smithy/types': 4.10.0 '@smithy/util-config-provider': 4.0.0 - '@smithy/util-middleware': 4.2.5 + '@smithy/util-middleware': 4.2.6 tslib: 2.8.1 '@aws-sdk/token-providers@3.823.0': @@ -14606,22 +14634,22 @@ snapshots: '@aws-sdk/core': 3.823.0 '@aws-sdk/nested-clients': 3.823.0 '@aws-sdk/types': 3.821.0 - '@smithy/property-provider': 4.2.5 - '@smithy/shared-ini-file-loader': 4.4.0 - '@smithy/types': 4.9.0 + '@smithy/property-provider': 4.2.6 + '@smithy/shared-ini-file-loader': 4.4.1 + '@smithy/types': 4.10.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt '@aws-sdk/types@3.821.0': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/util-endpoints@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 '@smithy/util-endpoints': 3.0.6 tslib: 2.8.1 @@ -14632,7 +14660,7 @@ snapshots: '@aws-sdk/util-user-agent-browser@3.821.0': dependencies: '@aws-sdk/types': 3.821.0 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 bowser: 2.11.0 tslib: 2.8.1 @@ -14640,13 +14668,13 @@ snapshots: dependencies: '@aws-sdk/middleware-user-agent': 3.823.0 '@aws-sdk/types': 3.821.0 - '@smithy/node-config-provider': 4.3.5 - '@smithy/types': 4.9.0 + '@smithy/node-config-provider': 4.3.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@aws-sdk/xml-builder@3.821.0': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@babel/code-frame@7.27.1': @@ -15040,6 +15068,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -15232,6 +15262,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-classic@47.3.0': dependencies: @@ -15770,6 +15802,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-restricted-editing@47.3.0': dependencies: @@ -18986,7 +19020,7 @@ snapshots: '@smithy/abort-controller@4.2.5': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/abort-controller@4.2.6': @@ -18994,25 +19028,17 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 - '@smithy/config-resolver@4.1.4': + '@smithy/abort-controller@4.2.7': dependencies: - '@smithy/node-config-provider': 4.3.5 - '@smithy/types': 4.9.0 - '@smithy/util-config-provider': 4.0.0 - '@smithy/util-middleware': 4.2.5 + '@smithy/types': 4.11.0 tslib: 2.8.1 - '@smithy/core@3.18.7': + '@smithy/config-resolver@4.1.4': dependencies: - '@smithy/middleware-serde': 4.2.6 - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-middleware': 4.2.5 - '@smithy/util-stream': 4.5.6 - '@smithy/util-utf8': 4.2.0 - '@smithy/uuid': 1.1.0 + '@smithy/node-config-provider': 4.3.6 + '@smithy/types': 4.10.0 + '@smithy/util-config-provider': 4.0.0 + '@smithy/util-middleware': 4.2.6 tslib: 2.8.1 '@smithy/core@3.19.0': @@ -19028,49 +19054,62 @@ snapshots: '@smithy/uuid': 1.1.0 tslib: 2.8.1 + '@smithy/core@3.20.0': + dependencies: + '@smithy/middleware-serde': 4.2.8 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-stream': 4.5.8 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + '@smithy/credential-provider-imds@4.0.6': dependencies: - '@smithy/node-config-provider': 4.3.5 - '@smithy/property-provider': 4.2.5 - '@smithy/types': 4.9.0 - '@smithy/url-parser': 4.2.5 + '@smithy/node-config-provider': 4.3.6 + '@smithy/property-provider': 4.2.6 + '@smithy/types': 4.10.0 + '@smithy/url-parser': 4.2.6 tslib: 2.8.1 '@smithy/eventstream-codec@4.0.4': dependencies: '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 '@smithy/util-hex-encoding': 4.2.0 tslib: 2.8.1 '@smithy/eventstream-serde-browser@4.0.4': dependencies: '@smithy/eventstream-serde-universal': 4.0.4 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/eventstream-serde-config-resolver@4.1.2': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/eventstream-serde-node@4.0.4': dependencies: '@smithy/eventstream-serde-universal': 4.0.4 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/eventstream-serde-universal@4.0.4': dependencies: '@smithy/eventstream-codec': 4.0.4 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/fetch-http-handler@5.3.6': dependencies: - '@smithy/protocol-http': 5.3.5 + '@smithy/protocol-http': 5.3.6 '@smithy/querystring-builder': 4.2.5 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 '@smithy/util-base64': 4.3.0 tslib: 2.8.1 @@ -19082,16 +19121,24 @@ snapshots: '@smithy/util-base64': 4.3.0 tslib: 2.8.1 + '@smithy/fetch-http-handler@5.3.8': + dependencies: + '@smithy/protocol-http': 5.3.7 + '@smithy/querystring-builder': 4.2.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + tslib: 2.8.1 + '@smithy/hash-node@4.0.4': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 '@smithy/util-buffer-from': 4.2.0 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 '@smithy/invalid-dependency@4.0.4': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/is-array-buffer@2.2.0': @@ -19104,19 +19151,8 @@ snapshots: '@smithy/middleware-content-length@4.0.4': dependencies: - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 - tslib: 2.8.1 - - '@smithy/middleware-endpoint@4.3.14': - dependencies: - '@smithy/core': 3.18.7 - '@smithy/middleware-serde': 4.2.6 - '@smithy/node-config-provider': 4.3.5 - '@smithy/shared-ini-file-loader': 4.4.0 - '@smithy/types': 4.9.0 - '@smithy/url-parser': 4.2.5 - '@smithy/util-middleware': 4.2.5 + '@smithy/protocol-http': 5.3.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/middleware-endpoint@4.4.0': @@ -19130,22 +19166,33 @@ snapshots: '@smithy/util-middleware': 4.2.6 tslib: 2.8.1 - '@smithy/middleware-retry@4.4.16': + '@smithy/middleware-endpoint@4.4.1': dependencies: - '@smithy/node-config-provider': 4.3.6 - '@smithy/protocol-http': 5.3.6 - '@smithy/service-error-classification': 4.2.6 - '@smithy/smithy-client': 4.10.1 - '@smithy/types': 4.10.0 - '@smithy/util-middleware': 4.2.6 - '@smithy/util-retry': 4.2.6 + '@smithy/core': 3.20.0 + '@smithy/middleware-serde': 4.2.8 + '@smithy/node-config-provider': 4.3.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-middleware': 4.2.7 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.17': + dependencies: + '@smithy/node-config-provider': 4.3.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/service-error-classification': 4.2.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 '@smithy/uuid': 1.1.0 tslib: 2.8.1 '@smithy/middleware-serde@4.2.6': dependencies: - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 + '@smithy/protocol-http': 5.3.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/middleware-serde@4.2.7': @@ -19154,9 +19201,10 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 - '@smithy/middleware-stack@4.2.5': + '@smithy/middleware-serde@4.2.8': dependencies: - '@smithy/types': 4.9.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 '@smithy/middleware-stack@4.2.6': @@ -19164,11 +19212,9 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 - '@smithy/node-config-provider@4.3.5': + '@smithy/middleware-stack@4.2.7': dependencies: - '@smithy/property-provider': 4.2.5 - '@smithy/shared-ini-file-loader': 4.4.0 - '@smithy/types': 4.9.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 '@smithy/node-config-provider@4.3.6': @@ -19178,12 +19224,19 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 + '@smithy/node-config-provider@4.3.7': + dependencies: + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + '@smithy/node-http-handler@4.4.5': dependencies: '@smithy/abort-controller': 4.2.5 - '@smithy/protocol-http': 5.3.5 + '@smithy/protocol-http': 5.3.6 '@smithy/querystring-builder': 4.2.5 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/node-http-handler@4.4.6': @@ -19194,9 +19247,12 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 - '@smithy/property-provider@4.2.5': + '@smithy/node-http-handler@4.4.7': dependencies: - '@smithy/types': 4.9.0 + '@smithy/abort-controller': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/querystring-builder': 4.2.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 '@smithy/property-provider@4.2.6': @@ -19204,9 +19260,9 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 - '@smithy/protocol-http@5.3.5': + '@smithy/property-provider@4.2.7': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 '@smithy/protocol-http@5.3.6': @@ -19214,9 +19270,14 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 + '@smithy/protocol-http@5.3.7': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + '@smithy/querystring-builder@4.2.5': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 '@smithy/util-uri-escape': 4.2.0 tslib: 2.8.1 @@ -19226,9 +19287,15 @@ snapshots: '@smithy/util-uri-escape': 4.2.0 tslib: 2.8.1 + '@smithy/querystring-builder@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + '@smithy/util-uri-escape': 4.2.0 + tslib: 2.8.1 + '@smithy/querystring-parser@4.2.5': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/querystring-parser@4.2.6': @@ -19236,31 +19303,36 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 - '@smithy/service-error-classification@4.2.5': + '@smithy/querystring-parser@4.2.7': dependencies: - '@smithy/types': 4.9.0 + '@smithy/types': 4.11.0 + tslib: 2.8.1 '@smithy/service-error-classification@4.2.6': dependencies: '@smithy/types': 4.10.0 - '@smithy/shared-ini-file-loader@4.4.0': + '@smithy/service-error-classification@4.2.7': dependencies: - '@smithy/types': 4.9.0 - tslib: 2.8.1 + '@smithy/types': 4.11.0 '@smithy/shared-ini-file-loader@4.4.1': dependencies: '@smithy/types': 4.10.0 tslib: 2.8.1 + '@smithy/shared-ini-file-loader@4.4.2': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + '@smithy/signature-v4@5.1.2': dependencies: '@smithy/is-array-buffer': 4.2.0 - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 + '@smithy/protocol-http': 5.3.6 + '@smithy/types': 4.10.0 '@smithy/util-hex-encoding': 4.2.0 - '@smithy/util-middleware': 4.2.5 + '@smithy/util-middleware': 4.2.6 '@smithy/util-uri-escape': 4.2.0 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 @@ -19275,28 +19347,28 @@ snapshots: '@smithy/util-stream': 4.5.7 tslib: 2.8.1 - '@smithy/smithy-client@4.9.10': + '@smithy/smithy-client@4.10.2': dependencies: - '@smithy/core': 3.18.7 - '@smithy/middleware-endpoint': 4.3.14 - '@smithy/middleware-stack': 4.2.5 - '@smithy/protocol-http': 5.3.5 - '@smithy/types': 4.9.0 - '@smithy/util-stream': 4.5.6 + '@smithy/core': 3.20.0 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-stack': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-stream': 4.5.8 tslib: 2.8.1 '@smithy/types@4.10.0': dependencies: tslib: 2.8.1 - '@smithy/types@4.9.0': + '@smithy/types@4.11.0': dependencies: tslib: 2.8.1 '@smithy/url-parser@4.2.5': dependencies: '@smithy/querystring-parser': 4.2.5 - '@smithy/types': 4.9.0 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/url-parser@4.2.6': @@ -19305,6 +19377,12 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 + '@smithy/url-parser@4.2.7': + dependencies: + '@smithy/querystring-parser': 4.2.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + '@smithy/util-base64@4.3.0': dependencies: '@smithy/util-buffer-from': 4.2.0 @@ -19335,9 +19413,9 @@ snapshots: '@smithy/util-defaults-mode-browser@4.0.22': dependencies: - '@smithy/property-provider': 4.2.5 - '@smithy/smithy-client': 4.9.10 - '@smithy/types': 4.9.0 + '@smithy/property-provider': 4.2.6 + '@smithy/smithy-client': 4.10.1 + '@smithy/types': 4.10.0 bowser: 2.11.0 tslib: 2.8.1 @@ -19345,36 +19423,30 @@ snapshots: dependencies: '@smithy/config-resolver': 4.1.4 '@smithy/credential-provider-imds': 4.0.6 - '@smithy/node-config-provider': 4.3.5 - '@smithy/property-provider': 4.2.5 - '@smithy/smithy-client': 4.9.10 - '@smithy/types': 4.9.0 + '@smithy/node-config-provider': 4.3.6 + '@smithy/property-provider': 4.2.6 + '@smithy/smithy-client': 4.10.1 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/util-endpoints@3.0.6': dependencies: - '@smithy/node-config-provider': 4.3.5 - '@smithy/types': 4.9.0 + '@smithy/node-config-provider': 4.3.6 + '@smithy/types': 4.10.0 tslib: 2.8.1 '@smithy/util-hex-encoding@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-middleware@4.2.5': - dependencies: - '@smithy/types': 4.9.0 - tslib: 2.8.1 - '@smithy/util-middleware@4.2.6': dependencies: '@smithy/types': 4.10.0 tslib: 2.8.1 - '@smithy/util-retry@4.2.5': + '@smithy/util-middleware@4.2.7': dependencies: - '@smithy/service-error-classification': 4.2.5 - '@smithy/types': 4.9.0 + '@smithy/types': 4.11.0 tslib: 2.8.1 '@smithy/util-retry@4.2.6': @@ -19383,15 +19455,10 @@ snapshots: '@smithy/types': 4.10.0 tslib: 2.8.1 - '@smithy/util-stream@4.5.6': + '@smithy/util-retry@4.2.7': dependencies: - '@smithy/fetch-http-handler': 5.3.6 - '@smithy/node-http-handler': 4.4.5 - '@smithy/types': 4.9.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-buffer-from': 4.2.0 - '@smithy/util-hex-encoding': 4.2.0 - '@smithy/util-utf8': 4.2.0 + '@smithy/service-error-classification': 4.2.7 + '@smithy/types': 4.11.0 tslib: 2.8.1 '@smithy/util-stream@4.5.7': @@ -19405,6 +19472,17 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@smithy/util-stream@4.5.8': + dependencies: + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/node-http-handler': 4.4.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + '@smithy/util-uri-escape@4.2.0': dependencies: tslib: 2.8.1 From 03cea8b7021d224dfc989d36a3d326f68748e4bc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 00:15:28 +0000 Subject: [PATCH 638/873] chore(deps): update dependency esbuild to v0.27.2 --- package.json | 2 +- packages/share-theme/package.json | 2 +- pnpm-lock.yaml | 432 ++++++++++++++++++++++++------ 3 files changed, 352 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index 707b1aa499..1a8cf503e9 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "chalk": "5.6.2", "cross-env": "10.1.0", "dpdm": "3.14.0", - "esbuild": "0.27.1", + "esbuild": "0.27.2", "eslint": "9.39.2", "eslint-config-preact": "2.0.0", "eslint-config-prettier": "10.1.8", diff --git a/packages/share-theme/package.json b/packages/share-theme/package.json index 47731a3abe..7c723e6fd8 100644 --- a/packages/share-theme/package.json +++ b/packages/share-theme/package.json @@ -35,7 +35,7 @@ "@typescript-eslint/eslint-plugin": "8.50.0", "@typescript-eslint/parser": "8.50.0", "dotenv": "17.2.3", - "esbuild": "0.27.1", + "esbuild": "0.27.2", "eslint": "9.39.2", "highlight.js": "11.11.1", "typescript": "5.9.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c586ef7428..4046b88240 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,8 +71,8 @@ importers: specifier: 3.14.0 version: 3.14.0 esbuild: - specifier: 0.27.1 - version: 0.27.1 + specifier: 0.27.2 + version: 0.27.2 eslint: specifier: 9.39.2 version: 9.39.2(jiti@2.6.1) @@ -334,7 +334,7 @@ importers: version: 6.3.0 copy-webpack-plugin: specifier: 13.0.1 - version: 13.0.1(webpack@5.101.3(esbuild@0.27.1)) + version: 13.0.1(webpack@5.101.3(esbuild@0.27.2)) happy-dom: specifier: 20.0.11 version: 20.0.11 @@ -386,7 +386,7 @@ importers: devDependencies: '@electron-forge/cli': specifier: 7.10.2 - version: 7.10.2(encoding@0.1.13)(esbuild@0.27.1) + version: 7.10.2(encoding@0.1.13)(esbuild@0.27.2) '@electron-forge/maker-deb': specifier: 7.10.2 version: 7.10.2 @@ -419,7 +419,7 @@ importers: version: 1.0.2 copy-webpack-plugin: specifier: 13.0.1 - version: 13.0.1(webpack@5.101.3(esbuild@0.27.1)) + version: 13.0.1(webpack@5.101.3(esbuild@0.27.2)) electron: specifier: 39.2.7 version: 39.2.7 @@ -475,7 +475,7 @@ importers: version: 11.0.4 copy-webpack-plugin: specifier: 13.0.1 - version: 13.0.1(webpack@5.101.3(esbuild@0.27.1)) + version: 13.0.1(webpack@5.101.3(esbuild@0.27.2)) electron: specifier: 39.2.7 version: 39.2.7 @@ -883,7 +883,7 @@ importers: version: 5.0.0 '@ckeditor/ckeditor5-package-tools': specifier: 5.0.1 - version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.1)(utf-8-validate@6.0.5) + version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.2)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.50.0 version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -943,7 +943,7 @@ importers: version: 5.0.0 '@ckeditor/ckeditor5-package-tools': specifier: 5.0.1 - version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.1)(utf-8-validate@6.0.5) + version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.2)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.50.0 version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -1003,7 +1003,7 @@ importers: version: 5.0.0 '@ckeditor/ckeditor5-package-tools': specifier: 5.0.1 - version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.1)(utf-8-validate@6.0.5) + version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.2)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.50.0 version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -1067,7 +1067,7 @@ importers: version: 5.0.0 '@ckeditor/ckeditor5-package-tools': specifier: 5.0.1 - version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.1)(utf-8-validate@6.0.5) + version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.2)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.50.0 version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -1134,7 +1134,7 @@ importers: version: 5.0.0 '@ckeditor/ckeditor5-package-tools': specifier: 5.0.1 - version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.1)(utf-8-validate@6.0.5) + version: 5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.2)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.50.0 version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -1389,8 +1389,8 @@ importers: specifier: 17.2.3 version: 17.2.3 esbuild: - specifier: 0.27.1 - version: 0.27.1 + specifier: 0.27.2 + version: 0.27.2 eslint: specifier: 9.39.2 version: 9.39.2(jiti@2.6.1) @@ -2361,6 +2361,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} @@ -2379,6 +2385,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} @@ -2397,6 +2409,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} @@ -2415,6 +2433,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} @@ -2433,6 +2457,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} @@ -2451,6 +2481,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} @@ -2469,6 +2505,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} @@ -2487,6 +2529,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} @@ -2505,6 +2553,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} @@ -2523,6 +2577,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} @@ -2541,6 +2601,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} @@ -2559,6 +2625,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} @@ -2577,6 +2649,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} @@ -2595,6 +2673,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} @@ -2613,6 +2697,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} @@ -2631,6 +2721,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -2649,6 +2745,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} @@ -2667,6 +2769,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -2685,6 +2793,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} @@ -2703,6 +2817,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -2721,6 +2841,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} @@ -2739,6 +2865,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} @@ -2757,6 +2889,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} @@ -2775,6 +2913,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} @@ -2793,6 +2937,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -2811,6 +2961,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7867,6 +8023,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -15139,11 +15300,11 @@ snapshots: - tslib - typescript - '@ckeditor/ckeditor5-dev-translations@54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1))': + '@ckeditor/ckeditor5-dev-translations@54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2))': dependencies: '@babel/parser': 7.28.4 '@babel/traverse': 7.28.4 - '@ckeditor/ckeditor5-dev-utils': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + '@ckeditor/ckeditor5-dev-utils': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) chalk: 5.6.2 fs-extra: 11.3.2 glob: 11.0.3 @@ -15162,34 +15323,34 @@ snapshots: - uglify-js - webpack - '@ckeditor/ckeditor5-dev-utils@54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1))': + '@ckeditor/ckeditor5-dev-utils@54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2))': dependencies: - '@ckeditor/ckeditor5-dev-translations': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + '@ckeditor/ckeditor5-dev-translations': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) '@types/postcss-import': 14.0.3 '@types/through2': 2.0.41 - babel-loader: 10.0.0(@babel/core@7.28.0)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + babel-loader: 10.0.0(@babel/core@7.28.0)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) chalk: 5.6.2 cli-cursor: 5.0.0 cli-spinners: 3.2.0 - css-loader: 7.1.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + css-loader: 7.1.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) cssnano: 7.1.1(postcss@8.5.6) - esbuild-loader: 4.3.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + esbuild-loader: 4.3.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) fs-extra: 11.3.2 glob: 11.0.3 is-interactive: 2.0.0 - mini-css-extract-plugin: 2.9.4(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + mini-css-extract-plugin: 2.9.4(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) mocha: 11.7.2 pacote: 21.0.1 postcss: 8.5.6 postcss-import: 16.1.1(postcss@8.5.6) - postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) postcss-mixins: 11.0.3(postcss@8.5.6) postcss-nesting: 13.0.2(postcss@8.5.6) - raw-loader: 4.0.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + raw-loader: 4.0.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) shelljs: 0.10.0 simple-git: 3.28.0 - style-loader: 4.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) - terser-webpack-plugin: 5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + style-loader: 4.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) through2: 4.0.2 upath: 2.0.1 transitivePeerDependencies: @@ -15659,30 +15820,30 @@ snapshots: es-toolkit: 1.39.5 protobufjs: 7.5.0 - '@ckeditor/ckeditor5-package-tools@5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.1)(utf-8-validate@6.0.5)': + '@ckeditor/ckeditor5-package-tools@5.0.1(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(bufferutil@4.0.9)(esbuild@0.27.2)(utf-8-validate@6.0.5)': dependencies: - '@ckeditor/ckeditor5-dev-translations': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) - '@ckeditor/ckeditor5-dev-utils': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + '@ckeditor/ckeditor5-dev-translations': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) + '@ckeditor/ckeditor5-dev-utils': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) buffer: 6.0.3 chalk: 5.6.2 - css-loader: 5.2.7(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + css-loader: 5.2.7(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) fs-extra: 11.3.2 glob: 11.0.3 minimist: 1.2.8 postcss: 8.5.6 - postcss-loader: 4.3.0(postcss@8.5.6)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + postcss-loader: 4.3.0(postcss@8.5.6)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) process: 0.11.10 - raw-loader: 4.0.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) - style-loader: 2.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + raw-loader: 4.0.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) + style-loader: 2.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) stylelint: 16.26.1(typescript@5.0.4) stylelint-config-ckeditor5: 2.0.1(stylelint@16.26.1(typescript@5.9.3)) - terser-webpack-plugin: 5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) - ts-loader: 9.5.4(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) + ts-loader: 9.5.4(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) ts-node: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(typescript@5.0.4) typescript: 5.0.4 upath: 2.0.1 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) - webpack-dev-server: 5.2.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) + webpack-dev-server: 5.2.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) transitivePeerDependencies: - '@babel/core' - '@rspack/core' @@ -16225,15 +16386,15 @@ snapshots: dependencies: '@digitak/grubber': 3.1.4 chokidar: 3.6.0 - esbuild: 0.27.1 + esbuild: 0.27.2 '@digitak/grubber@3.1.4': {} '@dual-bundle/import-meta-resolve@4.2.1': {} - '@electron-forge/cli@7.10.2(encoding@0.1.13)(esbuild@0.27.1)': + '@electron-forge/cli@7.10.2(encoding@0.1.13)(esbuild@0.27.2)': dependencies: - '@electron-forge/core': 7.10.2(encoding@0.1.13)(esbuild@0.27.1) + '@electron-forge/core': 7.10.2(encoding@0.1.13)(esbuild@0.27.2) '@electron-forge/core-utils': 7.10.2 '@electron-forge/shared-types': 7.10.2 '@electron/get': 3.1.0 @@ -16271,7 +16432,7 @@ snapshots: - bluebird - supports-color - '@electron-forge/core@7.10.2(encoding@0.1.13)(esbuild@0.27.1)': + '@electron-forge/core@7.10.2(encoding@0.1.13)(esbuild@0.27.2)': dependencies: '@electron-forge/core-utils': 7.10.2 '@electron-forge/maker-base': 7.10.2 @@ -16282,7 +16443,7 @@ snapshots: '@electron-forge/template-vite': 7.10.2 '@electron-forge/template-vite-typescript': 7.10.2 '@electron-forge/template-webpack': 7.10.2 - '@electron-forge/template-webpack-typescript': 7.10.2(esbuild@0.27.1) + '@electron-forge/template-webpack-typescript': 7.10.2(esbuild@0.27.2) '@electron-forge/tracer': 7.10.2 '@electron/get': 3.1.0 '@electron/packager': 18.3.6 @@ -16452,13 +16613,13 @@ snapshots: - bluebird - supports-color - '@electron-forge/template-webpack-typescript@7.10.2(esbuild@0.27.1)': + '@electron-forge/template-webpack-typescript@7.10.2(esbuild@0.27.2)': dependencies: '@electron-forge/shared-types': 7.10.2 '@electron-forge/template-base': 7.10.2 fs-extra: 10.1.0 typescript: 5.4.5 - webpack: 5.101.3(esbuild@0.27.1) + webpack: 5.101.3(esbuild@0.27.2) transitivePeerDependencies: - '@swc/core' - bluebird @@ -16680,6 +16841,9 @@ snapshots: '@esbuild/aix-ppc64@0.27.1': optional: true + '@esbuild/aix-ppc64@0.27.2': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true @@ -16689,6 +16853,9 @@ snapshots: '@esbuild/android-arm64@0.27.1': optional: true + '@esbuild/android-arm64@0.27.2': + optional: true + '@esbuild/android-arm@0.25.12': optional: true @@ -16698,6 +16865,9 @@ snapshots: '@esbuild/android-arm@0.27.1': optional: true + '@esbuild/android-arm@0.27.2': + optional: true + '@esbuild/android-x64@0.25.12': optional: true @@ -16707,6 +16877,9 @@ snapshots: '@esbuild/android-x64@0.27.1': optional: true + '@esbuild/android-x64@0.27.2': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true @@ -16716,6 +16889,9 @@ snapshots: '@esbuild/darwin-arm64@0.27.1': optional: true + '@esbuild/darwin-arm64@0.27.2': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true @@ -16725,6 +16901,9 @@ snapshots: '@esbuild/darwin-x64@0.27.1': optional: true + '@esbuild/darwin-x64@0.27.2': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true @@ -16734,6 +16913,9 @@ snapshots: '@esbuild/freebsd-arm64@0.27.1': optional: true + '@esbuild/freebsd-arm64@0.27.2': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true @@ -16743,6 +16925,9 @@ snapshots: '@esbuild/freebsd-x64@0.27.1': optional: true + '@esbuild/freebsd-x64@0.27.2': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true @@ -16752,6 +16937,9 @@ snapshots: '@esbuild/linux-arm64@0.27.1': optional: true + '@esbuild/linux-arm64@0.27.2': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true @@ -16761,6 +16949,9 @@ snapshots: '@esbuild/linux-arm@0.27.1': optional: true + '@esbuild/linux-arm@0.27.2': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true @@ -16770,6 +16961,9 @@ snapshots: '@esbuild/linux-ia32@0.27.1': optional: true + '@esbuild/linux-ia32@0.27.2': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true @@ -16779,6 +16973,9 @@ snapshots: '@esbuild/linux-loong64@0.27.1': optional: true + '@esbuild/linux-loong64@0.27.2': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true @@ -16788,6 +16985,9 @@ snapshots: '@esbuild/linux-mips64el@0.27.1': optional: true + '@esbuild/linux-mips64el@0.27.2': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true @@ -16797,6 +16997,9 @@ snapshots: '@esbuild/linux-ppc64@0.27.1': optional: true + '@esbuild/linux-ppc64@0.27.2': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true @@ -16806,6 +17009,9 @@ snapshots: '@esbuild/linux-riscv64@0.27.1': optional: true + '@esbuild/linux-riscv64@0.27.2': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true @@ -16815,6 +17021,9 @@ snapshots: '@esbuild/linux-s390x@0.27.1': optional: true + '@esbuild/linux-s390x@0.27.2': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true @@ -16824,6 +17033,9 @@ snapshots: '@esbuild/linux-x64@0.27.1': optional: true + '@esbuild/linux-x64@0.27.2': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true @@ -16833,6 +17045,9 @@ snapshots: '@esbuild/netbsd-arm64@0.27.1': optional: true + '@esbuild/netbsd-arm64@0.27.2': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true @@ -16842,6 +17057,9 @@ snapshots: '@esbuild/netbsd-x64@0.27.1': optional: true + '@esbuild/netbsd-x64@0.27.2': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true @@ -16851,6 +17069,9 @@ snapshots: '@esbuild/openbsd-arm64@0.27.1': optional: true + '@esbuild/openbsd-arm64@0.27.2': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true @@ -16860,6 +17081,9 @@ snapshots: '@esbuild/openbsd-x64@0.27.1': optional: true + '@esbuild/openbsd-x64@0.27.2': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true @@ -16869,6 +17093,9 @@ snapshots: '@esbuild/openharmony-arm64@0.27.1': optional: true + '@esbuild/openharmony-arm64@0.27.2': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true @@ -16878,6 +17105,9 @@ snapshots: '@esbuild/sunos-x64@0.27.1': optional: true + '@esbuild/sunos-x64@0.27.2': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true @@ -16887,6 +17117,9 @@ snapshots: '@esbuild/win32-arm64@0.27.1': optional: true + '@esbuild/win32-arm64@0.27.2': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true @@ -16896,6 +17129,9 @@ snapshots: '@esbuild/win32-ia32@0.27.1': optional: true + '@esbuild/win32-ia32@0.27.2': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true @@ -16905,6 +17141,9 @@ snapshots: '@esbuild/win32-x64@0.27.1': optional: true + '@esbuild/win32-x64@0.27.2': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -21040,11 +21279,11 @@ snapshots: b4a@1.6.7: {} - babel-loader@10.0.0(@babel/core@7.28.0)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + babel-loader@10.0.0(@babel/core@7.28.0)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: '@babel/core': 7.28.0 find-up: 5.0.0 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) babel-plugin-transform-hook-names@1.0.2(@babel/core@7.28.0): dependencies: @@ -21904,14 +22143,14 @@ snapshots: is-what: 3.14.1 optional: true - copy-webpack-plugin@13.0.1(webpack@5.101.3(esbuild@0.27.1)): + copy-webpack-plugin@13.0.1(webpack@5.101.3(esbuild@0.27.2)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 schema-utils: 4.3.2 serialize-javascript: 6.0.2 tinyglobby: 0.2.14 - webpack: 5.101.3(esbuild@0.27.1) + webpack: 5.101.3(esbuild@0.27.2) core-js@3.46.0: {} @@ -22020,7 +22259,7 @@ snapshots: css-functions-list@3.2.3: {} - css-loader@5.2.7(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + css-loader@5.2.7(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) loader-utils: 2.0.4 @@ -22032,9 +22271,9 @@ snapshots: postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.7.3 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) - css-loader@7.1.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + css-loader@7.1.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -22045,7 +22284,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.3 optionalDependencies: - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) css-select@4.3.0: dependencies: @@ -23141,12 +23380,12 @@ snapshots: es6-promise@4.2.8: {} - esbuild-loader@4.3.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + esbuild-loader@4.3.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: esbuild: 0.25.12 get-tsconfig: 4.10.1 loader-utils: 2.0.4 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) webpack-sources: 1.4.3 esbuild@0.25.12: @@ -23236,6 +23475,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.1 '@esbuild/win32-x64': 0.27.1 + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + escalade@3.2.0: {} escape-goat@4.0.0: {} @@ -26241,11 +26509,11 @@ snapshots: mind-elixir@5.3.8: {} - mini-css-extract-plugin@2.9.4(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + mini-css-extract-plugin@2.9.4(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: schema-utils: 4.3.2 tapable: 2.2.3 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) minimalistic-assert@1.0.1: {} @@ -27307,7 +27575,7 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.6 - postcss-loader@4.3.0(postcss@8.5.6)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + postcss-loader@4.3.0(postcss@8.5.6)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: cosmiconfig: 7.1.0 klona: 2.0.6 @@ -27315,16 +27583,16 @@ snapshots: postcss: 8.5.6 schema-utils: 3.3.0 semver: 7.7.3 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) - postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: cosmiconfig: 9.0.0(typescript@5.0.4) jiti: 2.6.1 postcss: 8.5.6 semver: 7.7.3 optionalDependencies: - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) transitivePeerDependencies: - typescript @@ -27826,11 +28094,11 @@ snapshots: raw-loader@0.5.1: {} - raw-loader@4.0.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + raw-loader@4.0.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) rc@1.2.8: dependencies: @@ -29226,15 +29494,15 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 4.1.0 - style-loader@2.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + style-loader@2.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) - style-loader@4.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + style-loader@4.0.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) style-mod@4.1.2: {} @@ -29586,28 +29854,28 @@ snapshots: rimraf: 2.6.3 optional: true - terser-webpack-plugin@5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + terser-webpack-plugin@5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.44.0 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) optionalDependencies: '@swc/core': 1.11.29(@swc/helpers@0.5.17) - esbuild: 0.27.1 + esbuild: 0.27.2 - terser-webpack-plugin@5.3.14(esbuild@0.27.1)(webpack@5.101.3(esbuild@0.27.1)): + terser-webpack-plugin@5.3.14(esbuild@0.27.2)(webpack@5.101.3(esbuild@0.27.2)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.44.0 - webpack: 5.101.3(esbuild@0.27.1) + webpack: 5.101.3(esbuild@0.27.2) optionalDependencies: - esbuild: 0.27.1 + esbuild: 0.27.2 terser@5.44.0: dependencies: @@ -29755,7 +30023,7 @@ snapshots: ts-dedent@2.2.0: {} - ts-loader@9.5.4(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + ts-loader@9.5.4(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: chalk: 4.1.2 enhanced-resolve: 5.18.3 @@ -29763,7 +30031,7 @@ snapshots: semver: 7.7.3 source-map: 0.7.6 typescript: 5.0.4 - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.10.4)(typescript@5.0.4): dependencies: @@ -30436,7 +30704,7 @@ snapshots: webidl-conversions@7.0.0: optional: true - webpack-dev-middleware@7.4.3(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + webpack-dev-middleware@7.4.3(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: colorette: 2.0.20 memfs: 4.42.0 @@ -30445,9 +30713,9 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.2 optionalDependencies: - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) - webpack-dev-server@5.2.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)): + webpack-dev-server@5.2.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -30475,10 +30743,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.3(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + webpack-dev-middleware: 7.4.3(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) optionalDependencies: - webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1) + webpack: 5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2) transitivePeerDependencies: - bufferutil - debug @@ -30492,7 +30760,7 @@ snapshots: webpack-sources@3.3.3: {} - webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1): + webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -30516,7 +30784,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.3 - terser-webpack-plugin: 5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.1)) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -30524,7 +30792,7 @@ snapshots: - esbuild - uglify-js - webpack@5.101.3(esbuild@0.27.1): + webpack@5.101.3(esbuild@0.27.2): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -30548,7 +30816,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.3 - terser-webpack-plugin: 5.3.14(esbuild@0.27.1)(webpack@5.101.3(esbuild@0.27.1)) + terser-webpack-plugin: 5.3.14(esbuild@0.27.2)(webpack@5.101.3(esbuild@0.27.2)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: From 551b2aa33ae34825567003894fcf6e591ef871c0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 00:16:42 +0000 Subject: [PATCH 639/873] fix(deps): update dependency @codemirror/commands to v6.10.1 --- packages/codemirror/package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 0a92dffbc5..b13d626cb3 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -5,7 +5,7 @@ "type": "module", "main": "./src/index.ts", "dependencies": { - "@codemirror/commands": "6.10.0", + "@codemirror/commands": "6.10.1", "@codemirror/lang-css": "6.3.1", "@codemirror/lang-html": "6.4.11", "@codemirror/lang-javascript": "6.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c586ef7428..b27e68889d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1187,8 +1187,8 @@ importers: packages/codemirror: dependencies: '@codemirror/commands': - specifier: 6.10.0 - version: 6.10.0 + specifier: 6.10.1 + version: 6.10.1 '@codemirror/lang-css': specifier: 6.3.1 version: 6.3.1 @@ -1302,7 +1302,7 @@ importers: version: 6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2) '@replit/codemirror-vim': specifier: 6.3.0 - version: 6.3.0(@codemirror/commands@6.10.0)(@codemirror/language@6.11.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4) + version: 6.3.0(@codemirror/commands@6.10.1)(@codemirror/language@6.11.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4) '@ssddanbrown/codemirror-lang-smarty': specifier: 1.0.0 version: 1.0.0 @@ -2049,8 +2049,8 @@ packages: '@codemirror/autocomplete@6.18.6': resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} - '@codemirror/commands@6.10.0': - resolution: {integrity: sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==} + '@codemirror/commands@6.10.1': + resolution: {integrity: sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==} '@codemirror/commands@6.8.1': resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} @@ -16034,7 +16034,7 @@ snapshots: '@codemirror/view': 6.39.4 '@lezer/common': 1.2.3 - '@codemirror/commands@6.10.0': + '@codemirror/commands@6.10.1': dependencies: '@codemirror/language': 6.11.0 '@codemirror/state': 6.5.2 @@ -18688,9 +18688,9 @@ snapshots: '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 - '@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.0)(@codemirror/language@6.11.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)': + '@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.1)(@codemirror/language@6.11.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)': dependencies: - '@codemirror/commands': 6.10.0 + '@codemirror/commands': 6.10.1 '@codemirror/language': 6.11.0 '@codemirror/search': 6.5.11 '@codemirror/state': 6.5.2 From c56a253e4973cedb595546f6be3c4dec26bf1337 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 00:17:17 +0000 Subject: [PATCH 640/873] fix(deps): update dependency preact-iso to v2.11.1 --- apps/website/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/website/package.json b/apps/website/package.json index 1c686513f0..46e6f61d60 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -12,7 +12,7 @@ "i18next": "25.7.3", "i18next-http-backend": "3.0.2", "preact": "10.28.0", - "preact-iso": "2.11.0", + "preact-iso": "2.11.1", "preact-render-to-string": "6.6.4", "react-i18next": "16.5.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c586ef7428..33e007b18b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -808,8 +808,8 @@ importers: specifier: 10.28.0 version: 10.28.0 preact-iso: - specifier: 2.11.0 - version: 2.11.0(preact-render-to-string@6.6.4(preact@10.28.0))(preact@10.28.0) + specifier: 2.11.1 + version: 2.11.1(preact-render-to-string@6.6.4(preact@10.28.0))(preact@10.28.0) preact-render-to-string: specifier: 6.6.4 version: 6.6.4(preact@10.28.0) @@ -11569,8 +11569,8 @@ packages: potpack@2.1.0: resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==} - preact-iso@2.11.0: - resolution: {integrity: sha512-oThWJQcgcnaWh6UKy1qrBkxIWp5CkqvnHiFdLiDUxfNkGdpQ5veGQw9wOVS0NDp7X8xo98wxE4wng5jLv1e9Ug==} + preact-iso@2.11.1: + resolution: {integrity: sha512-rLy0RmzP/hrDjnFdnEblxFgKtzUj4njkHrpGJBGS7S4QuYw1zv0lA38qsWpeAAB10JAz/hF2CsHrLen9ufCtbw==} peerDependencies: preact: 10.28.0 preact-render-to-string: '>=6.4.0' @@ -27634,7 +27634,7 @@ snapshots: potpack@2.1.0: {} - preact-iso@2.11.0(preact-render-to-string@6.6.4(preact@10.28.0))(preact@10.28.0): + preact-iso@2.11.1(preact-render-to-string@6.6.4(preact@10.28.0))(preact@10.28.0): dependencies: preact: 10.28.0 preact-render-to-string: 6.6.4(preact@10.28.0) From 53df319aebfe8201a812249573d1e91e204d0440 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 00:17:52 +0000 Subject: [PATCH 641/873] chore(deps): update dependency @redocly/cli to v2.13.0 --- apps/build-docs/package.json | 2 +- pnpm-lock.yaml | 42 ++++++++++++++++-------------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json index 93e0e69f44..a48ca2f021 100644 --- a/apps/build-docs/package.json +++ b/apps/build-docs/package.json @@ -11,7 +11,7 @@ "license": "AGPL-3.0-only", "packageManager": "pnpm@10.26.0", "devDependencies": { - "@redocly/cli": "2.12.7", + "@redocly/cli": "2.13.0", "archiver": "7.0.1", "fs-extra": "11.3.2", "react": "19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c586ef7428..7e0d867066 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,8 +134,8 @@ importers: apps/build-docs: devDependencies: '@redocly/cli': - specifier: 2.12.7 - version: 2.12.7(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5) + specifier: 2.13.0 + version: 2.13.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5) archiver: specifier: 7.0.1 version: 7.0.1 @@ -4242,8 +4242,8 @@ packages: '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.12.7': - resolution: {integrity: sha512-cevNpojACA3JVUU3fqIzebY1CXipeBl84EMrriAgB4Pi0cHWImf12WkpLN/MCKMlW20/IzBzQ04CVlTyKFXuyw==} + '@redocly/cli@2.13.0': + resolution: {integrity: sha512-VOGh8p5gKy+u94SbvMGaHvDM6TPw668D9iQkNSztoi4T5sj3ZwM7Y8Z3yZnMqC5s5epDcLAMq4jCO8UVn5ZWHg==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true @@ -4257,12 +4257,12 @@ packages: resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.12.7': - resolution: {integrity: sha512-b32Pvl4IE2QZFPpPXD7Qciwy1/AZ2EUaYJ++Oyngaz5WlyeGb9HX/fWmf2QO0YvSqNdK7OSY3m8lPBQ+zlNlgw==} + '@redocly/openapi-core@2.13.0': + resolution: {integrity: sha512-xQ4z5tsrXbIa4EfCniHv1zZ4etmQ0lpRcxy750iOamV5A/+19mgbPtD+UQCoT18puDAjcnOgpX7x2ha72qKrnw==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.12.7': - resolution: {integrity: sha512-pBm81qeCYkOC0BCAO6lnEDifLChpCUFP6CsBPNXTYgpFa606UjDULYVIcVUOvwZGlqv6euWIHNT8DfkzFGeltQ==} + '@redocly/respect-core@2.13.0': + resolution: {integrity: sha512-35OidNXWkmmsJiwgX+tFw7FaU8usZVvZ/lFBFNJga65pivEvaDlfiwKxIRTzM4iuNbc2FRvP2q30dlGAztv0tg==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@replit/codemirror-indentation-markers@6.5.3': @@ -10236,10 +10236,6 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} - engines: {node: 20 || >=22} - minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} @@ -15040,6 +15036,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -18584,14 +18582,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.12.7(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)': + '@redocly/cli@2.13.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.12.7(ajv@8.17.1) - '@redocly/respect-core': 2.12.7(ajv@8.17.1) + '@redocly/openapi-core': 2.13.0(ajv@8.17.1) + '@redocly/respect-core': 2.13.0(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -18643,7 +18641,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.12.7(ajv@8.17.1)': + '@redocly/openapi-core@2.13.0(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 '@redocly/config': 0.41.0 @@ -18657,12 +18655,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.12.7(ajv@8.17.1)': + '@redocly/respect-core@2.13.0(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.17.1 - '@redocly/openapi-core': 2.12.7(ajv@8.17.1) + '@redocly/openapi-core': 2.13.0(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 @@ -24118,7 +24116,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.1.1 - minimatch: 10.0.3 + minimatch: 10.1.1 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 2.0.0 @@ -24663,7 +24661,7 @@ snapshots: ignore-walk@8.0.0: dependencies: - minimatch: 10.0.3 + minimatch: 10.1.1 ignore@5.3.2: {} @@ -26249,10 +26247,6 @@ snapshots: minimalistic-assert@1.0.1: {} - minimatch@10.0.3: - dependencies: - '@isaacs/brace-expansion': 5.0.0 - minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 From 5ce81f1a3262882569d0b13f50ae5a46afe50851 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 02:30:46 +0200 Subject: [PATCH 642/873] client/note title widget: add support for custom CSS class name --- apps/client/src/widgets/note_title.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_title.tsx b/apps/client/src/widgets/note_title.tsx index 7f7f0cd221..b2f8e68697 100644 --- a/apps/client/src/widgets/note_title.tsx +++ b/apps/client/src/widgets/note_title.tsx @@ -9,8 +9,9 @@ import { isLaunchBarConfig } from "../services/utils"; import appContext from "../components/app_context"; import branches from "../services/branches"; import { isIMEComposing } from "../services/shortcuts"; +import clsx from "clsx"; -export default function NoteTitleWidget() { +export default function NoteTitleWidget(props: {className?: string}) { const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext(); const title = useNoteProperty(note, "title", componentId); const isProtected = useNoteProperty(note, "isProtected"); @@ -67,7 +68,7 @@ export default function NoteTitleWidget() { }); return ( -
    +
    {note && Date: Fri, 19 Dec 2025 02:46:50 +0200 Subject: [PATCH 643/873] client/note title row: use distinct style when used as a note split title --- apps/client/src/layouts/desktop_layout.tsx | 2 +- apps/client/src/widgets/note_title.css | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 4f6f7e5af6..7a6cfc3c10 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -132,7 +132,7 @@ export default class DesktopLayout { new SplitNoteContainer(() => new NoteWrapperWidget() .child(new FlexContainer("row") - .class("title-row") + .class("title-row note-split-title") .cssBlock(".title-row > * { margin: 5px; }") .child() .child() diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index f798e83ef4..fe3e0e912a 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -36,18 +36,25 @@ body.mobile .note-title-widget input.note-title { body.experimental-feature-new-layout { .title-row { container-type: size; - border-bottom: 1px solid var(--main-border-color); transition: border 400ms ease-out; + + &.note-split-title { + border-bottom: 1px solid var(--main-border-color); - &.hide-title { - border-bottom-color: transparent; - transition: none; + &.hide-title { + border-bottom-color: transparent; + transition: none; + } + + .note-icon-widget { + margin-inline: 12px 8px; + } } .note-icon-widget { --note-icon-size: 16px; --note-icon-container-padding-size: 6px; - margin-inline: 12px 8px; + margin-inline: 0; } .note-title-widget { From 3e783817b6f436e3df711652e034fff6522ccf2e Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 19 Dec 2025 02:51:04 +0200 Subject: [PATCH 644/873] style/quick edit dialog: tweak title --- apps/client/src/widgets/dialogs/PopupEditor.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css index 136bc5015e..4a83521743 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.css +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -54,10 +54,8 @@ body.mobile .modal.popup-editor-dialog .modal-dialog { min-height: unset; } -.modal.popup-editor-dialog .note-icon-widget { - width: 32px; - margin: 0; - padding: 0; +.modal.popup-editor-dialog div.note-title-widget { + --note-title-padding-inline: 8px; } .modal.popup-editor-dialog .note-icon-widget button.note-icon, From 44ae60005c6be679c5d4c3afeb4622d0c4e709d8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 06:38:07 +0000 Subject: [PATCH 645/873] chore(deps): update dependency fs-extra to v11.3.3 --- apps/build-docs/package.json | 2 +- apps/edit-docs/package.json | 2 +- apps/server/package.json | 2 +- pnpm-lock.yaml | 54 ++++++++++++++++++++++++------------ 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json index 93e0e69f44..47ea30d5ec 100644 --- a/apps/build-docs/package.json +++ b/apps/build-docs/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@redocly/cli": "2.12.7", "archiver": "7.0.1", - "fs-extra": "11.3.2", + "fs-extra": "11.3.3", "react": "19.2.3", "react-dom": "19.2.3", "typedoc": "0.28.15", diff --git a/apps/edit-docs/package.json b/apps/edit-docs/package.json index 6106672473..3ddfbcb24a 100644 --- a/apps/edit-docs/package.json +++ b/apps/edit-docs/package.json @@ -13,7 +13,7 @@ "@types/fs-extra": "11.0.4", "copy-webpack-plugin": "13.0.1", "electron": "39.2.7", - "fs-extra": "11.3.2" + "fs-extra": "11.3.3" }, "scripts": { "edit-docs": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-docs.ts", diff --git a/apps/server/package.json b/apps/server/package.json index 20e9974abd..8af8d202d8 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -92,7 +92,7 @@ "express-rate-limit": "8.2.1", "express-session": "1.18.2", "file-uri-to-path": "2.0.0", - "fs-extra": "11.3.2", + "fs-extra": "11.3.3", "helmet": "8.1.0", "html": "1.0.0", "html2plaintext": "2.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 426fac9c9d..239317973f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,8 +140,8 @@ importers: specifier: 7.0.1 version: 7.0.1 fs-extra: - specifier: 11.3.2 - version: 11.3.2 + specifier: 11.3.3 + version: 11.3.3 react: specifier: 19.2.3 version: 19.2.3 @@ -480,8 +480,8 @@ importers: specifier: 39.2.7 version: 39.2.7 fs-extra: - specifier: 11.3.2 - version: 11.3.2 + specifier: 11.3.3 + version: 11.3.3 apps/server: dependencies: @@ -673,8 +673,8 @@ importers: specifier: 2.0.0 version: 2.0.0 fs-extra: - specifier: 11.3.2 - version: 11.3.2 + specifier: 11.3.3 + version: 11.3.3 helmet: specifier: 8.1.0 version: 8.1.0 @@ -8533,8 +8533,8 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.2: - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} fs-extra@7.0.1: @@ -15083,6 +15083,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-upload': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-ai@47.3.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -15336,7 +15338,7 @@ snapshots: '@babel/traverse': 7.28.4 '@ckeditor/ckeditor5-dev-utils': 54.0.0(@babel/core@7.28.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) chalk: 5.6.2 - fs-extra: 11.3.2 + fs-extra: 11.3.3 glob: 11.0.3 plural-forms: 0.5.5 pofile: 1.1.4 @@ -15365,7 +15367,7 @@ snapshots: css-loader: 7.1.2(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) cssnano: 7.1.1(postcss@8.5.6) esbuild-loader: 4.3.0(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) - fs-extra: 11.3.2 + fs-extra: 11.3.3 glob: 11.0.3 is-interactive: 2.0.0 mini-css-extract-plugin: 2.9.4(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) @@ -15434,6 +15436,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-decoupled@47.3.0': dependencies: @@ -15443,6 +15447,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-inline@47.3.0': dependencies: @@ -15560,6 +15566,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-font@47.3.0': dependencies: @@ -15634,6 +15642,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 '@ckeditor/ckeditor5-widget': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-html-embed@47.3.0': dependencies: @@ -15859,7 +15869,7 @@ snapshots: buffer: 6.0.3 chalk: 5.6.2 css-loader: 5.2.7(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)) - fs-extra: 11.3.2 + fs-extra: 11.3.3 glob: 11.0.3 minimist: 1.2.8 postcss: 8.5.6 @@ -15926,6 +15936,8 @@ snapshots: '@ckeditor/ckeditor5-paste-from-office': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-paste-from-office@47.3.0': dependencies: @@ -15933,6 +15945,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-engine': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-real-time-collaboration@47.3.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -15974,6 +15988,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-revision-history@47.3.0': dependencies: @@ -16051,6 +16067,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-special-characters@47.3.0': dependencies: @@ -16060,6 +16078,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-style@47.3.0': dependencies: @@ -16755,7 +16775,7 @@ snapshots: debug: 4.4.3(supports-color@8.1.1) extract-zip: 2.0.1 filenamify: 4.3.0 - fs-extra: 11.3.2 + fs-extra: 11.3.3 galactus: 1.0.0 get-package-info: 1.0.0 junk: 3.1.0 @@ -16816,7 +16836,7 @@ snapshots: '@malept/cross-spawn-promise': 2.0.0 debug: 4.4.3(supports-color@8.1.1) dir-compare: 4.2.0 - fs-extra: 11.3.2 + fs-extra: 11.3.3 minimatch: 9.0.5 plist: 3.1.0 transitivePeerDependencies: @@ -16826,7 +16846,7 @@ snapshots: dependencies: cross-dirname: 0.1.0 debug: 4.4.3(supports-color@8.1.1) - fs-extra: 11.3.2 + fs-extra: 11.3.3 minimist: 1.2.8 postject: 1.0.0-alpha.6 transitivePeerDependencies: @@ -19161,7 +19181,7 @@ snapshots: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) ajv-formats: 3.0.1(ajv@8.13.0) - fs-extra: 11.3.2 + fs-extra: 11.3.3 import-lazy: 4.0.0 jju: 1.4.0 resolve: 1.22.10 @@ -23021,7 +23041,7 @@ snapshots: dpdm@3.14.0: dependencies: chalk: 4.1.2 - fs-extra: 11.3.2 + fs-extra: 11.3.3 glob: 10.4.5 ora: 5.4.1 tslib: 2.8.1 @@ -24236,7 +24256,7 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 - fs-extra@11.3.2: + fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 From bf74c40f73d293d7c4d05912ab382790d1a7639b Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 19 Dec 2025 07:33:04 +0100 Subject: [PATCH 646/873] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ --- docs/README-pt_BR.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README-pt_BR.md b/docs/README-pt_BR.md index 6b77298f6f..3a76610788 100644 --- a/docs/README-pt_BR.md +++ b/docs/README-pt_BR.md @@ -43,7 +43,7 @@ conhecimento pessoal. unstable development version, updated daily with the latest features and fixes. -## 📚 Documentation +## 📚 Documentação **Visit our comprehensive documentation at [docs.triliumnotes.org](https://docs.triliumnotes.org/)** From 200e5d04a40b8b9db206a9eefc45019386bdb412 Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 19 Dec 2025 11:24:19 +0100 Subject: [PATCH 647/873] Translated using Weblate (Russian) Currently translated at 97.6% (380 of 389 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/ --- apps/server/src/assets/translations/ru/server.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/assets/translations/ru/server.json b/apps/server/src/assets/translations/ru/server.json index ef64ef149b..e5d275ba14 100644 --- a/apps/server/src/assets/translations/ru/server.json +++ b/apps/server/src/assets/translations/ru/server.json @@ -101,8 +101,8 @@ "toggle-classic-editor-toolbar": "Перейти на вкладку «Форматирование» для редактора с фиксированной панелью инструментов", "export-as-pdf": "Экспортировать текущую заметку в формате PDF", "toggle-zen-mode": "Включает/отключает режим дзен (минимальный пользовательский интерфейс для фокусирования)", - "toggle-note-hoisting": "Переключить закрепление активной заметки", - "unhoist": "Убрать закрепление везде", + "toggle-note-hoisting": "Переключить фокус активной заметки", + "unhoist": "Убрать фокус с любой заметки", "force-save-revision": "Принудительное создать/сохранить снимок версии активной заметки" }, "hidden-subtree": { @@ -245,7 +245,7 @@ "reset-zoom-level": "Сбросить уровень масштабирования", "copy-without-formatting": "Копировать без форматирования", "force-save-revision": "Принудительное сохранение версии", - "unhoist-note": "Открепить заметку", + "unhoist-note": "Снять фокус с заметки", "toggle-right-pane": "Переключить правую панель", "print-active-note": "Печать активной заметки", "render-active-note": "Рендеринг активной заметки", From be5448eba22288dd8506e1f0714c26b2a01a879b Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 19 Dec 2025 11:24:37 +0100 Subject: [PATCH 648/873] Translated using Weblate (Russian) Currently translated at 65.5% (76 of 116 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ru/ --- docs/README-ru.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/README-ru.md b/docs/README-ru.md index 65570afb65..ee6dc748b7 100644 --- a/docs/README-ru.md +++ b/docs/README-ru.md @@ -81,9 +81,8 @@ Trilium Notes – это приложение для заметок с иера подсветку синтаксиса * Быстрая и простая [навигация между заметками](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation), - полнотекстовый поиск и [выделение - заметок](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting) - в отдельный блок + полнотекстовый поиск и [режим фокуса на + заметке](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting) * Бесшовное [версионирование заметки](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions) * Специальные From cbeb5dfb588bb0efcab8e79a62841119bdc992e0 Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 19 Dec 2025 11:49:28 +0100 Subject: [PATCH 649/873] Translated using Weblate (Russian) Currently translated at 98.0% (1676 of 1709 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/ --- .../src/translations/ru/translation.json | 164 ++++++++++++++---- 1 file changed, 131 insertions(+), 33 deletions(-) diff --git a/apps/client/src/translations/ru/translation.json b/apps/client/src/translations/ru/translation.json index 34ee4a8f70..4c41a126e1 100644 --- a/apps/client/src/translations/ru/translation.json +++ b/apps/client/src/translations/ru/translation.json @@ -472,11 +472,11 @@ "app_css": "отмечает заметки CSS, которые загружаются в приложение Trilium и, таким образом, могут использоваться для изменения внешнего вида Trilium.", "app_theme_base": "установите значение \"next\", \"next-light\" или \"next-dark\", чтобы использовать соответствующую тему TriliumNext (автоматическую, светлую или темную) в качестве основы для пользовательской темы вместо устаревшей.", "exclude_from_note_map": "Заметки с этой меткой будут скрыты на карте заметок", - "workspace": "отмечает эту заметку как рабочее пространство, для удобного закрепления", - "workspace_icon_class": "определяет CSS-класс значка поля, который будет использоваться во вкладке при закреплении этой заметки", - "workspace_tab_background_color": "Цвет CSS, используемый во вкладке заметки при ее закреплении", - "workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если она будет перемещена в рабочую область, содержащую этот шаблон", - "workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки при перемещении их к какому-либо предку этой заметки рабочей области", + "workspace": "отмечает эту заметку как рабочее пространство, для удобной установки фокуса", + "workspace_icon_class": "определяет CSS-класс значка поля, который будет использоваться во вкладке при установке фокуса на этой заметке", + "workspace_tab_background_color": "Цвет CSS, используемый во вкладке заметки при установке на нее фокуса", + "workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если будет установлен фокус на рабочую область с этим шаблоном", + "workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки, когда установлен фокус на какую-либо родительскую заметку этого рабочего пространство", "workspace_calendar_root": "Определяет корень календаря для каждого рабочего пространства", "hide_highlight_widget": "Скрыть виджет «Выделенное»", "is_owned_by_note": "принадлежит заметке", @@ -503,7 +503,7 @@ "custom_resource_provider": "см. Пользовательский обработчик запросов", "widget": "отмечает эту заметку как пользовательский виджет, который будет добавлен в дерево компонентов Trilium", "search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки", - "workspace_inbox": "расположение в папке «Входящие» по умолчанию для новых заметок при перемещении их в некую родственную папку этой заметки в рабочей области", + "workspace_inbox": "расположение в папке «Входящие» по умолчанию для новых заметок, когда установлен фокус на какую-либо родительскую заметку этого рабочего пространство", "sql_console_home": "расположение заметок консоли SQL по умолчанию", "css_class": "значение этой метки затем добавляется как CSS-класс к узлу, представляющему данную заметку в дереве. Это может быть полезно для изменения внешнего вида заметки. Может использоваться в шаблонах заметок.", "bookmark_folder": "заметка с этой меткой появится в закладках как папка (с предоставлением доступа к ее дочерним элементам)", @@ -519,7 +519,7 @@ "share_index": "заметка с этой меткой будет содержать список всех корневых узлов общедоступных заметок", "toc": "#toc или #toc=show принудительно отобразят оглавление, #toc=hide — скроют его. Если метка отсутствует, применяется глобальная настройка", "color": "определяет цвет заметки в дереве заметок, ссылках и т. д. Используйте любое допустимое значение цвета CSS, например «red» или #a13d5f", - "keep_current_hoisting": "Открытие этой ссылки не изменит закрепление, даже если заметка не отображается в текущем закрепленном поддереве.", + "keep_current_hoisting": "Открытие этой ссылки не изменит фокус, даже если заметка не отображается в текущем закрепленном поддереве.", "execute_description": "Более подробное описание текущей заметки типа \"Код\", отображаемое вместе с кнопкой \"Выполнить\"", "run_on_note_creation": "выполняется при создании заметки на сервере. Используйте это отношение, если хотите запустить скрипт для всех заметок, созданных в определённом поддереве. В этом случае создайте его в корневой заметке поддерева и сделайте его наследуемым. Новая заметка, созданная в поддереве (любой глубины), запустит скрипт.", "run_on_child_note_creation": "выполняется, когда создается новая заметка под заметкой, в которой определено это отношение", @@ -567,7 +567,8 @@ "edit-column-title": "Нажмите, чтобы изменить заголовок столбца", "edit-note-title": "Нажмите, чтобы изменить название заметки", "add-column-placeholder": "Введите имя столбца...", - "new-item-placeholder": "Введите название заметки..." + "new-item-placeholder": "Введите название заметки...", + "column-already-exists": "Такая колонка уже добавлена на доску." }, "table_context_menu": { "delete_row": "Удалить строку" @@ -576,7 +577,7 @@ "vector_dark": "Vector (Темная)", "vector_light": "Vector (Светлая)", "max-nesting-depth": "Максимальная глубина вложенности:", - "map-style": "Стиль карты:", + "map-style": "Стиль карты", "display-week-numbers": "Отображать номера недель", "hide-weekends": "Скрыть выходные", "raster": "Растр", @@ -606,7 +607,8 @@ "title": "Внешний вид" }, "svg": { - "export_to_png": "Диаграмму не может быть экспортирована в PNG." + "export_to_png": "Диаграмму не может быть экспортирована в PNG.", + "export_to_svg": "Не удалось экспортировать диаграмму в SVG." }, "png_export_button": { "button_title": "Экспортировать диаграмму как PNG" @@ -621,7 +623,8 @@ }, "note_language": { "configure-languages": "Настроить языки...", - "not_set": "Не установлен" + "not_set": "Язык не установлен", + "help-on-languages": "Помощь по языкам содержимого..." }, "time_selector": { "invalid_input": "Введенное значение времени не является допустимым числом.", @@ -692,7 +695,8 @@ "copy": "Скопировать", "cut": "Вырезать", "search_online": "Поиск \"{{term}}\" в {{searchEngine}}", - "add-term-to-dictionary": "Добавить \"{{term}}\" в словарь" + "add-term-to-dictionary": "Добавить \"{{term}}\" в словарь", + "search_in_trilium": "Искать \"{{term}}\" в Trilium" }, "editing": { "editor_type": { @@ -746,10 +750,10 @@ "hide-archived-notes": "Скрыть архивные заметки", "automatically-collapse-notes": "Автоматически сворачивать заметки", "tree-settings-title": "Настройки дерева", - "unhoist": "Открепить", + "unhoist": "Убрать фокус", "scroll-active-title": "Прокрутить к активной заметке", "collapse-title": "Свернуть дерево", - "hoist-this-note-workspace": "Закрепить заметку (рабочая область)", + "hoist-this-note-workspace": "Фокус на заметке (рабочая область)", "auto-collapsing-notes-after-inactivity": "Автоматическое сворачивание заметок после бездействия...", "create-child-note": "Создать дочернюю заметку", "save-changes": "Сохранить и применить изменения", @@ -812,8 +816,8 @@ "export": "Экспорт", "open-in-a-new-tab": "Открыть в новой вкладке", "open-in-a-new-split": "Открыть в новой панели", - "unhoist-note": "Открепить заметку", - "hoist-note": "Закрепить заметку", + "unhoist-note": "Снять фокус", + "hoist-note": "Фокус на заметке", "protect-subtree": "Защитить поддерево", "unprotect-subtree": "Снять защиту с поддерева", "copy-clone": "Скопировать / Склонировать", @@ -833,7 +837,7 @@ "apply-bulk-actions": "Применить массовые действия", "recent-changes-in-subtree": "Последние изменения в поддереве", "copy-note-path-to-clipboard": "Копировать путь к заметке в буфер обмена", - "convert-to-attachment-confirm": "Вы уверены, что хотите преобразовать выбранные заметки во вложения их родительских заметок?", + "convert-to-attachment-confirm": "Вы уверены, что хотите преобразовать выбранные заметки во вложения их родительских заметок? Эта операция применяется только к заметкам в виде изображений; другие заметки будут пропущены.", "converted-to-attachments": "{{count}} заметок были преобразованы во вложения.", "archive": "Архивировать", "unarchive": "Разархивировать" @@ -982,9 +986,9 @@ }, "zpetne_odkazy": { "relation": "отношение", - "backlink_one": "{{count}} ссылки", - "backlink_few": "", - "backlink_many": "{{count}} ссылок" + "backlink_one": "{{count}} обратная ссылка", + "backlink_few": "{{count}} обратные ссылки", + "backlink_many": "{{count}} обратных ссылок" }, "note_icon": { "category": "Категория:", @@ -1060,7 +1064,7 @@ "clone_button": "Клонировать заметку в новое место...", "intro_placed": "Эта заметка размещена по следующим путям:", "intro_not_placed": "Эта заметка еще не помещена в дерево заметок.", - "outside_hoisted": "Этот путь находится за пределами закрепленной заметки, и вам придется снять закрепление.", + "outside_hoisted": "Этот путь находится за пределами сфокусированной заметки, и вам придется снять фокус.", "archived": "Архивировано" }, "note_properties": { @@ -1201,7 +1205,8 @@ "max_width_unit": "пикселей", "title": "Ширина контентной области", "default_description": "Trilium по умолчанию ограничивает максимальную ширину контента, чтобы улучшить читаемость на широких экранах.", - "max_width_label": "Максимальная ширина контентной области" + "max_width_label": "Максимальная ширина контентной области", + "centerContent": "Размещать контент по центру" }, "native_title_bar": { "enabled": "включено", @@ -1409,7 +1414,13 @@ "min-days-in-first-week": "Минимальное количество дней в первой неделе", "first-week-info": "Первая неделя содержит первый четверг года в соответствии со стандартом ISO 8601.", "first-week-warning": "Изменение параметров первой недели может привести к дублированию существующих недельных заметок, и существующие недельные заметки не будут обновлены соответствующим образом.", - "formatting-locale": "Формат даты и числа" + "formatting-locale": "Формат даты и числа", + "formatting-locale-auto": "Выбирать на основе языка приложения", + "saturday": "Суббота", + "friday": "Пятница", + "thursday": "Четверг", + "wednesday": "Среда", + "tuesday": "Вторник" }, "backup": { "path": "Путь", @@ -1842,7 +1853,10 @@ "next_theme_button": "Попробовать новую тему", "background_effects_message": "На устройствах Windows фоновые эффекты теперь полностью стабильны. Они добавляют цвет в пользовательский интерфейс, размывая фон за ним. Этот приём также используется в других приложениях, например, в проводнике Windows.", "background_effects_title": "Фоновые эффекты теперь стабильны", - "next_theme_title": "Попробуйте новую тему Trilium" + "next_theme_title": "Попробуйте новую тему Trilium", + "new_layout_button": "Подробнее", + "new_layout_message": "Мы обновили интерфейс Trilium. Старая лента инструментов была удалена и органично интегрирована в основной интерфейс, а ключевые функции теперь выполняет новая строка состояния и разворачиваемые разделы.\n\nНовый интерфейс включен по умолчанию и может быть временно отключен через «Параметры» → «Внешний вид».", + "new_layout_title": "Новый дизайн" }, "zoom_factor": { "description": "Масштабированием также можно управлять с помощью сочетаний клавиш CTRL+- и CTRL+=.", @@ -1852,7 +1866,8 @@ "show_toc": "Показать оглавление" }, "code_mime_types": { - "title": "Доступные типы в выпадающем списке" + "title": "Доступные типы в выпадающем списке", + "tooltip_syntax_highlighting": "Подсветка синтаксиса" }, "search_result": { "no_notes_found": "По заданным параметрам поиска заметки не найдены.", @@ -1975,7 +1990,14 @@ "deletion_reason": ", поскольку вложение не связано с содержимым заметки. Чтобы предотвратить удаление, добавьте ссылку на вложение обратно в содержимое или преобразуйте вложение в заметку." }, "note_title": { - "placeholder": "введите здесь название заметки..." + "placeholder": "введите здесь название заметки...", + "edited_notes": "Измененные заметки", + "note_type_switcher_collection": "Коллекция", + "note_type_switcher_templates": "Шаблон", + "note_type_switcher_others": "Другой тип заметки", + "note_type_switcher_label": "Переключить с {{type}} на:", + "last_modified": "Изменена ", + "created_on": "Создана" }, "units": { "percentage": "%" @@ -2014,7 +2036,10 @@ }, "settings_appearance": { "related_code_blocks": "Цветовая схема для блоков кода в текстовых заметках", - "related_code_notes": "Цветовая схема для заметок типа \"Код\"" + "related_code_notes": "Цветовая схема для заметок типа \"Код\"", + "ui_new_layout": "Новый дизайн", + "ui_old_layout": "Старый дизайн", + "ui": "Пользовательский интерфейс" }, "sql_result": { "no_rows": "По этому запросу не возвращено ни одной строки" @@ -2024,17 +2049,23 @@ }, "editable_text": { "placeholder": "Введите содержимое для заметки...", - "auto-detect-language": "Определен автоматически" + "auto-detect-language": "Определен автоматически", + "keeps-crashing": "Компонент редактирования вылетает. Пожалуйста, попробуйте перезапустить Trilium. Если проблема сохраняется, пожалуйста, создайте отчет об ошибке.", + "editor_crashed_details_title": "Техническая информация", + "editor_crashed_details_intro": "Если эта ошибка возникает несколько раз, пожалуйста, сообщите о ней на GitHub, сопроводив информаций ниже.", + "editor_crashed_content": "Ваши данные были успешно восстановлены, но некоторые из последних изменений могли не быть сохранены." }, "hoisted_note": { - "confirm_unhoisting": "Запрошенная заметка «{{requestedNote}}» находится за пределами поддерева закрепленной заметки \"{{hoistedNote}}\", и для доступа к ней необходимо снять закрепление. Открепить заметку?" + "confirm_unhoisting": "Запрошенная заметка «{{requestedNote}}» находится за пределами поддерева закрепленной заметки \"{{hoistedNote}}\", и для доступа к ней необходимо снять фокус. Снять фокус с заметки?" }, "frontend_script_api": { "sync_warning": "Вы передаете синхронную функцию в `api.runAsyncOnBackendWithManualTransactionHandling()`, \\nхотя вместо этого вам, скорее всего, следует использовать `api.runOnBackend()`.", "async_warning": "Вы передаете асинхронную функцию в `api.runOnBackend()`, которая, скорее всего, не будет работать так, как вы предполагали.\\nЛибо сделайте функцию синхронной (удалив ключевое слово `async`), либо используйте `api.runAsyncOnBackendWithManualTransactionHandling()`." }, "note_detail": { - "could_not_find_typewidget": "Не удалось найти typeWidget для типа '{{type}}'" + "could_not_find_typewidget": "Не удалось найти typeWidget для типа '{{type}}'", + "printing_pdf": "Выполняется экспорт PDF...", + "printing": "Выполняется печать..." }, "book": { "no_children_help": "В этой коллекции нет дочерних заметок, поэтому отображать нечего. Подробности см. в wiki.", @@ -2060,8 +2091,26 @@ "attributes_one": "{{count}} атрибут", "attributes_few": "{{count}} атрибута", "attributes_many": "{{count}} атрибутов", - "note_info_title": "Просмотр информации об этой заметке, например, даты создания/изменения или размер.", - "language_title": "Изменить язык всего содержимого" + "note_info_title": "Просмотр информации о заметке, (даты, размер)", + "language_title": "Изменить язык содержимого", + "code_note_switcher": "Изменить режим языка", + "note_paths_title": "Расположения заметки", + "note_paths_one": "{{count}} место", + "note_paths_few": "{{count}} места", + "note_paths_many": "{{count}} мест", + "attributes_title": "Собственные и унаследованные атрибуты", + "attachments_title_one": "Открыть вложение в новой вкладке", + "attachments_title_few": "Открыть вложения в новой вкладке", + "attachments_title_many": "Открыть вложения в новой вкладке", + "attachments_one": "{{count}} вложение", + "attachments_few": "{{count}} вложения", + "attachments_many": "{{count}} вложений", + "backlinks_one": "{{count}} обратная ссылка", + "backlinks_few": "{{count}} обратные ссылки", + "backlinks_many": "{{count}} обратных ссылок", + "backlinks_title_one": "Обратная ссылка", + "backlinks_title_few": "Обратные ссылки", + "backlinks_title_many": "Обратные ссылки" }, "breadcrumb_badges": { "execute_sql_description": "Эта заметка - SQL-запрос. Нажмите, чтобы выполнить его.", @@ -2070,6 +2119,55 @@ "execute_script": "Выполнить скрипт", "clipped_note_description": "Эта заметка первоначально взята с сайта {{url}}.\n\nНажмите, чтобы перейти на исходную веб-страницу.", "shared_publicly": "Доступно публично", - "shared_locally": "Доступно локально" + "shared_locally": "Доступно локально", + "clipped_note": "Web фрагмент", + "shared_unshare": "Убрать публичный доступ", + "shared_open_in_browser": "Открыть ссылку в браузере", + "shared_copy_to_clipboard": "Скопировать ссылку", + "read_only_temporarily_disabled_description": "В данный момент эта заметка доступна для редактирования, но обычно она находится только в режиме чтения. Заметка снова станет доступна только для чтения, как только вы перейдете к другой заметке.\n\nНажмите, чтобы снова включить режим только для чтения.", + "read_only_temporarily_disabled": "Временное редактирование", + "read_only_auto_description": "Эта заметка была автоматически переведена в режим только для чтения по соображениям производительности. Это автоматическое ограничение можно изменить в настройках.\n\nНажмите, чтобы временно отредактировать её.", + "read_only_auto": "Автоматический режим \"только для чтения\"", + "read_only_explicit_description": "Эта заметка была вручную установлена в режим «только для чтения».\n\nНажмите, чтобы временно отредактировать её.", + "read_only_explicit": "Только для чтения" + }, + "breadcrumb": { + "hoisted_badge_title": "Снять фокус", + "hoisted_badge": "Фокус", + "empty_hide_archived_notes": "Скрыть заметки в архиве", + "create_new_note": "Новая дочерняя заметка", + "scroll_to_top_title": "К началу заметки", + "workspace_badge": "Рабочее пространство" + }, + "tab_history_navigation_buttons": { + "go-forward": "Перейти к следующей заметке", + "go-back": "Перейти к предыдущей заметке" + }, + "server": { + "traefik_blocks_requests": "Если вы используете обратный прокси-сервер Traefik, то следует учитывать, что в него внесены критические изменения, влияющие на связь с сервером.", + "unknown_http_error_content": "Код: {{statusCode}}\nURL: {{method}} {{url}}\nСообщение: {{message}}", + "unknown_http_error_title": "Ошибка связи с сервером" + }, + "note-color": { + "set-color": "Установить цвет заметки", + "clear-color": "Убрать цвет заметки", + "set-custom-color": "Установить другой цвет" + }, + "calendar_view": { + "delete_note": "Удалить заметку..." + }, + "presentation_view": { + "start-presentation": "Начать презентацию", + "edit-slide": "Редактировать слайд" + }, + "read-only-info": { + "edit-note": "Изменить заметку", + "auto-read-only-note": "Заметка отображена в режиме \"только для чтения\" для быстрой загрузки" + }, + "experimental_features": { + "new_layout_description": "Попробуйте новый современный и удобный дизайн. В будущих обновлениях возможны его существенные изменения.", + "new_layout_name": "Новый дизайн", + "title": "Экспериментальные параметры", + "disclaimer": "Эти параметры экспериментальные и могут повлиять на стабильность. Используйте с осторожностью." } } From de5b766d0c48f50bfa72e96a4c2b9fd840ee9764 Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 19 Dec 2025 11:25:03 +0100 Subject: [PATCH 650/873] Translated using Weblate (Russian) Currently translated at 17.7% (27 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/ --- apps/website/src/translations/ru/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/website/src/translations/ru/translation.json b/apps/website/src/translations/ru/translation.json index 6a39c9597d..854fa8a883 100644 --- a/apps/website/src/translations/ru/translation.json +++ b/apps/website/src/translations/ru/translation.json @@ -19,7 +19,7 @@ "note_structure_title": "Структура заметки", "note_structure_description": "Строки могут распологаться иерархически. Не нужно постоянно создавать папки, так как каждая заметка может содержать вложенные под-заметки. Одну и ту же заметку можно добавить сразу в несколько мест в иерархии.", "attributes_title": "Ярлыки и связи заметок", - "hoisting_title": "Рабочие пространства и хосты", + "hoisting_title": "Рабочие пространства и фокус на заметках", "hoisting_description": "Легко разделяйте заметки на личные и рабочие, группируя их в рабочей области. Благодаря этому в вашем дереве будет отображаться только определённый набор заметок.", "attributes_description": "Используйте связи между заметками или добавляйте метки для удобной категоризации. Используйте расширенные атрибуты для ввода структурированной информации, которую можно использовать в таблицах и досках." }, From d22583457f1f7074475c539bb328362393b67dee Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 20:51:27 +0200 Subject: [PATCH 651/873] style(right_pane): left-align title --- apps/client/src/widgets/sidebar/RightPanelContainer.css | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 443fc35b87..39c67fd9ac 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -11,6 +11,7 @@ body.experimental-feature-new-layout #right-pane { .card-header { padding-block: 0.2em; cursor: pointer; + justify-content: flex-start; .card-header-title { padding-inline: 0.5em; From 9acef4d502d601d1d656d007b2f16cdb9e5e6ae3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 21:05:21 +0200 Subject: [PATCH 652/873] feat(layout): button to toggle right pane on horizontal layout --- apps/client/src/layouts/desktop_layout.tsx | 2 ++ .../src/translations/en/translation.json | 3 ++- .../src/widgets/buttons/right_pane_toggle.tsx | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/buttons/right_pane_toggle.tsx diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index af47fd4788..a4f60aa05a 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -9,6 +9,7 @@ import CreatePaneButton from "../widgets/buttons/create_pane_button.js"; import GlobalMenu from "../widgets/buttons/global_menu.jsx"; import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js"; import MovePaneButton from "../widgets/buttons/move_pane_button.js"; +import RightPaneToggle from "../widgets/buttons/right_pane_toggle.jsx"; import CloseZenModeButton from "../widgets/close_zen_button.jsx"; import NoteList from "../widgets/collections/NoteList.jsx"; import ContentHeader from "../widgets/containers/content_header.js"; @@ -91,6 +92,7 @@ export default class DesktopLayout { .optChild(launcherPaneIsHorizontal, ) .child() .child(new TabRowWidget().class("full-width")) + .optChild(launcherPaneIsHorizontal && isNewLayout, ) .optChild(customTitleBarButtons, ) .css("height", "40px") .css("background-color", "var(--launcher-pane-background-color)") diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index c7aeaf50c4..077d24fac5 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2199,6 +2199,7 @@ }, "right_pane": { "empty_message": "Nothing to show for this note", - "empty_button": "Hide the panel" + "empty_button": "Hide the panel", + "toggle": "Toggle right panel" } } diff --git a/apps/client/src/widgets/buttons/right_pane_toggle.tsx b/apps/client/src/widgets/buttons/right_pane_toggle.tsx new file mode 100644 index 0000000000..53bf67ae20 --- /dev/null +++ b/apps/client/src/widgets/buttons/right_pane_toggle.tsx @@ -0,0 +1,21 @@ +import clsx from "clsx"; + +import { t } from "../../services/i18n"; +import ActionButton from "../react/ActionButton"; +import { useTriliumOptionBool } from "../react/hooks"; + +export default function RightPaneToggle() { + const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible"); + + return ( + setRightPaneVisible(!rightPaneVisible)} + /> + ); +} From 3d9efb23eca45f88fa49ba0ef741ade2a338719c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 21:11:46 +0200 Subject: [PATCH 653/873] chore(right_pane): make right pane collapsible --- .../widgets/sidebar/RightPanelContainer.css | 1 - .../widgets/sidebar/RightPanelContainer.tsx | 38 ++++++++++--------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 39c67fd9ac..9afce65e8f 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -1,5 +1,4 @@ body.experimental-feature-new-layout #right-pane { - width: 300px; display: flex; flex-direction: column; diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index e10fae7492..bb15ee10af 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -16,37 +16,41 @@ import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; export default function RightPanelContainer() { - useSplit(); - const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible"); + useSplit(rightPaneVisible); + const { note } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); - const items = [ + const items = (rightPaneVisible ? [ (noteType === "text" || noteType === "doc") && , noteType === "text" && - ].filter(Boolean); + ] : []).filter(Boolean); return (
    - {items.length > 0 ? ( - items - ) : ( -
    - - {t("right_pane.empty_message")} -
    + {rightPaneVisible && ( + items.length > 0 ? ( + items + ) : ( +
    + + {t("right_pane.empty_message")} +
    + ) )}
    ); } -function useSplit() { +function useSplit(visible: boolean) { // Split between right pane and the content pane. useEffect(() => { + if (!visible) return; + // We are intentionally omitting useTriliumOption to avoid re-render due to size change. const rightPaneWidth = Math.max(MIN_WIDTH_PERCENT, options.getInt("rightPaneWidth") ?? MIN_WIDTH_PERCENT); const splitInstance = Split(["#center-pane", "#right-pane"], { @@ -57,5 +61,5 @@ function useSplit() { onDragEnd: (sizes) => options.save("rightPaneWidth", Math.round(sizes[1])) }); return () => splitInstance.destroy(); - }, []); + }, [ visible ]); } From 06ad0bfa9014b9050d9bc5c143b03e4abec9f5f7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 21:11:57 +0200 Subject: [PATCH 654/873] feat(hooks): react faster to setting options --- apps/client/src/widgets/react/hooks.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index eadbf12a67..9540cb8adb 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -168,6 +168,7 @@ export function useTriliumOption(name: OptionNames, needsRefresh?: boolean): [st const wrappedSetValue = useMemo(() => { return async (newValue: OptionValue) => { + setValue(String(newValue)); await options.save(name, newValue); if (needsRefresh) { From 45dd47d039824c966653cc976fda367328c3bdcf Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 21:20:36 +0200 Subject: [PATCH 655/873] feat(layout): button to toggle right pane on vertical layout --- apps/client/src/layouts/desktop_layout.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index a4f60aa05a..e8a43955a6 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -118,8 +118,11 @@ export default class DesktopLayout { new FlexContainer("row") .child() .child(new TabRowWidget()) + .optChild(isNewLayout, ) .optChild(customTitleBarButtons, ) - .css("height", "40px")) + .css("height", "40px") + .css("align-items", "center") + ) .optChild(isNewLayout, ) .child( new FlexContainer("row") From 7d386c249af02c605693e0b07891a894dacd5f8d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 22:23:46 +0200 Subject: [PATCH 656/873] fix(right_pane_widget): toggle button clipped on desktop --- apps/client/src/layouts/desktop_layout.tsx | 1 + apps/client/src/stylesheets/style.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index e8a43955a6..601b0e643a 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -116,6 +116,7 @@ export default class DesktopLayout { .css("flex-grow", "1") .optChild(!fullWidthTabBar, new FlexContainer("row") + .class("tab-row-container") .child() .child(new TabRowWidget()) .optChild(isNewLayout, ) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index f10ace6947..5ea6776ff9 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1961,7 +1961,7 @@ body.electron.platform-darwin:not(.native-titlebar):not(.full-screen) #tab-row-l width: 80px; } -.tab-row-widget { +.tab-row-container { padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw)); } From 01d4fa8afdbf37be28838aa69cc50f55078b7436 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 22:26:17 +0200 Subject: [PATCH 657/873] chore(right_pane_widget): add padding to no items --- apps/client/src/widgets/sidebar/RightPanelContainer.css | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 9afce65e8f..3ec53abda4 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -40,6 +40,7 @@ body.experimental-feature-new-layout #right-pane { justify-content: center; flex-grow: 1; flex-direction: column; + padding: 0.75em; color: var(--muted-text-color); .bx { From 9d351ae479ddf6ea2e3ce1b4bfe51e3066f501d3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 22:33:26 +0200 Subject: [PATCH 658/873] chore(options/text_notes): adapt to new layout sidebar --- .../type_widgets/options/text_notes.tsx | 86 ++++++++++--------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/options/text_notes.tsx b/apps/client/src/widgets/type_widgets/options/text_notes.tsx index 6179b5bc7e..b17c2bb3dd 100644 --- a/apps/client/src/widgets/type_widgets/options/text_notes.tsx +++ b/apps/client/src/widgets/type_widgets/options/text_notes.tsx @@ -1,24 +1,28 @@ -import { useEffect, useMemo, useState } from "preact/hooks"; -import { t } from "../../../services/i18n"; -import FormCheckbox from "../../react/FormCheckbox"; -import FormRadioGroup from "../../react/FormRadioGroup"; -import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks"; -import OptionsSection from "./components/OptionsSection"; -import { formatDateTime, toggleBodyClass } from "../../../services/utils"; -import FormGroup from "../../react/FormGroup"; -import Column from "../../react/Column"; -import { FormSelectGroup, FormSelectWithGroups } from "../../react/FormSelect"; -import { Themes } from "@triliumnext/highlightjs"; -import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../services/syntax_highlight"; import { normalizeMimeTypeForCKEditor, type OptionNames } from "@triliumnext/commons"; -import { getHtml } from "../../react/RawHtml"; +import { Themes } from "@triliumnext/highlightjs"; import type { CSSProperties } from "preact/compat"; +import { useEffect, useMemo, useState } from "preact/hooks"; +import { Trans } from "react-i18next"; + +import { isExperimentalFeatureEnabled } from "../../../services/experimental_features"; +import { t } from "../../../services/i18n"; +import { ensureMimeTypesForHighlighting, loadHighlightingTheme } from "../../../services/syntax_highlight"; +import { formatDateTime, toggleBodyClass } from "../../../services/utils"; +import Column from "../../react/Column"; +import FormCheckbox from "../../react/FormCheckbox"; +import FormGroup from "../../react/FormGroup"; +import FormRadioGroup from "../../react/FormRadioGroup"; +import { FormSelectGroup, FormSelectWithGroups } from "../../react/FormSelect"; import FormText from "../../react/FormText"; import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox"; -import CheckboxList from "./components/CheckboxList"; +import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks"; import KeyboardShortcut from "../../react/KeyboardShortcut"; -import { Trans } from "react-i18next"; +import { getHtml } from "../../react/RawHtml"; import AutoReadOnlySize from "./components/AutoReadOnlySize"; +import CheckboxList from "./components/CheckboxList"; +import OptionsSection from "./components/OptionsSection"; + +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export default function TextNoteSettings() { return ( @@ -32,7 +36,7 @@ export default function TextNoteSettings() { - ) + ); } function FormattingToolbar() { @@ -65,7 +69,7 @@ function FormattingToolbar() { containerStyle={{ marginInlineStart: "1em" }} /> - ) + ); } function EditorFeatures() { @@ -119,7 +123,7 @@ function CodeBlockStyle() { for (const [ id, theme ] of Object.entries(Themes)) { const data: ThemeData = { - val: "default:" + id, + val: `default:${ id}`, title: theme.name }; @@ -177,7 +181,7 @@ function CodeBlockStyle() { - ) + ); } const SAMPLE_LANGUAGE = normalizeMimeTypeForCKEditor("application/javascript;env=frontend"); @@ -219,9 +223,9 @@ function CodeBlockPreview({ theme, wordWrap }: { theme: string, wordWrap: boolea const codeStyle = useMemo(() => { if (wordWrap) { return { whiteSpace: "pre-wrap" }; - } else { - return { whiteSpace: "pre"}; } + return { whiteSpace: "pre"}; + }, [ wordWrap ]); return ( @@ -230,7 +234,7 @@ function CodeBlockPreview({ theme, wordWrap }: { theme: string, wordWrap: boolea
    - ) + ); } interface ThemeData { @@ -241,7 +245,7 @@ interface ThemeData { function TableOfContent() { const [ minTocHeadings, setMinTocHeadings ] = useTriliumOption("minTocHeadings"); - return ( + return (!isNewLayout && {t("table_of_contents.description")} @@ -257,7 +261,7 @@ function TableOfContent() { {t("table_of_contents.disable_info")} {t("table_of_contents.shortcut_info")} - ) + ); } function HighlightsList() { @@ -277,13 +281,17 @@ function HighlightsList() { keyProperty="val" titleProperty="title" currentValue={highlightsList} onChange={setHighlightsList} /> -
    -
    {t("highlights_list.visibility_title")}
    - {t("highlights_list.visibility_description")} - {t("highlights_list.shortcut_info")} + {!isNewLayout && ( + <> +
    +
    {t("highlights_list.visibility_title")}
    + {t("highlights_list.visibility_description")} + {t("highlights_list.shortcut_info")} + + )} - ) + ); } function DateTimeFormatOptions() { @@ -302,19 +310,19 @@ function DateTimeFormatOptions() {
    - - + - + /> + - -
    - {formatDateTime(new Date(), customDateTimeFormat)} -
    -
    + +
    + {formatDateTime(new Date(), customDateTimeFormat)} +
    +
    - ) + ); } From 7a5d24f9688d0aafdf539884d54fa2c6a0ac759f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 23:02:32 +0200 Subject: [PATCH 659/873] feat(right_pane_widget): options modal for highlight list --- .../src/translations/en/translation.json | 3 +- .../src/widgets/sidebar/HighlightsList.tsx | 43 +++++++++++++++++-- .../widgets/sidebar/RightPanelContainer.css | 1 + .../type_widgets/options/text_notes.tsx | 30 ++++++++----- 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 077d24fac5..903cdc8b6f 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1723,7 +1723,8 @@ }, "highlights_list_2": { "title": "Highlights List", - "options": "Options" + "options": "Options", + "modal_title": "Configure Highlights List" }, "quick-search": { "placeholder": "Quick search", diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index c3621d6f5f..0eb3dcf03a 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -1,8 +1,12 @@ import { CKTextEditor, ModelText } from "@triliumnext/ckeditor5"; +import { createPortal } from "preact/compat"; import { useCallback, useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; +import ActionButton from "../react/ActionButton"; import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; +import Modal from "../react/Modal"; +import { HighlightsListOptions } from "../type_widgets/options/text_notes"; import RightPanelWidget from "./RightPanelWidget"; interface RawHighlight { @@ -21,12 +25,43 @@ export default function HighlightsList() { const { note, noteContext } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); const { isReadOnly } = useIsNoteReadOnly(note, noteContext); + const [ shown, setShown ] = useState(false); return ( - - {noteType === "text" && isReadOnly && } - {noteType === "text" && !isReadOnly && } - + <> + { + e.stopPropagation(); + setShown(true); + }} + /> + )} + > + {noteType === "text" && isReadOnly && } + {noteType === "text" && !isReadOnly && } + + {createPortal(, document.body)} + + ); +} + +function HighlightListOptionsModal({ shown, setShown }: { shown: boolean, setShown(value: boolean): void }) { + return ( + setShown(false)} + > + + ); } diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 3ec53abda4..595656c290 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -14,6 +14,7 @@ body.experimental-feature-new-layout #right-pane { .card-header-title { padding-inline: 0.5em; + flex-grow: 1; } } diff --git a/apps/client/src/widgets/type_widgets/options/text_notes.tsx b/apps/client/src/widgets/type_widgets/options/text_notes.tsx index b17c2bb3dd..7b6e15ddae 100644 --- a/apps/client/src/widgets/type_widgets/options/text_notes.tsx +++ b/apps/client/src/widgets/type_widgets/options/text_notes.tsx @@ -265,10 +265,27 @@ function TableOfContent() { } function HighlightsList() { + return ( + + + + {!isNewLayout && ( + <> +
    +
    {t("highlights_list.visibility_title")}
    + {t("highlights_list.visibility_description")} + {t("highlights_list.shortcut_info")} + + )} +
    + ); +} + +export function HighlightsListOptions() { const [ highlightsList, setHighlightsList ] = useTriliumOptionJson("highlightsList"); return ( - + <> {t("highlights_list.description")} - - {!isNewLayout && ( - <> -
    -
    {t("highlights_list.visibility_title")}
    - {t("highlights_list.visibility_description")} - {t("highlights_list.shortcut_info")} - - )} -
    + ); } From e94704ce64977dfb2dd14636d25f8b36d9d2edc6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 23:18:28 +0200 Subject: [PATCH 660/873] chore(right_pane_widget): respect highlight settings --- .../src/widgets/sidebar/HighlightsList.tsx | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 0eb3dcf03a..ed00d47ddf 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; import ActionButton from "../react/ActionButton"; -import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; +import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor, useTriliumOptionJson } from "../react/hooks"; import Modal from "../react/Modal"; import { HighlightsListOptions } from "../type_widgets/options/text_notes"; import RightPanelWidget from "./RightPanelWidget"; @@ -69,25 +69,39 @@ function AbstractHighlightsList({ highlights, scrollToHi highlights: T[], scrollToHighlight(highlight: T): void; }) { + const [ highlightsList ] = useTriliumOptionJson<["bold" | "italic" | "underline" | "color" | "bgColor"]>("highlightsList"); + const highlightsListSet = new Set(highlightsList || []); + return (
      - {highlights.map(highlight => ( -
    1. scrollToHighlight(highlight)} - > - {highlight.text} -
    2. - ))} + {highlights + .filter(highlight => { + const { attrs } = highlight; + return ( + (highlightsListSet.has("bold") && attrs.bold) || + (highlightsListSet.has("italic") && attrs.italic) || + (highlightsListSet.has("underline") && attrs.underline) || + (highlightsListSet.has("color") && !!attrs.color) || + (highlightsListSet.has("bgColor") && !!attrs.background) + ); + }) + .map(highlight => ( +
    3. scrollToHighlight(highlight)} + > + {highlight.text} +
    4. + ))}
    ); From c0cd9e36d9078fda9ce67468b0de6aae01dffdea Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 23:25:58 +0200 Subject: [PATCH 661/873] feat(right_pane_widget): hide highlights if disabled in settings --- apps/client/src/widgets/sidebar/RightPanelContainer.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index bb15ee10af..3422ebf36d 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -8,7 +8,7 @@ import { t } from "../../services/i18n"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; import Button from "../react/Button"; -import { useActiveNoteContext, useNoteProperty, useTriliumOptionBool } from "../react/hooks"; +import { useActiveNoteContext, useNoteProperty, useTriliumOptionBool, useTriliumOptionJson } from "../react/hooks"; import Icon from "../react/Icon"; import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; @@ -17,13 +17,14 @@ const MIN_WIDTH_PERCENT = 5; export default function RightPanelContainer() { const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible"); + const [ highlightsList ] = useTriliumOptionJson("highlightsList"); useSplit(rightPaneVisible); const { note } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); const items = (rightPaneVisible ? [ (noteType === "text" || noteType === "doc") && , - noteType === "text" && + noteType === "text" && highlightsList.length > 0 && ] : []).filter(Boolean); return ( From fad6414e1da671c700d9dff7af5bf45d8774c2d3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 23:29:52 +0200 Subject: [PATCH 662/873] feat(right_pane_widget): handle zero highlights --- .../src/translations/en/translation.json | 3 +- .../src/widgets/sidebar/HighlightsList.tsx | 33 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 903cdc8b6f..fc3bd58794 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1724,7 +1724,8 @@ "highlights_list_2": { "title": "Highlights List", "options": "Options", - "modal_title": "Configure Highlights List" + "modal_title": "Configure Highlights List", + "no_highlights": "No highlights found." }, "quick-search": { "placeholder": "Quick search", diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index ed00d47ddf..7c36053611 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -71,22 +71,22 @@ function AbstractHighlightsList({ highlights, scrollToHi }) { const [ highlightsList ] = useTriliumOptionJson<["bold" | "italic" | "underline" | "color" | "bgColor"]>("highlightsList"); const highlightsListSet = new Set(highlightsList || []); + const filteredHighlights = highlights.filter(highlight => { + const { attrs } = highlight; + return ( + (highlightsListSet.has("bold") && attrs.bold) || + (highlightsListSet.has("italic") && attrs.italic) || + (highlightsListSet.has("underline") && attrs.underline) || + (highlightsListSet.has("color") && !!attrs.color) || + (highlightsListSet.has("bgColor") && !!attrs.background) + ); + }); return ( -
      - {highlights - .filter(highlight => { - const { attrs } = highlight; - return ( - (highlightsListSet.has("bold") && attrs.bold) || - (highlightsListSet.has("italic") && attrs.italic) || - (highlightsListSet.has("underline") && attrs.underline) || - (highlightsListSet.has("color") && !!attrs.color) || - (highlightsListSet.has("bgColor") && !!attrs.background) - ); - }) - .map(highlight => ( + {filteredHighlights.length > 0 ? ( +
        + {filteredHighlights.map(highlight => (
      1. scrollToHighlight(highlight)} @@ -102,7 +102,12 @@ function AbstractHighlightsList({ highlights, scrollToHi >{highlight.text}
      2. ))} -
      +
    + ) : ( +
    + {t("highlights_list_2.no_highlights")} +
    + )}
    ); } From 6da42fac20c73567ceb235e1bcbd121f82f53bf8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 23:32:58 +0200 Subject: [PATCH 663/873] feat(right_pane_widget): handle zero headings --- apps/client/src/translations/en/translation.json | 3 ++- apps/client/src/widgets/sidebar/TableOfContents.tsx | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index fc3bd58794..458ea6b0c1 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1796,7 +1796,8 @@ }, "toc": { "table_of_contents": "Table of Contents", - "options": "Options" + "options": "Options", + "no_headings": "No headings." }, "watched_file_update_status": { "file_last_modified": "File has been last modified on .", diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 48c5217e99..b58c2d8c4a 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -40,9 +40,13 @@ function AbstractTableOfContents({ headings, scrollToHeadi const nestedHeadings = buildHeadingTree(headings); return ( -
      - {nestedHeadings.map(heading => )} -
    + {nestedHeadings.length > 0 ? ( +
      + {nestedHeadings.map(heading => )} +
    + ) : ( +
    {t("toc.no_headings")}
    + )}
    ); } From a5f322617d678a4febe5451b1064da6152de6a34 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 19 Dec 2025 23:44:19 +0200 Subject: [PATCH 664/873] chore(script): remove node-detail-pane --- apps/client/src/layouts/desktop_layout.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 601b0e643a..3191f5030d 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -172,10 +172,7 @@ export default class DesktopLayout { ) .child() .child(new FindWidget()) - .child( - ...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC - ...this.customWidgets.get("note-detail-pane") - ) + .child(...this.customWidgets.get("note-detail-pane")) ) ) .child(...this.customWidgets.get("center-pane")) From 8f1614f603d7b09dce5ce637242f9036e44eebcf Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 00:01:21 +0200 Subject: [PATCH 665/873] chore(right_pane_widget): basic support for custom widgets --- apps/client/src/layouts/desktop_layout.tsx | 2 +- .../src/widgets/sidebar/RightPanelContainer.tsx | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 3191f5030d..30b62a3b19 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -184,7 +184,7 @@ export default class DesktopLayout { .child(new HighlightsListWidget()) .child(...this.customWidgets.get("right-pane")) ) - .optChild(isNewLayout, ) + .optChild(isNewLayout, ) ) .optChild(!launcherPaneIsHorizontal && isNewLayout, ) ) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 3422ebf36d..85010e8508 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -7,15 +7,16 @@ import { useEffect } from "preact/hooks"; import { t } from "../../services/i18n"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; +import BasicWidget from "../basic_widget"; import Button from "../react/Button"; -import { useActiveNoteContext, useNoteProperty, useTriliumOptionBool, useTriliumOptionJson } from "../react/hooks"; +import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumOptionBool, useTriliumOptionJson } from "../react/hooks"; import Icon from "../react/Icon"; import HighlightsList from "./HighlightsList"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; -export default function RightPanelContainer() { +export default function RightPanelContainer({ customWidgets }: { customWidgets: BasicWidget[] }) { const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible"); const [ highlightsList ] = useTriliumOptionJson("highlightsList"); useSplit(rightPaneVisible); @@ -24,7 +25,8 @@ export default function RightPanelContainer() { const noteType = useNoteProperty(note, "type"); const items = (rightPaneVisible ? [ (noteType === "text" || noteType === "doc") && , - noteType === "text" && highlightsList.length > 0 && + noteType === "text" && highlightsList.length > 0 && , + ...customWidgets.map((w) => ) ] : []).filter(Boolean); return ( @@ -64,3 +66,8 @@ function useSplit(visible: boolean) { return () => splitInstance.destroy(); }, [ visible ]); } + +function CustomWidget({ originalWidget }: { originalWidget: BasicWidget }) { + const [ el ] = useLegacyWidget(() => originalWidget); + return <>{el}; +} From 0fa6335d0fd112d645d02993da02c47591d0fc96 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:58:53 +0000 Subject: [PATCH 666/873] chore(deps): update pnpm to v10.26.1 --- apps/build-docs/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json index 65eac6c280..a18162833a 100644 --- a/apps/build-docs/package.json +++ b/apps/build-docs/package.json @@ -9,7 +9,7 @@ "keywords": [], "author": "Elian Doran ", "license": "AGPL-3.0-only", - "packageManager": "pnpm@10.26.0", + "packageManager": "pnpm@10.26.1", "devDependencies": { "@redocly/cli": "2.13.0", "archiver": "7.0.1", diff --git a/package.json b/package.json index 1a8cf503e9..6e26b291f4 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "url": "https://github.com/TriliumNext/Trilium/issues" }, "homepage": "https://triliumnotes.org", - "packageManager": "pnpm@10.26.0", + "packageManager": "pnpm@10.26.1", "pnpm": { "patchedDependencies": { "@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch", From 489113f582b46da707c6e1a1918c59caf08a7caf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:59:47 +0000 Subject: [PATCH 667/873] chore(deps): update dependency @redocly/cli to v2.14.0 --- apps/build-docs/package.json | 2 +- pnpm-lock.yaml | 64 ++++++++++-------------------------- 2 files changed, 19 insertions(+), 47 deletions(-) diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json index 65eac6c280..c19a9fd966 100644 --- a/apps/build-docs/package.json +++ b/apps/build-docs/package.json @@ -11,7 +11,7 @@ "license": "AGPL-3.0-only", "packageManager": "pnpm@10.26.0", "devDependencies": { - "@redocly/cli": "2.13.0", + "@redocly/cli": "2.14.0", "archiver": "7.0.1", "fs-extra": "11.3.3", "react": "19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd337b7078..ec22fb7b49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,8 +134,8 @@ importers: apps/build-docs: devDependencies: '@redocly/cli': - specifier: 2.13.0 - version: 2.13.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5) + specifier: 2.14.0 + version: 2.14.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5) archiver: specifier: 7.0.1 version: 7.0.1 @@ -4398,27 +4398,27 @@ packages: '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.13.0': - resolution: {integrity: sha512-VOGh8p5gKy+u94SbvMGaHvDM6TPw668D9iQkNSztoi4T5sj3ZwM7Y8Z3yZnMqC5s5epDcLAMq4jCO8UVn5ZWHg==} + '@redocly/cli@2.14.0': + resolution: {integrity: sha512-LvVYV7KJGtVqltBc8Cbw2s4QpFOzend5nCsgR1JgWvHNt70f1AzqoHr5y7GO+3ThwumrTzPvjta+Ln+n3x5NmA==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true '@redocly/config@0.22.2': resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} - '@redocly/config@0.41.0': - resolution: {integrity: sha512-8yJ2e+ex8KVF25zijdpDbAEjyubk7NLfHsLI8h0MUnLEo2iEg6rTCDT9Qw71XDqd5UlXvfJb0Z0h6dd+Y6pWLw==} + '@redocly/config@0.41.1': + resolution: {integrity: sha512-LcMCzFbP/sqkCLSG3YswmeScP4fM5SjDCQizwa+psZ0PhYrKOMF7azZ6ZBkWs115uv5RfOk+jYAWLdKkZGGGXg==} '@redocly/openapi-core@1.34.5': resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.13.0': - resolution: {integrity: sha512-xQ4z5tsrXbIa4EfCniHv1zZ4etmQ0lpRcxy750iOamV5A/+19mgbPtD+UQCoT18puDAjcnOgpX7x2ha72qKrnw==} + '@redocly/openapi-core@2.14.0': + resolution: {integrity: sha512-GeSIesfbh5TdqoWBu7wPzCAGUvKfLBnN60rKnhZCyxrs6M0tn7GYhtET+P5HsNlXmvW4vFNDBlLDoATW/dKrrQ==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.13.0': - resolution: {integrity: sha512-35OidNXWkmmsJiwgX+tFw7FaU8usZVvZ/lFBFNJga65pivEvaDlfiwKxIRTzM4iuNbc2FRvP2q30dlGAztv0tg==} + '@redocly/respect-core@2.14.0': + resolution: {integrity: sha512-7HYB66oNUOcBjBZpK/i5xPpXIYXt09a98WX0subaAQZJinLGq8D3hbgLAp+pXZgosHNeZ0QKahOExsZ25JZSHw==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@replit/codemirror-indentation-markers@6.5.3': @@ -15079,8 +15079,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-upload': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-ai@47.3.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -15227,8 +15225,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -15421,8 +15417,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-classic@47.3.0': dependencies: @@ -15432,8 +15426,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-decoupled@47.3.0': dependencies: @@ -15443,8 +15435,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-inline@47.3.0': dependencies: @@ -15454,8 +15444,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-multi-root@47.3.0': dependencies: @@ -15562,8 +15550,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-font@47.3.0': dependencies: @@ -15638,8 +15624,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 '@ckeditor/ckeditor5-widget': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-html-embed@47.3.0': dependencies: @@ -15932,8 +15916,6 @@ snapshots: '@ckeditor/ckeditor5-paste-from-office': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-paste-from-office@47.3.0': dependencies: @@ -15941,8 +15923,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-engine': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-real-time-collaboration@47.3.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -15973,8 +15953,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-restricted-editing@47.3.0': dependencies: @@ -15984,8 +15962,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-revision-history@47.3.0': dependencies: @@ -16063,8 +16039,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-special-characters@47.3.0': dependencies: @@ -16074,8 +16048,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-style@47.3.0': dependencies: @@ -18873,14 +18845,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.13.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)': + '@redocly/cli@2.14.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.13.0(ajv@8.17.1) - '@redocly/respect-core': 2.13.0(ajv@8.17.1) + '@redocly/openapi-core': 2.14.0(ajv@8.17.1) + '@redocly/respect-core': 2.14.0(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -18914,7 +18886,7 @@ snapshots: '@redocly/config@0.22.2': {} - '@redocly/config@0.41.0': + '@redocly/config@0.41.1': dependencies: json-schema-to-ts: 2.7.2 @@ -18932,10 +18904,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.13.0(ajv@8.17.1)': + '@redocly/openapi-core@2.14.0(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 - '@redocly/config': 0.41.0 + '@redocly/config': 0.41.1 ajv-formats: 3.0.1(ajv@8.17.1) colorette: 1.4.0 js-levenshtein: 1.1.6 @@ -18946,12 +18918,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.13.0(ajv@8.17.1)': + '@redocly/respect-core@2.14.0(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.17.1 - '@redocly/openapi-core': 2.13.0(ajv@8.17.1) + '@redocly/openapi-core': 2.14.0(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 From ba242a61695a136734c340aeb1d7ba54590c93b2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 01:00:35 +0000 Subject: [PATCH 668/873] chore(deps): update dependency openai to v6.15.0 --- apps/server/package.json | 2 +- pnpm-lock.yaml | 36 +++++------------------------------- 2 files changed, 6 insertions(+), 32 deletions(-) diff --git a/apps/server/package.json b/apps/server/package.json index 8af8d202d8..32023eb0e4 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -111,7 +111,7 @@ "multer": "2.0.2", "normalize-strings": "1.1.1", "ollama": "0.6.3", - "openai": "6.14.0", + "openai": "6.15.0", "rand-token": "1.0.1", "safe-compare": "1.1.4", "sanitize-filename": "1.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd337b7078..60ba8f9711 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -730,8 +730,8 @@ importers: specifier: 0.6.3 version: 0.6.3 openai: - specifier: 6.14.0 - version: 6.14.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@4.1.12) + specifier: 6.15.0 + version: 6.15.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@4.1.12) rand-token: specifier: 1.0.1 version: 1.0.1 @@ -10929,8 +10929,8 @@ packages: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} - openai@6.14.0: - resolution: {integrity: sha512-ZPD9MG5/sPpyGZ0idRoDK0P5MWEMuXe0Max/S55vuvoxqyEVkN94m9jSpE3YgNgz3WoESFvozs57dxWqAco31w==} + openai@6.15.0: + resolution: {integrity: sha512-F1Lvs5BoVvmZtzkUEVyh8mDQPPFolq4F+xdsx/DO8Hee8YF3IGAlZqUIsF+DVGhqf4aU0a3bTghsxB6OIsRy1g==} hasBin: true peerDependencies: ws: ^8.18.0 @@ -15079,8 +15079,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-upload': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-ai@47.3.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -15227,8 +15225,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -15421,8 +15417,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-classic@47.3.0': dependencies: @@ -15432,8 +15426,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-decoupled@47.3.0': dependencies: @@ -15443,8 +15435,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-inline@47.3.0': dependencies: @@ -15562,8 +15552,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-font@47.3.0': dependencies: @@ -15638,8 +15626,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.3.0 '@ckeditor/ckeditor5-widget': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-html-embed@47.3.0': dependencies: @@ -15932,8 +15918,6 @@ snapshots: '@ckeditor/ckeditor5-paste-from-office': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-paste-from-office@47.3.0': dependencies: @@ -15941,8 +15925,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-engine': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-real-time-collaboration@47.3.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -15973,8 +15955,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-restricted-editing@47.3.0': dependencies: @@ -15984,8 +15964,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-revision-history@47.3.0': dependencies: @@ -16063,8 +16041,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-special-characters@47.3.0': dependencies: @@ -16074,8 +16050,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-style@47.3.0': dependencies: @@ -27190,7 +27164,7 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 - openai@6.14.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@4.1.12): + openai@6.15.0(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@4.1.12): optionalDependencies: ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) zod: 4.1.12 From 9caa058b18594d5d5fada38fdf001adbeeaf214a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 20 Dec 2025 08:01:59 +0100 Subject: [PATCH 669/873] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ --- docs/README-fr.md | 4 +- docs/README-ru.md | 175 ++++++++++++++++++++++++---------------------- 2 files changed, 92 insertions(+), 87 deletions(-) diff --git a/docs/README-fr.md b/docs/README-fr.md index 4bf6163c70..b4dd529a7f 100644 --- a/docs/README-fr.md +++ b/docs/README-fr.md @@ -133,8 +133,8 @@ Notre documentation est disponible sous plusieurs formats : une sauvegarde facile du contenu web * Interface utilisateur personnalisable (boutons de la barre latérale, widgets définis par l'utilisateur, ...) -* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), - along with a Grafana Dashboard. +* [Statistiques](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), + avec un tableau de bord Grafana. ✨ Consultez les ressources/communautés tierces suivantes pour plus de fonctionnalités liées à TriliumNext : diff --git a/docs/README-ru.md b/docs/README-ru.md index ee6dc748b7..500b7ed15c 100644 --- a/docs/README-ru.md +++ b/docs/README-ru.md @@ -51,8 +51,8 @@ Trilium Notes – это приложение для заметок с иера [docs.triliumnotes.org](https://docs.triliumnotes.org/) - **Справка в приложении**: Нажмите`F1` в Trilium для доступа к этой документации прямо в приложении -- **GitHub**: Navigate through the [User Guide](./User%20Guide/User%20Guide/) in - this repository +- **GitHub**: Ознакомьтесь с [Руководством + пользователя](./User%20Guide/User%20Guide/) в этом репозитории ### Важные Ссылки - [Руководство по началу работы](https://docs.triliumnotes.org/) @@ -98,24 +98,24 @@ Trilium Notes – это приложение для заметок с иера заметок со своим сервером * существуют [сторонние сервисы для хостинга сервера синхронизации](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) -* [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) - (publishing) notes to public internet +* [Публикация](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) + заметок в открытом доступе в Интернете * Надёжное [шифрование](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes) с детализацией по каждой заметке -* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type - "canvas") -* [Relation - maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and - [note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map) - for visualizing notes and their relations -* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/) -* [Geo maps](https://docs.triliumnotes.org/user-guide/collections/geomap) with - location pins and GPX tracks +* Рисование и скетчинг диаграм, при помощи [Excalidraw](https://excalidraw.com/) + (тип заметки "холст") +* [Карты + связей](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and + [карты заметок](https://docs.triliumnotes.org/user-guide/note-types/note-map) + для визуализации заметок и их связей +* Интеллект-карты на основе [Mind Elixir](https://docs.mind-elixir.com/) +* [Карты](https://docs.triliumnotes.org/user-guide/collections/geomap) с метками + для мест и треками GPX * [Скрипты](https://docs.triliumnotes.org/user-guide/scripts) - см. [продвинутые примеры](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases) -* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) for - automation +* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) для + автоматизации * Хорошо масштабируется, как по удобству использования, так и по производительности до 100000 заметок * Оптимизированный [мобильный @@ -129,101 +129,104 @@ Trilium Notes – это приложение для заметок с иера формате * [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) для удобного сохранения веб-контента -* Customizable UI (sidebar buttons, user-defined widgets, ...) -* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), - along with a Grafana Dashboard. +* Настраиваемый пользовательский интерфейс (кнопки боковой панели, + пользовательские виджеты и т. д.) +* [Метрики](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), а + также панель мониторинга Grafana. -✨ Check out the following third-party resources/communities for more TriliumNext -related goodies: +✨ Ознакомьтесь со следующими сторонними ресурсами/сообществами, чтобы найти +больше полезной информации о TriliumNext: -- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party - themes, scripts, plugins and more. -- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more. +- [awesome-trilium](https://github.com/Nriver/awesome-trilium) - сторонние темы, + скриптов, плагинов и многого другого. +- [TriliumRocks!](https://trilium.rocks/) — обучающие материалы, руководства и + многое другое. -## ❓Why TriliumNext? +## ❓Почему именно TriliumNext? -The original Trilium developer ([Zadam](https://github.com/zadam)) has -graciously given the Trilium repository to the community project which resides -at https://github.com/TriliumNext +Оригинальный разработчик Trilium ([Zadam](https://github.com/zadam)) любезно +предоставил репозиторий Trilium проекту сообщества, который находится по адресу +https://github.com/TriliumNext -### ⬆️Migrating from Zadam/Trilium? +### ⬆️Переходите с Zadam/Trilium? -There are no special migration steps to migrate from a zadam/Trilium instance to -a TriliumNext/Trilium instance. Simply [install -TriliumNext/Trilium](#-installation) as usual and it will use your existing -database. +Для миграции с экземпляра zadam/Trilium на экземпляр TriliumNext/Trilium не +требуется никаких дополнительных шагов. Просто [установите +TriliumNext/Trilium](#-installation) как обычно, и он будет использовать вашу +существующую базу данных. -Versions up to and including -[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are -compatible with the latest zadam/trilium version of -[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later -versions of TriliumNext/Trilium have their sync versions incremented which -prevents direct migration. +Версии до [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) +включительно совместимы с последней версией zadam/trilium +[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). В более +поздних версиях TriliumNext/Trilium версии схемы данных отличаются сильнее, что +препятствует прямой миграции. -## 💬 Discuss with us +## 💬 Обсудите с нами -Feel free to join our official conversations. We would love to hear what -features, suggestions, or issues you may have! +Приглашаем вас присоединиться к нашим официальным обсуждениям. Мы будем рады +узнать о ваших предложениях по улучшению, идеях или проблемах! -- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous - discussions.) - - The `General` Matrix room is also bridged to +- [Матрица](https://matrix.to/#/#triliumnext:matrix.org) (Для оперативной + коммуникации.) + - Комната `General` в Matrix также подключена к [XMPP](xmpp:discuss@trilium.thisgreat.party?join) -- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For - asynchronous discussions.) -- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug - reports and feature requests.) +- [Обсуждения на Github](https://github.com/TriliumNext/Trilium/discussions) + (Для асинхронных обсуждений.) +- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (Для сообщений + об ошибках и запросов на добавление новых функций.) ## 🏗 Сборки ### Windows / macOS -Download the binary release for your platform from the [latest release -page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package -and run the `trilium` executable. +Загрузите бинарный релиз для вашей платформы с [страницы последнего +релиза](https://github.com/TriliumNext/Trilium/releases/latest), распакуйте +пакет и запустите исполняемый файл `trilium`. ### Linux -If your distribution is listed in the table below, use your distribution's -package. +Если ваш дистрибутив указан в таблице ниже, используйте пакет, соответствующий +вашему дистрибутиву. -[![Packaging -status](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions) +[![Доступность](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions) -You may also download the binary release for your platform from the [latest -release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the -package and run the `trilium` executable. +Вы также можете загрузить бинарный релиз для вашей платформы со [страницы +последнего релиза](https://github.com/TriliumNext/Trilium/releases/latest), +распаковать пакет и запустить исполняемый файл `trilium`. -TriliumNext is also provided as a Flatpak, but not yet published on FlatHub. +TriliumNext также предоставляется в виде Flatpak-пакета, но пока не опубликован +на FlatHub. -### Browser (any OS) +### Браузер (любая ОС) -If you use a server installation (see below), you can directly access the web -interface (which is almost identical to the desktop app). +Если вы используете серверную установку (см. ниже), вы можете получить прямой +доступ к веб-интерфейсу (который практически идентичен настольному приложению). -Currently only the latest versions of Chrome & Firefox are supported (and -tested). +В настоящее время поддерживаются (и протестированы) только последние версии +Chrome и Firefox. -### Mobile +### Мобильная версия -To use TriliumNext on a mobile device, you can use a mobile web browser to -access the mobile interface of a server installation (see below). +Для использования TriliumNext на мобильном устройстве вы можете использовать +мобильный веб-браузер для доступа к мобильному интерфейсу сервера (см. ниже). -See issue https://github.com/TriliumNext/Trilium/issues/4962 for more -information on mobile app support. +Дополнительную информацию о поддержке мобильных приложений см. в треде +https://github.com/TriliumNext/Trilium/issues/4962. -If you prefer a native Android app, you can use +Если вы предпочитаете нативное приложение для Android, вы можете использовать [TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid). -Report bugs and missing features at [their -repository](https://github.com/FliegendeWurst/TriliumDroid). Note: It is best to -disable automatic updates on your server installation (see below) when using -TriliumDroid since the sync version must match between Trilium and TriliumDroid. +Сообщайте об ошибках и недостающих функциях в [их +репозитории](https://github.com/FliegendeWurst/TriliumDroid). Примечание: при +использовании TriliumDroid лучше отключить автоматические обновления на вашем +сервере (см. ниже), поскольку синхронизированные версии должны совпадать между +Trilium и TriliumDroid. -### Server +### Сервер -To install TriliumNext on your own server (including via Docker from -[Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server -installation docs](https://docs.triliumnotes.org/user-guide/setup/server). +Чтобы установить TriliumNext на свой собственный сервер (в том числе через +Docker из [Dockerhub](https://hub.docker.com/r/triliumnext/trilium)), следуйте +[документации по установке +сервера](https://docs.triliumnotes.org/user-guide/setup/server). ## 💻 Участвуйте в разработке @@ -275,10 +278,11 @@ pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32 ### Документация для разработчиков -Please view the [documentation -guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) -for details. If you have more questions, feel free to reach out via the links -described in the "Discuss with us" section above. +Пожалуйста, ознакомьтесь с +[руководством](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) +для получения подробной информации. Если у вас возникнут дополнительные вопросы, +вы можете связаться с нами, используя ссылки, указанные в разделе «Обсудите с +нами» выше. ## 👏 Благодарности @@ -287,7 +291,8 @@ described in the "Discuss with us" section above. * [Sarah Hussein](https://github.com/Sarah-Hussein) за создание иконки приложения. * [nriver](https://github.com/nriver) за работу по интернационализации. -* [Thomas Frei](https://github.com/thfrei) for his original work on the Canvas. +* [Thomas Frei](https://github.com/thfrei) за его оригинальную работу над + Холстом. * [antoniotejada](https://github.com/nriver) за оригинальный виджет подсветки синтаксиса. * [Dosu](https://dosu.dev/) за обеспечение автоматических ответов на вопросы и @@ -302,7 +307,7 @@ Trilium не существовал бы без технологий, лежащ * [CodeMirror](https://github.com/codemirror/CodeMirror) - редактор кода с поддержкой огромного количества языков. * [Excalidraw](https://github.com/excalidraw/excalidraw) - бесконечная белая - доска, используемая в заметках Canvas. + доска, используемая в заметках типа "Холст". * [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - обеспечивает функционирование ментальной карты. * [Leaflet](https://github.com/Leaflet/Leaflet) - отображение географических @@ -327,7 +332,7 @@ Trilium не существовал бы без технологий, лежащ Вы также можете поддержать главного разработчика приложения ([eliandoran](https://github.com/eliandoran)) с помощью: -- [GitHub Sponsors](https://github.com/sponsors/eliandoran) +- [Спонсоры GitHub](https://github.com/sponsors/eliandoran) - [PayPal](https://paypal.me/eliandoran) - [Buy Me a Coffee](https://buymeacoffee.com/eliandoran) From 457d30cd80ce808c9e47ad46fc1517486a1cedb6 Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 19 Dec 2025 17:43:25 +0100 Subject: [PATCH 670/873] Translated using Weblate (Russian) Currently translated at 100.0% (1709 of 1709 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/ --- .../src/translations/ru/translation.json | 78 +++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/apps/client/src/translations/ru/translation.json b/apps/client/src/translations/ru/translation.json index 4c41a126e1..e030abbb3c 100644 --- a/apps/client/src/translations/ru/translation.json +++ b/apps/client/src/translations/ru/translation.json @@ -39,7 +39,10 @@ "edit_branch_prefix": "Редактировать префикс ветки", "prefix": "Префикс: ", "branch_prefix_saved": "Префикс ветки сохранен.", - "help_on_tree_prefix": "Помощь по префиксу дерева" + "help_on_tree_prefix": "Помощь по префиксу дерева", + "affected_branches": "Затронутые ветки ({{count}}):", + "branch_prefix_saved_multiple": "Префикс сохранен для {{count}} ветвей.", + "edit_branch_prefix_multiple": "Изменить префикс для {{count}} ветвей" }, "bulk_actions": { "available_actions": "Доступные действия", @@ -236,7 +239,8 @@ "export_status": "Статус экспорта", "export_in_progress": "Экспорт: {{progressCount}}", "export_finished_successfully": "Экспорт завершился успешно.", - "format_pdf": "PDF - для печати или обмена." + "format_pdf": "PDF - для печати или обмена.", + "share-format": "HTML для веб-публикаций — использует ту же тему оформления, что и общие заметки, но может быть опубликован как статический веб-сайт." }, "help": { "noteNavigation": "Навигация по заметке", @@ -290,7 +294,8 @@ "blockQuote": "начните строку с >, а затем пробела для блока цитаты", "quickSearch": "сфокусироваться на поле ввода быстрого поиска", "editNoteTitle": "в области дерева переключится с области дерева на заголовок заметки. Сочетание клавиш Enter из области заголовка заметки переключит фокус на текстовый редактор. Ctrl+. переключит обратно с редактора на область дерева.", - "title": "Справка" + "title": "Справка", + "editShortcuts": "Редактировать сочетания клавиш" }, "modal": { "close": "Закрыть", @@ -682,7 +687,8 @@ "open_note_in_popup": "Быстрое редактирование", "open_note_in_new_window": "Открыть заметку в новом окне", "open_note_in_new_tab": "Открыть заметку в новой вкладке", - "open_note_in_new_split": "Открыть заметку в новой панели" + "open_note_in_new_split": "Открыть заметку в новой панели", + "open_note_in_other_split": "Открыть заметку в другой панели" }, "image_context_menu": { "copy_image_to_clipboard": "Копировать изображение в буфер обмена", @@ -760,7 +766,8 @@ "saved-search-note-refreshed": "Сохраненная поисковая заметка обновлена.", "refresh-saved-search-results": "Обновить сохраненные результаты поиска", "automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве.", - "toggle-sidebar": "Переключить боковую панель" + "toggle-sidebar": "Переключить боковую панель", + "dropping-not-allowed": "Перетаскивание заметок в эту область не разрешено." }, "quick-search": { "no-results": "Результаты не найдены", @@ -797,7 +804,7 @@ "text": "Текст", "launcher": "Лаунчер", "doc": "Документация", - "relation-map": "Карта отношений", + "relation-map": "Карта связей", "note-map": "Карта заметок", "render-note": "Рендеринг заметки", "web-view": "Веб-страница", @@ -845,7 +852,8 @@ "info": { "closeButton": "Закрыть", "okButton": "ОК", - "modalTitle": "Информация" + "modalTitle": "Информация", + "copy_to_clipboard": "Скопировать в буфер обмена" }, "jump_to_note": { "search_placeholder": "Найдите заметку по ее названию или введите > для команд...", @@ -982,7 +990,8 @@ "show_shared_notes_subtree": "Поддерево общедоступных заметок", "switch_to_mobile_version": "Перейти на мобильную версию", "switch_to_desktop_version": "Переключиться на версию для ПК", - "new-version-available": "Доступно обновление" + "new-version-available": "Доступно обновление", + "download-update": "Обновить до {{latestVersion}}" }, "zpetne_odkazy": { "relation": "отношение", @@ -1016,7 +1025,12 @@ "geo-map": "Карта", "invalid_view_type": "Недопустимый тип представления '{{type}}'", "collapse_all_notes": "Свернуть все заметки", - "include_archived_notes": "Показать заархивированные заметки" + "include_archived_notes": "Показать заархивированные заметки", + "presentation": "Презентация", + "expand_all_levels": "Развернуть все вложенные уровни", + "expand_nth_level": "Развернуть уровни: {{depth}} шт.", + "expand_first_level": "Развернуть прямые дочерние уровни", + "expand_tooltip": "Разщвернуть дочерние элементы этой коллекции (на один уровень вложенности). Для получения дополнительных параметров нажмите стрелку справа." }, "edited_notes": { "deleted": "(удалено)", @@ -1056,7 +1070,9 @@ "title": "Информация", "calculate": "подсчитать", "note_size_info": "Размер заметки позволяет приблизительно оценить требования к объёму хранилища для данной заметки. Он учитывает её содержание и содержание её сохраненных версий.", - "subtree_size": "(размер поддерева: {{size}} в {{count}} заметках)" + "subtree_size": "(размер поддерева: {{size}} в {{count}} заметках)", + "mime": "MIME тип", + "show_similar_notes": "Похожие заметки" }, "note_paths": { "search": "Поиск", @@ -1109,7 +1125,8 @@ "save_to_note": "Сохранить в заметку", "search_note_saved": "Заметка с настройкой поиска сохранена в {{- notePathTitle}}", "unknown_search_option": "Неизвестный параметр поиска {{searchOptionName}}", - "actions_executed": "Действия выполнены." + "actions_executed": "Действия выполнены.", + "view_options": "Просмотреть опции:" }, "ancestor": { "depth_label": "глубина", @@ -1620,7 +1637,14 @@ "convert_into_attachment_failed": "Не удалось преобразовать заметку '{{title}}'.", "open_note_externally_title": "Файл будет открыт во внешнем приложении и отслеживается на наличие изменений. После этого вы сможете загрузить изменённую версию обратно в Trilium.", "open_note_externally": "Открыть заметку вне приложения", - "open_note_custom": "Открыть заметку как..." + "open_note_custom": "Открыть заметку как...", + "export_as_image_svg": "SVG (вектор)", + "export_as_image_png": "PNG (растр)", + "export_as_image": "Экспорт изображения", + "open_note_on_server": "Открыть заметку на сервере", + "view_revisions": "История изменений...", + "note_map": "Карта заметок", + "advanced": "Дополнительно" }, "revisions_button": { "note_revisions": "Версии заметки" @@ -1645,7 +1669,7 @@ "zoom_in_title": "Увеличить масштаб", "zoom_out_title": "Уменьшить масштаб", "reset_pan_zoom_title": "Сбросить панорамирование и масштабирование", - "create_child_note_title": "Создать новую дочернюю заметку и добавить ее в эту карту отношений" + "create_child_note_title": "Создать новую дочернюю заметку и добавить ее в эту карту связей" }, "code_auto_read_only_size": { "unit": "символов", @@ -1700,7 +1724,7 @@ "remove_relation": "Удалить отношение", "default_new_note_title": "новая заметка", "open_in_new_tab": "Открыть в новой вкладке", - "confirm_remove_relation": "Вы уверены, что хотите удалить отношение?", + "confirm_remove_relation": "Вы уверены, что хотите удалить связь?", "enter_new_title": "Введите новое название заметки:", "note_not_found": "Заметка {{noteId}} не найдена!", "cannot_match_transform": "Невозможно сопоставить преобразование: {{transform}}", @@ -1708,7 +1732,7 @@ "click_on_canvas_to_place_new_note": "Щелкните по холсту, чтобы разместить новую заметку", "note_already_in_diagram": "Заметка \"{{title}}\" уже есть на диаграмме.", "connection_exists": "Связь '{{name}}' между этими заметками уже существует.", - "specify_new_relation_name": "Укажите новое имя отношения (допустимые символы: буквы, цифры, двоеточие и подчеркивание):", + "specify_new_relation_name": "Укажите новое имя связи (допустимые символы: буквы, цифры, двоеточие и подчеркивание):", "start_dragging_relations": "Начните перетягивать отношения отсюда на другую заметку." }, "vacuum_database": { @@ -1796,7 +1820,8 @@ "error_unrecognized_command": "Нераспознанная команда {{command}}", "error_cannot_get_branch_id": "Невозможно получить branchId для notePath '{{notePath}}'", "delete_this_note": "Удалить эту заметку", - "insert_child_note": "Вставить дочернюю заметку" + "insert_child_note": "Вставить дочернюю заметку", + "note_revisions": "История изменений" }, "svg_export_button": { "button_title": "Экспортировать диаграмму как SVG" @@ -1867,7 +1892,9 @@ }, "code_mime_types": { "title": "Доступные типы в выпадающем списке", - "tooltip_syntax_highlighting": "Подсветка синтаксиса" + "tooltip_syntax_highlighting": "Подсветка синтаксиса", + "tooltip_code_note_syntax": "Заметки с кодом", + "tooltip_code_block_syntax": "Блоки кода в текстовых заметках" }, "search_result": { "no_notes_found": "По заданным параметрам поиска заметки не найдены.", @@ -1997,7 +2024,7 @@ "note_type_switcher_others": "Другой тип заметки", "note_type_switcher_label": "Переключить с {{type}} на:", "last_modified": "Изменена ", - "created_on": "Создана" + "created_on": "Создана в " }, "units": { "percentage": "%" @@ -2053,7 +2080,9 @@ "keeps-crashing": "Компонент редактирования вылетает. Пожалуйста, попробуйте перезапустить Trilium. Если проблема сохраняется, пожалуйста, создайте отчет об ошибке.", "editor_crashed_details_title": "Техническая информация", "editor_crashed_details_intro": "Если эта ошибка возникает несколько раз, пожалуйста, сообщите о ней на GitHub, сопроводив информаций ниже.", - "editor_crashed_content": "Ваши данные были успешно восстановлены, но некоторые из последних изменений могли не быть сохранены." + "editor_crashed_content": "Ваши данные были успешно восстановлены, но некоторые из последних изменений могли не быть сохранены.", + "editor_crashed_details_button": "Подробнее...", + "editor_crashed_title": "Возникла ошибка в текстовом редакторе" }, "hoisted_note": { "confirm_unhoisting": "Запрошенная заметка «{{requestedNote}}» находится за пределами поддерева закрепленной заметки \"{{hoistedNote}}\", и для доступа к ней необходимо снять фокус. Снять фокус с заметки?" @@ -2128,7 +2157,7 @@ "read_only_temporarily_disabled": "Временное редактирование", "read_only_auto_description": "Эта заметка была автоматически переведена в режим только для чтения по соображениям производительности. Это автоматическое ограничение можно изменить в настройках.\n\nНажмите, чтобы временно отредактировать её.", "read_only_auto": "Автоматический режим \"только для чтения\"", - "read_only_explicit_description": "Эта заметка была вручную установлена в режим «только для чтения».\n\nНажмите, чтобы временно отредактировать её.", + "read_only_explicit_description": "Эта заметка была вручную установлена в режим «только для чтения».\nНажмите, чтобы временно отредактировать её.", "read_only_explicit": "Только для чтения" }, "breadcrumb": { @@ -2158,16 +2187,21 @@ }, "presentation_view": { "start-presentation": "Начать презентацию", - "edit-slide": "Редактировать слайд" + "edit-slide": "Редактировать слайд", + "slide-overview": "Переключить общий просмотр слайдов" }, "read-only-info": { "edit-note": "Изменить заметку", - "auto-read-only-note": "Заметка отображена в режиме \"только для чтения\" для быстрой загрузки" + "auto-read-only-note": "Заметка отображена в режиме \"только для чтения\" для быстрой загрузки.", + "read-only-note": "Заметка отображается в режиме \"только для чтения\"." }, "experimental_features": { "new_layout_description": "Попробуйте новый современный и удобный дизайн. В будущих обновлениях возможны его существенные изменения.", "new_layout_name": "Новый дизайн", "title": "Экспериментальные параметры", "disclaimer": "Эти параметры экспериментальные и могут повлиять на стабильность. Используйте с осторожностью." + }, + "popup-editor": { + "maximize": "Переключить на полный редактор" } } From 422c391c82cbbaad6a69f8356d782414f9929589 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 19 Dec 2025 18:17:20 +0100 Subject: [PATCH 671/873] Translated using Weblate (French) Currently translated at 95.9% (1640 of 1709 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/fr/ --- .../src/translations/fr/translation.json | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/apps/client/src/translations/fr/translation.json b/apps/client/src/translations/fr/translation.json index 56e55511e1..8a965d5530 100644 --- a/apps/client/src/translations/fr/translation.json +++ b/apps/client/src/translations/fr/translation.json @@ -107,7 +107,8 @@ "export_status": "Statut d'exportation", "export_in_progress": "Exportation en cours : {{progressCount}}", "export_finished_successfully": "L'exportation s'est terminée avec succès.", - "format_pdf": "PDF - pour l'impression ou le partage de documents." + "format_pdf": "PDF - pour l'impression ou le partage de documents.", + "share-format": "HTML pour la publication Web - utilise le même thème que celui utilisé pour les notes partagées, mais peut être publié sous forme de site Web statique." }, "help": { "noteNavigation": "Navigation dans les notes", @@ -161,7 +162,8 @@ "quickSearch": "aller à la recherche rapide", "inPageSearch": "recherche sur la page", "title": "Aide-mémoire", - "newTabWithActivationNoteLink": "Lorsqu’on clique sur un lien de note, celle-ci s’ouvre et devient active dans un nouvel onglet" + "newTabWithActivationNoteLink": "Lorsqu’on clique sur un lien de note, celle-ci s’ouvre et devient active dans un nouvel onglet", + "editShortcuts": "Modifier les raccourcis clavier" }, "import": { "importIntoNote": "Importer dans la note", @@ -203,7 +205,8 @@ "info": { "modalTitle": "Message d'information", "closeButton": "Fermer", - "okButton": "OK" + "okButton": "OK", + "copy_to_clipboard": "Copier dans le presse-papiers" }, "jump_to_note": { "search_button": "Rechercher dans le texte intégral", @@ -689,7 +692,13 @@ "convert_into_attachment_failed": "La conversion de la note '{{title}}' a échoué.", "convert_into_attachment_successful": "La note '{{title}}' a été convertie en pièce jointe.", "convert_into_attachment_prompt": "Êtes-vous sûr de vouloir convertir la note '{{title}}' en une pièce jointe de la note parente ?", - "print_pdf": "Exporter en PDF..." + "print_pdf": "Exporter en PDF...", + "open_note_on_server": "Ouvrir la note sur le serveur", + "view_revisions": "Révisions...", + "advanced": "Avancé", + "export_as_image": "Exporter en tant qu'image", + "export_as_image_png": "PNG", + "export_as_image_svg": "SVG (vectoriel)" }, "onclick_button": { "no_click_handler": "Le widget bouton '{{componentId}}' n'a pas de gestionnaire de clic défini" @@ -772,7 +781,11 @@ "geo-map": "Carte géographique", "board": "Tableau de bord", "include_archived_notes": "Afficher les notes archivées", - "presentation": "Présentation" + "presentation": "Présentation", + "expand_tooltip": "Développe les éléments enfants directs de cette collection (à un niveau). Pour plus d'options, appuyez sur la flèche à droite.", + "expand_first_level": "Développer les enfants directs", + "expand_nth_level": "Développer sur {{depth}} niveaux", + "expand_all_levels": "Développer tous les niveaux" }, "edited_notes": { "no_edited_notes_found": "Aucune note modifiée ce jour-là...", @@ -816,7 +829,9 @@ "note_size_info": "La taille de la note fournit une estimation approximative des besoins de stockage pour cette note. Il prend en compte le contenu de la note et de ses versions.", "calculate": "calculer", "subtree_size": "(taille du sous-arbre : {{size}} pour {{count}} notes)", - "title": "Infos sur la Note" + "title": "Infos sur la Note", + "mime": "type MIME", + "show_similar_notes": "Afficher des notes similaires" }, "note_map": { "open_full": "Développer au maximum", @@ -879,7 +894,8 @@ "search_parameters": "Paramètres de recherche", "unknown_search_option": "Option de recherche inconnue {{searchOptionName}}", "search_note_saved": "La note de recherche a été enregistrée dans {{- notePathTitle}}", - "actions_executed": "Les actions ont été exécutées." + "actions_executed": "Les actions ont été exécutées.", + "view_options": "Afficher les options:" }, "similar_notes": { "title": "Notes similaires", @@ -982,7 +998,13 @@ }, "editable_text": { "placeholder": "Saisir le contenu de votre note ici...", - "auto-detect-language": "Détecté automatiquement" + "auto-detect-language": "Détecté automatiquement", + "editor_crashed_title": "L'éditeur de texte a cessé de fonctionner", + "editor_crashed_content": "Votre contenu a été récupéré avec succès, mais certaines de vos modifications les plus récentes n'ont peut-être pas été enregistrées.", + "editor_crashed_details_button": "Afficher plus de détails...", + "editor_crashed_details_intro": "Si cette erreur se produit plusieurs fois, pensez à la signaler sur GitHub en collant les informations ci-dessous.", + "editor_crashed_details_title": "Informations techniques", + "keeps-crashing": "Le composant d'édition cesse de fonctionner. Veuillez essayer de redémarrer Trilium. Si le problème persiste, envisager de créer un rapport de bogue." }, "empty": { "open_note_instruction": "Ouvrez une note en tapant son titre dans la zone ci-dessous ou choisissez une note dans l'arborescence.", @@ -1110,7 +1132,8 @@ "title": "Largeur du contenu", "default_description": "Trilium limite par défaut la largeur maximale du contenu pour améliorer la lisibilité sur des écrans larges.", "max_width_label": "Largeur maximale du contenu en pixels", - "max_width_unit": "Pixels" + "max_width_unit": "Pixels", + "centerContent": "Garder le contenu centré" }, "native_title_bar": { "title": "Barre de titre native (nécessite le redémarrage de l'application)", @@ -1149,7 +1172,10 @@ "unit": "caractères" }, "code_mime_types": { - "title": "Types MIME disponibles dans la liste déroulante" + "title": "Types MIME disponibles dans la liste déroulante", + "tooltip_syntax_highlighting": "Souligner la syntaxe", + "tooltip_code_block_syntax": "Blocs de code dans les notes de texte", + "tooltip_code_note_syntax": "Notes de code" }, "vim_key_bindings": { "use_vim_keybindings_in_code_notes": "Raccourcis clavier Vim", @@ -2074,5 +2100,15 @@ "note_completion_description": "Si cette option est activée, des liens vers des notes peuvent être créés en tapant `@` suivi du titre d'une note.", "slash_commands_enabled": "Activer les commandes slash", "slash_commands_description": "Si cette option est activée, les commandes d'édition telles que l'insertion de sauts de ligne ou d'en-têtes peuvent être activées en tapant `/`." + }, + "experimental_features": { + "title": "Options expérimentales", + "disclaimer": "Ces options sont expérimentales et peuvent provoquer une instabilité. Utilisez avec prudence.", + "new_layout_name": "Nouvelle mise en page", + "new_layout_description": "Essayez la nouvelle mise en page pour un look plus moderne et un usage améliorée. Sous réserve de changements importants dans les prochaines versions." + }, + "read-only-info": { + "read-only-note": "Vous consultez actuellement une note en lecture seule.", + "auto-read-only-note": "Cette note s'affiche en mode lecture seule pour un chargement plus rapide." } } From 60572a28ff4456f7b93e89b7b3dc45282861a5f7 Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 19 Dec 2025 17:34:33 +0100 Subject: [PATCH 672/873] Translated using Weblate (Russian) Currently translated at 100.0% (389 of 389 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/ --- .../src/assets/translations/ru/server.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/server/src/assets/translations/ru/server.json b/apps/server/src/assets/translations/ru/server.json index e5d275ba14..d6a52f501a 100644 --- a/apps/server/src/assets/translations/ru/server.json +++ b/apps/server/src/assets/translations/ru/server.json @@ -156,7 +156,9 @@ "go-to-previous-note-title": "К предыдущей заметке", "go-to-next-note-title": "К следующей заметке", "open-today-journal-note-title": "Открыть сегодняшнюю заметку в журнале", - "llm-chat-title": "ИИ чат с заметками" + "llm-chat-title": "ИИ чат с заметками", + "zen-mode": "Режим \"Дзен\"", + "command-palette": "Открыть панель команд" }, "tray": { "bookmarks": "Закладки", @@ -363,7 +365,7 @@ "calendar": "Календарь", "table": "Таблица", "geolocation": "Геолокация", - "board": "Доска", + "board": "Канбан-доска", "status": "Статус", "board_status_done": "Готово", "text-snippet": "Фрагмент текста", @@ -379,7 +381,12 @@ "board_note_second": "Вторая заметка", "board_note_third": "Третья заметка", "board_status_todo": "Сделать", - "board_status_progress": "В процессе" + "board_status_progress": "В процессе", + "background": "Фон", + "presentation_slide_second": "Второй слайд", + "presentation_slide_first": "Первый слайд", + "presentation": "Презентация", + "presentation_slide": "Слайд презентации" }, "share_404": { "title": "Не найдено", @@ -411,7 +418,8 @@ "export_filter": "Документ PDF (*.pdf)", "unable-to-save-message": "Не удалось записать выбранный файл. Попробуйте ещё раз или выберите другой путь.", "unable-to-export-message": "Текущую заметку невозможно экспортировать в формате PDF.", - "unable-to-export-title": "Невозможно экспортировать в PDF" + "unable-to-export-title": "Невозможно экспортировать в PDF", + "unable-to-print": "Не удается распечатать заметку" }, "migration": { "wrong_db_version": "Версия базы данных ({{version}}) новее, чем та, которую ожидает приложение ({{targetVersion}}). Это означает, что она была создана более новой и несовместимой версией Trilium. Для решения этой проблемы обновите Trilium до последней версии.", From 0be5581fe5652ffa8d42d6da1d82156196cfd8e4 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 19 Dec 2025 18:09:17 +0100 Subject: [PATCH 673/873] Translated using Weblate (French) Currently translated at 99.7% (388 of 389 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/fr/ --- apps/server/src/assets/translations/fr/server.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/fr/server.json b/apps/server/src/assets/translations/fr/server.json index b4ff71af73..50f47a5d01 100644 --- a/apps/server/src/assets/translations/fr/server.json +++ b/apps/server/src/assets/translations/fr/server.json @@ -256,7 +256,9 @@ "multi-factor-authentication-title": "MFA", "ai-llm-title": "AI/LLM", "localization": "Langue et région", - "inbox-title": "Boîte de réception" + "inbox-title": "Boîte de réception", + "command-palette": "Ouvrir la palette de commandes", + "zen-mode": "Mode Zen" }, "notes": { "new-note": "Nouvelle note", From d3299d8aa42592ad1682651a05587f6a237c59c0 Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 19 Dec 2025 17:35:59 +0100 Subject: [PATCH 674/873] Translated using Weblate (Russian) Currently translated at 100.0% (152 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/ --- .../src/translations/ru/translation.json | 183 ++++++++++++++++-- 1 file changed, 172 insertions(+), 11 deletions(-) diff --git a/apps/website/src/translations/ru/translation.json b/apps/website/src/translations/ru/translation.json index 854fa8a883..2554c32478 100644 --- a/apps/website/src/translations/ru/translation.json +++ b/apps/website/src/translations/ru/translation.json @@ -1,14 +1,14 @@ { "get-started": { - "title": "Для начала", - "desktop_title": "Установите приложение для ПК (v{{version}})", + "title": "Начало работы", + "desktop_title": "Скачать приложение (v{{version}})", "architecture": "Архитектура:", "older_releases": "См. старые релизы", "server_title": "Настройка сервера для работы с нескольких устройств" }, "hero_section": { "title": "Упорядочите свои мысли. Создайте личную базу знаний.", - "subtitle": "Trilium - это open-source решение для ведение заметок и организации личной базы знаний. Используйте его локально на своём ПК, или синхронизируйтесь с собственным сервером, чтобы ваши заметки всегда были с вами.", + "subtitle": "Trilium - это open-source решение для заметок и организации личной базы знаний. Используйте его локально, или синхронизируйтесь с собственным сервером, чтобы ваши заметки всегда были с вами.", "get_started": "Начало работы", "github": "GitHub", "dockerhub": "Docker Hub", @@ -17,23 +17,184 @@ "organization_benefits": { "title": "Структура", "note_structure_title": "Структура заметки", - "note_structure_description": "Строки могут распологаться иерархически. Не нужно постоянно создавать папки, так как каждая заметка может содержать вложенные под-заметки. Одну и ту же заметку можно добавить сразу в несколько мест в иерархии.", + "note_structure_description": "Строки могут располагаться иерархически. Не нужно создавать папки, так как каждая заметка сама может содержать вложенные заметки. Одну и ту же заметку можно разместить сразу в нескольких местах в иерархии.", "attributes_title": "Ярлыки и связи заметок", - "hoisting_title": "Рабочие пространства и фокус на заметках", + "hoisting_title": "Рабочие пространства и режим фокуса", "hoisting_description": "Легко разделяйте заметки на личные и рабочие, группируя их в рабочей области. Благодаря этому в вашем дереве будет отображаться только определённый набор заметок.", - "attributes_description": "Используйте связи между заметками или добавляйте метки для удобной категоризации. Используйте расширенные атрибуты для ввода структурированной информации, которую можно использовать в таблицах и досках." + "attributes_description": "Устанавливайте связи между заметками или добавляйте метки для удобной категоризации. Используйте расширенные атрибуты для структуризации информации, которую можно использовать в таблицах и досках." }, "productivity_benefits": { - "revisions_content": "Заметки периодически сохраняются в фоне, ревизии могут быть использованы для просмотра или отмены случайных изменений. Ревизии также можно создавать самостоятельно.", + "revisions_content": "Ревизии заметок периодически сохраняются в фоне для просмотра или отмены случайных изменений. Ревизии также можно создавать самостоятельно.", "sync_title": "Синхронизация", "protected_notes_title": "Защищённые заметки", - "jump_to_title": "Бастрый поиск и команды", + "jump_to_title": "Мощный поиск и быстрые команды", "search_title": "Глубокий поиск", - "web_clipper_content": "Перемещайте целые веб-страницы (или скриншоты) в Trilium с помощью браузерного расширения web clipper." + "web_clipper_content": "Сохраняйте целые веб-страницы (или скриншоты) в Trilium с помощью браузерного расширения web clipper.", + "title": "Продуктивность и защищенность", + "revisions_title": "История изменений", + "sync_content": "Используйте собственный сервер или облачное хранилище, чтобы легко синхронизировать заметки на нескольких устройствах и получать к ним доступ с мобильного телефона с помощью PWA.", + "protected_notes_content": "Защитите личную информацию, зашифровав заметки и ограничив к ним доступ их с помощью защищенного паролем сеанса.", + "jump_to_content": "Быстро переходите к заметкам или командам, используя поиск по их заголовку, с поиском с учетом опечаток и неточного ввода.", + "web_clipper_title": "Web clipper", + "search_content": "Или выполните поиск текста внутри заметок и сузьте область поиска, отфильтровав результаты по родительской заметке или по глубине поиска." }, "note_types": { - "title": "Несколько способов представления вашей информации", + "title": "Множество способов представление информации", "text_title": "Текстовые заметки", - "text_description": "Заметки редактируются с помощью визуального (WYSIWYG) редактора, поддерживающего таблицы, изображения, математические выражения, блоки кода с подсветкой синтаксиса. Быстро форматируйте текст, используя синтаксис, похожий на Markdown, или слеш-команды." + "text_description": "Заметки редактируются с помощью визуального (WYSIWYG) редактора, поддерживающего таблицы, изображения, математические выражения, блоки кода с подсветкой синтаксиса. Быстро форматируйте текст, используя синтаксис, похожий на Markdown, или слеш-команды.", + "code_title": "Заметки с кодом", + "code_description": "Для работы с большими фрагментами исходного кода или скриптами используется специальный редактор с подсветкой синтаксиса для многих языков программирования и различными цветовыми темами.", + "file_title": "Файлы", + "file_description": "Встраивайте мультимедийные файлы, такие как PDF-файлы, изображения и видео, с предпросмотром прямо в приложении.", + "canvas_title": "Холст", + "canvas_description": "Размещайте фигуры, изображения и текст на бесконечном холсте, используя ту же технологию, что и на сайте excalidraw.com. Идеально подходит для диаграмм, эскизов и визуального планирования.", + "mermaid_title": "Диаграммы Mermaid", + "mermaid_description": "Создавайте диаграммы, такие как блок-схемы, диаграммы классов и последовательностей, диаграммы Ганта и многие другие, используя синтаксис Mermaid.", + "mindmap_title": "Интеллект-карта", + "mindmap_description": "Визуально систематизируйте свои мысли или проведите мозговой штурм.", + "others_list": "а также: <0>карта заметок, <1>карта связей, <2>сохраненный поиск, <3>рендер, и <4>веб-страница." + }, + "download_now": { + "text": "Скачать ", + "platform_big": "v{{version}} для {{platform}}", + "platform_small": "для {{platform}}", + "linux_big": "v{{version}} для Linux", + "linux_small": "для Linux", + "more_platforms": "Другие платформы & сервер" + }, + "final_cta": { + "get_started": "Начало работы", + "title": "Готовы начать работу с Trilium Notes?", + "description": "Создайте свою личную базу знаний с помощью мощных функций и c полной конфиденциальностью." + }, + "header": { + "get-started": "Начало работы", + "support-us": "Поддержите нас", + "documentation": "Документация" + }, + "social_buttons": { + "github": "GitHub", + "matrix": "Matrix", + "reddit": "Reddit", + "github_discussions": "Обсуждения GitHub" + }, + "support_us": { + "github_sponsors": "Спонсоры GitHub", + "title": "Поддержите нас", + "financial_donations_title": "Финансовые пожертвования", + "paypal": "PayPal", + "buy_me_a_coffee": "Buy Me A Coffee", + "financial_donations_cta": "Рассмотрите возможность поддержки главного разработчика приложения (eliandoran) через:", + "financial_donations_description": "Trilium создан и поддерживается благодаря сотням часов работы. Ваша поддержка позволяет сохранить его открытым, улучшить функциональность и покрыть такие расходы, как хостинг." + }, + "components": { + "link_learn_more": "Подробнее..." + }, + "faq": { + "database_answer": "Все ваши заметки хранятся в базе данных SQLite в папке приложения. Trilium использует базу данных вместо обычных текстовых файлов из-за соображений производительности, а также потому, что реализация некоторых функций, таких как клонирование (одна и та же заметка в нескольких местах дерева), была бы гораздо сложнее. Чтобы найти папку приложения, просто перейдите в окно «О программе».", + "scaling_answer": "В зависимости от интенсивности использования, приложение должно без проблем обрабатывать не менее 100 000 заметок. Следует отметить, что процесс синхронизации иногда может завершаться с ошибкой при загрузке большого количества больших файлов (1 ГБ на файл), поскольку Trilium предназначен скорее для создания базы знаний, чем для хранения файлов (как, например, NextCloud).", + "mobile_answer": "В настоящее время официального мобильного приложения нет. Однако, если у вас есть сервер, вы можете получить к нему доступ через веб-браузер и даже установить его как PWA. Для Android существует неофициальное приложение TriliumDroid, которое работает даже в автономном режиме (аналогично настольному клиенту).", + "server_answer": "Нет, сервер обеспечивает доступ через веб-браузер и управляет синхронизацией, если у вас несколько устройств. Для начала достаточно загрузить настольное приложение и начать им пользоваться.", + "title": "Часто задаваемые вопросы", + "mobile_question": "Есть ли мобильное приложение?", + "server_question": "Нужен ли мне сервер для использования Trilium?", + "network_share_question": "Могу ли я синхронизировать базу данных или поделиться ей через облачный диск?", + "network_share_answer": "Нет, в целом, не рекомендуется предоставлять общий доступ к базе данных SQLite через сетевой диск. Хотя иногда это может сработать, существует вероятность повреждения базы данных из-за ненадежной блокировки файлов в сети.", + "database_question": "Где хранятся данные приложения?", + "scaling_question": "Насколько хорошо приложение масштабируется при большом объеме заметок?", + "security_question": "Как защищены мои данные?", + "security_answer": "По умолчанию заметки не шифруются и могут быть прочитаны непосредственно из базы данных. Если вы включите режим шифрования для заметки, она зашифруется с использованием алгоритма AES-128-CBC." + }, + "extensibility_benefits": { + "title": "Публикация заметок и расширяемость", + "import_export_title": "Импорт/экспорт", + "share_title": "Публичный доступ к заметкам", + "scripting_title": "Продвинутый скриптинг", + "scripting_description": "Создавайте собственные интеграции в Trilium с помощью пользовательских виджетов или серверной логики.", + "api_title": "REST API", + "api_description": "Взаимодействуйте с Trilium программно, используя встроенный REST API.", + "import_export_description": "Удобное взаимодействие с другими приложениями благодаря использованию форматов Markdown, ENEX и OML.", + "share_description": "Если у вас есть сервер, его можно использовать для обмена частью ваших заметок с другими людьми." + }, + "contribute": { + "way_community": "Взаимодействуйте с сообществом на GitHub Discussions или на Matrix.", + "title": "Другие способы внести свой вклад", + "way_document": "Улучшите документацию, сообщив нам о пробелах в ней или добавив руководства, часто задаваемые вопросы или обучающие материалы.", + "way_market": "Расскажите о Trillium друзьям, в блоге и социальных сетях.", + "way_reports": "Сообщайте об ошибках через GitHub issues.", + "way_translate": "Переведите приложение на свой родной язык с помощью Weblate." + }, + "collections": { + "title": "Коллекции", + "calendar_title": "Календарь", + "table_title": "Таблица", + "board_description": "Организуйте свои задачи или статус проекта на доске Kanban, используя простой способ создания новых элементов и столбцов, а также изменения их статуса простым перетаскиванием по доске.", + "board_title": "Канбан-доска", + "presentation_description": "Организуйте информацию в виде слайдов и демонстрируйте их в полноэкранном режиме с плавными переходами. Слайды также можно экспортировать в PDF для удобного обмена.", + "presentation_title": "Презентация", + "geomap_description": "Планируйте свой отпуск или отмечайте интересующие вас места прямо на карте с помощью настраиваемых меток. Отображайте записанные GPX-треки для отслеживания маршрута.", + "geomap_title": "Карта", + "table_description": "Отображение и редактирование информации о заметках в виде таблицы с различными типами столбцов, такими как текст, число, флажки, дата и время, ссылки и цвета, а также поддержкой связей. Отображение заметок в древовидном виде внутри таблицы.", + "calendar_description": "Организуйте свои личные или профессиональные события с помощью календаря, поддерживающего события на весь день и на несколько дней. Работайте с календарем в недельном, месячном и годовом режимах просмотра. Простое добавление, событий." + }, + "download_helper_desktop_windows": { + "title_arm64": "Windows на ARM", + "description_arm64": "Совместимо с устройствами на базе ARM (например, с процессорами Qualcomm Snapdragon).", + "download_scoop": "Scoop", + "download_zip": "Портативная версия (.zip)", + "title_x64": "Windows x64", + "description_x64": "Совместимо с устройствами Intel или AMD под управлением Windows 10 и 11.", + "quick_start": "Для установки через Winget:", + "download_exe": "Скачать установщик (.exe)" + }, + "download_helper_desktop_macos": { + "title_x64": "macOS для Intel", + "title_arm64": "macOS для Apple Silicon", + "description_x64": "Для компьютеров Mac на базе процессоров Intel под управлением macOS Monterey или более поздней версии.", + "description_arm64": "Для компьютеров Mac на базе процессоров Apple Silicon, таких как M1 и M2.", + "quick_start": "Для установки через Homebrew:", + "download_dmg": "Скачать установщик (.dmg)", + "download_homebrew_cask": "Homebrew Cask", + "download_zip": "Портативная версия (.zip)" + }, + "download_helper_server_docker": { + "description": "Легко развертывайте приложения на Windows, Linux или macOS с помощью Docker контейнера.", + "title": "Свой сервер с Docker", + "download_dockerhub": "Docker Hub", + "download_ghcr": "ghcr.io" + }, + "download_helper_desktop_linux": { + "download_zip": "Портативная версия (.zip)", + "title_x64": "Linux x64", + "title_arm64": "Linux для ARM", + "description_x64": "Совместимо с архитектурой x86_64 для большинства дистрибутивов Linux.", + "description_arm64": "Для дистрибутивов Linux на базе ARM, совместимых с архитектурой aarch64.", + "quick_start": "Выберите подходящий формат пакета в зависимости от вашего дистрибутива:", + "download_deb": ".deb", + "download_rpm": ".rpm", + "download_flatpak": ".flatpak", + "download_nixpkgs": "nixpkgs", + "download_aur": "AUR" + }, + "download_helper_server_linux": { + "title": "Свой сервер на Linux", + "description": "Разверните Trilium Notes на собственном сервере или VPS, совместимом с большинством дистрибутивов.", + "download_tar_x64": "x64 (.tar.xz)", + "download_tar_arm64": "ARM (.tar.xz)", + "download_nixos": "Модуль NixOS" + }, + "download_helper_server_hosted": { + "title": "Платный хостинг", + "description": "Trilium Notes размещен на PikaPods, платном сервисе для удобного доступа и управления. Не имеет прямого отношения к команде Trilium.", + "download_pikapod": "Запустить на PikaPods", + "download_triliumcc": "Или trilium.cc" + }, + "footer": { + "copyright_and_the": " и ", + "copyright_community": "сообщество" + }, + "404": { + "title": "404: Не найдено", + "description": "Страница, которую вы искали, не найдена. Возможно, она была удалена или URL-адрес указан неверно." } } From 6861a61cacb4fcff546d625af914cdef4fb4668f Mon Sep 17 00:00:00 2001 From: Eugene Date: Fri, 19 Dec 2025 17:41:40 +0100 Subject: [PATCH 675/873] Translated using Weblate (Russian) Currently translated at 100.0% (152 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/ --- apps/website/src/translations/ru/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/website/src/translations/ru/translation.json b/apps/website/src/translations/ru/translation.json index 2554c32478..dd57aac637 100644 --- a/apps/website/src/translations/ru/translation.json +++ b/apps/website/src/translations/ru/translation.json @@ -30,7 +30,7 @@ "jump_to_title": "Мощный поиск и быстрые команды", "search_title": "Глубокий поиск", "web_clipper_content": "Сохраняйте целые веб-страницы (или скриншоты) в Trilium с помощью браузерного расширения web clipper.", - "title": "Продуктивность и защищенность", + "title": "Продуктивность и безопасность", "revisions_title": "История изменений", "sync_content": "Используйте собственный сервер или облачное хранилище, чтобы легко синхронизировать заметки на нескольких устройствах и получать к ним доступ с мобильного телефона с помощью PWA.", "protected_notes_content": "Защитите личную информацию, зашифровав заметки и ограничив к ним доступ их с помощью защищенного паролем сеанса.", From 243d8158cffc4f78548d77da7d4fe4e1e6958beb Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 19 Dec 2025 18:08:42 +0100 Subject: [PATCH 676/873] Translated using Weblate (French) Currently translated at 100.0% (152 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/fr/ --- apps/website/src/translations/fr/translation.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/website/src/translations/fr/translation.json b/apps/website/src/translations/fr/translation.json index 4724c2ac5d..a7d3512a98 100644 --- a/apps/website/src/translations/fr/translation.json +++ b/apps/website/src/translations/fr/translation.json @@ -55,7 +55,7 @@ "title": "Plusieurs façons de représenter vos informations" }, "faq": { - "database_question": "Où sont les données stockées?", + "database_question": "Où sont stockées les données ?", "database_answer": "Toutes vos notes seront stockées dans une base de données SQLite, dans un dossier d'application. Trilium utilise une base de données plutôt que des fichiers texte pour des raisons de performances, et certaines fonctionnalités seraient beaucoup plus complexes à implémenter, comme les clones (même note à plusieurs endroits de l'arborescence). Pour trouver le dossier d'application, accédez simplement à la fenêtre « À propos ».", "mobile_answer": "Il n'existe actuellement aucune application mobile officielle. Cependant, si vous disposez d'une instance serveur, vous pouvez y accéder via un navigateur web et même l'installer en tant que PWA. Pour Android, il existe une application non officielle appelée \"TriliumDroid\", qui fonctionne même hors ligne (comme un client de bureau).", "mobile_question": "Y a-t-il une application mobile ?", @@ -171,7 +171,8 @@ "geomap_title": "Géocarte", "geomap_description": "Planifiez vos vacances ou marquez vos points d'intérêt directement sur une carte géographique grâce à des marqueurs personnalisables. Affichez les traces GPX enregistrées pour suivre vos itinéraires.", "title": "Collections", - "presentation_title": "Présentation" + "presentation_title": "Présentation", + "presentation_description": "Organisez les informations sous forme de diapositives et présentez-les en plein écran avec des transitions fluides. Les diapositives peuvent également être exportées au format PDF pour faciliter leur partage." }, "download_now": { "text": "Télécharger maintenant. ", @@ -190,5 +191,10 @@ "github_discussions": "Discussions GitHub", "matrix": "Matrix", "reddit": "Reddit" + }, + "header": { + "get-started": "Commencer", + "documentation": "Documentation", + "support-us": "Soutenez-nous" } } From aac4316fb85e6be71102e5871a1563b6c2e90c41 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 10:33:28 +0200 Subject: [PATCH 677/873] feat(right_pane): render title bar --- apps/client/src/widgets/react/hooks.tsx | 17 ++++---- .../widgets/sidebar/RightPanelContainer.tsx | 39 +++++++++++++++++-- .../src/widgets/sidebar/RightPanelWidget.tsx | 11 +++--- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 9540cb8adb..d6b0db34c6 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -605,10 +605,11 @@ export function useNoteBlob(note: FNote | null | undefined, componentId?: string return blob; } -export function useLegacyWidget(widgetFactory: () => T, { noteContext, containerClassName, containerStyle }: { +export function useLegacyWidget(widgetFactory: () => T, { noteContext, containerClassName, containerStyle, noAttach }: { noteContext?: NoteContext; containerClassName?: string; containerStyle?: CSSProperties; + noAttach?: boolean; } = {}): [VNode, T] { const ref = useRef(null); const parentComponent = useContext(ParentComponent); @@ -627,22 +628,24 @@ export function useLegacyWidget(widgetFactory: () => T, { const renderedWidget = widget.render(); return [ widget, renderedWidget ]; - }, []); + }, [ noteContext, parentComponent, widgetFactory]); // Attach the widget to the parent. useEffect(() => { - if (ref.current) { - ref.current.innerHTML = ""; - renderedWidget.appendTo(ref.current); + if (noAttach) return; + const parentContainer = ref.current; + if (parentContainer) { + parentContainer.replaceChildren(); + renderedWidget.appendTo(parentContainer); } - }, [ renderedWidget ]); + }, [ renderedWidget, noAttach ]); // Inject the note context. useEffect(() => { if (noteContext && widget instanceof NoteContextAwareWidget) { widget.activeContextChangedEvent({ noteContext }); } - }, [ noteContext ]); + }, [ noteContext, widget ]); useDebugValue(widget); diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 85010e8508..fd339563ae 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -2,7 +2,7 @@ import "./RightPanelContainer.css"; import Split from "@triliumnext/split.js"; -import { useEffect } from "preact/hooks"; +import { useEffect, useRef } from "preact/hooks"; import { t } from "../../services/i18n"; import options from "../../services/options"; @@ -11,7 +11,9 @@ import BasicWidget from "../basic_widget"; import Button from "../react/Button"; import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumOptionBool, useTriliumOptionJson } from "../react/hooks"; import Icon from "../react/Icon"; +import LegacyRightPanelWidget from "../right_panel_widget"; import HighlightsList from "./HighlightsList"; +import RightPanelWidget from "./RightPanelWidget"; import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; @@ -67,7 +69,36 @@ function useSplit(visible: boolean) { }, [ visible ]); } -function CustomWidget({ originalWidget }: { originalWidget: BasicWidget }) { - const [ el ] = useLegacyWidget(() => originalWidget); - return <>{el}; +function CustomWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) { + const containerRef = useRef(null); + const [ el ] = useLegacyWidget(() => { + // Monkey-patch the original widget by replacing the default initialization logic. + originalWidget.doRender = function doRender(this: LegacyRightPanelWidget) { + if (!containerRef.current) { + this.$widget = $("
    "); + return; + }; + this.$widget = $(containerRef.current); + this.$body = this.$widget.find(".card-body"); + const renderResult = this.doRenderBody(); + if (typeof renderResult === "object" && "catch" in renderResult) { + this.initialized = renderResult.catch((e) => { + this.logRenderingError(e); + }); + } else { + this.initialized = Promise.resolve(); + } + }; + + return originalWidget; + }, { + noAttach: true + }); + return ( + {el} + ); } diff --git a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx index 6b3a56925f..a64b636d61 100644 --- a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx @@ -1,8 +1,8 @@ import clsx from "clsx"; -import { ComponentChildren } from "preact"; -import { useContext, useRef, useState } from "preact/hooks"; +import { ComponentChildren, RefObject } from "preact"; +import { useContext, useState } from "preact/hooks"; -import { useTriliumOptionJson } from "../react/hooks"; +import { useSyncedRef, useTriliumOptionJson } from "../react/hooks"; import Icon from "../react/Icon"; import { ParentComponent } from "../react/react_utils"; @@ -11,12 +11,13 @@ interface RightPanelWidgetProps { title: string; children: ComponentChildren; buttons?: ComponentChildren; + containerRef?: RefObject; } -export default function RightPanelWidget({ id, title, buttons, children }: RightPanelWidgetProps) { +export default function RightPanelWidget({ id, title, buttons, children, containerRef: externalContainerRef }: RightPanelWidgetProps) { const [ rightPaneCollapsedItems, setRightPaneCollapsedItems ] = useTriliumOptionJson("rightPaneCollapsedItems"); const [ expanded, setExpanded ] = useState(!rightPaneCollapsedItems.includes(id)); - const containerRef = useRef(null); + const containerRef = useSyncedRef(externalContainerRef, null); const parentComponent = useContext(ParentComponent); if (parentComponent) { From 37ea1584c94e35303eac437296a7d3e85780cbf2 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 20 Dec 2025 10:42:13 +0200 Subject: [PATCH 678/873] style/tab bar: visually merge the tab bar with the center panel --- apps/client/src/stylesheets/theme-next/shell.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index e3e92cd7d8..d44e6e059c 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -1025,7 +1025,7 @@ body.layout-vertical.electron.platform-darwin .tab-row-container { } .tab-row-widget > * { - margin-top: calc((var(--tab-bar-height) - var(--tab-height)) / 2); + margin-top: calc(var(--tab-bar-height) - var(--tab-height)); } body.layout-horizontal .tab-row-container { @@ -1069,8 +1069,9 @@ body.desktop:not(.background-effects.platform-win32) #root-widget.horizontal-lay border-bottom-color: transparent; } -.tab-row-widget .note-tab .note-tab-wrapper { +:root div.tab-row-widget div.note-tab div.note-tab-wrapper { height: var(--tab-height) !important; + border-radius: 8px 8px 0 0; transition: background 75ms ease-in, box-shadow 75ms ease-in; From b7b7610f4d1ff9411432876b8146ad57283f9985 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 20 Dec 2025 11:05:14 +0200 Subject: [PATCH 679/873] style/classic toolbar: allow customizing the background color via a CSS variable --- apps/client/src/stylesheets/theme-next-dark.css | 3 +++ apps/client/src/stylesheets/theme-next-light.css | 3 +++ apps/client/src/stylesheets/theme-next/shell.css | 16 ++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index f4848739e3..8e92f4900a 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -170,6 +170,9 @@ --protected-session-active-icon-color: #8edd8e; --sync-status-error-pulse-color: #f47871; + --classic-toolbar-vert-layout-background-color: var(--main-background-color); + --classic-toolbar-horiz-layout-background-color: var(--main-background-color); + --center-pane-vert-layout-background-color-bgfx: #0c0c0c69; --center-pane-horiz-layout-background-color-bgfx: #1e1e1ec7; diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 0f71df9c32..1bc72a3e2c 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -162,6 +162,9 @@ --protected-session-active-icon-color: #16b516; --sync-status-error-pulse-color: #ff5528; + --classic-toolbar-vert-layout-background-color: var(--main-background-color); + --classic-toolbar-horiz-layout-background-color: var(--main-background-color); + --center-pane-vert-layout-background-color-bgfx: #ffffff75; --center-pane-horiz-layout-background-color-bgfx: #ffffffd6; diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index d44e6e059c..9cf8b990f1 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -1222,6 +1222,22 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging . top: 0; } +/* + * CLASSIC FORMATTING TOOLBAR + */ + +#rest-pane > .classic-toolbar-widget { + margin-bottom: 2px; +} + +body.layout-vertical #rest-pane > .classic-toolbar-widget { + background: var(--classic-toolbar-vert-layout-background-color); +} + +body.layout-horizontal #rest-pane > .classic-toolbar-widget { + background: var(--classic-toolbar-horiz-layout-background-color); +} + /* * CENTER PANE */ From e1df65adce143c277c483b89f56351a076c3dcff Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 11:09:59 +0200 Subject: [PATCH 680/873] fix(right_pane): custom widgets not rendering after being expanded --- apps/client/src/widgets/react/hooks.tsx | 6 ++-- .../widgets/sidebar/RightPanelContainer.tsx | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index d6b0db34c6..898e425ade 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -605,11 +605,10 @@ export function useNoteBlob(note: FNote | null | undefined, componentId?: string return blob; } -export function useLegacyWidget(widgetFactory: () => T, { noteContext, containerClassName, containerStyle, noAttach }: { +export function useLegacyWidget(widgetFactory: () => T, { noteContext, containerClassName, containerStyle }: { noteContext?: NoteContext; containerClassName?: string; containerStyle?: CSSProperties; - noAttach?: boolean; } = {}): [VNode, T] { const ref = useRef(null); const parentComponent = useContext(ParentComponent); @@ -632,13 +631,12 @@ export function useLegacyWidget(widgetFactory: () => T, { // Attach the widget to the parent. useEffect(() => { - if (noAttach) return; const parentContainer = ref.current; if (parentContainer) { parentContainer.replaceChildren(); renderedWidget.appendTo(parentContainer); } - }, [ renderedWidget, noAttach ]); + }); // Inject the note context. useEffect(() => { diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index fd339563ae..21d10cd993 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -71,15 +71,26 @@ function useSplit(visible: boolean) { function CustomWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) { const containerRef = useRef(null); + + return ( + + + + ); +} + +function CustomWidgetContent({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) { const [ el ] = useLegacyWidget(() => { + originalWidget.contentSized(); + // Monkey-patch the original widget by replacing the default initialization logic. originalWidget.doRender = function doRender(this: LegacyRightPanelWidget) { - if (!containerRef.current) { - this.$widget = $("
    "); - return; - }; - this.$widget = $(containerRef.current); - this.$body = this.$widget.find(".card-body"); + this.$widget = $("
    "); + this.$body = this.$widget; const renderResult = this.doRenderBody(); if (typeof renderResult === "object" && "catch" in renderResult) { this.initialized = renderResult.catch((e) => { @@ -91,14 +102,7 @@ function CustomWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidg }; return originalWidget; - }, { - noAttach: true }); - return ( - {el} - ); + + return el; } From e82e92c22ca018456f776ec8b94cb89ab11a5483 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 11:16:41 +0200 Subject: [PATCH 681/873] fix(right_pane): custom widgets not aware of note context --- apps/client/src/widgets/sidebar/RightPanelContainer.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 21d10cd993..31743a4462 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -84,6 +84,7 @@ function CustomWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidg } function CustomWidgetContent({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) { + const { noteContext } = useActiveNoteContext(); const [ el ] = useLegacyWidget(() => { originalWidget.contentSized(); @@ -102,6 +103,8 @@ function CustomWidgetContent({ originalWidget }: { originalWidget: LegacyRightPa }; return originalWidget; + }, { + noteContext }); return el; From eeea96b98c1dd4972333cf672d248e38811363c0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 11:17:29 +0200 Subject: [PATCH 682/873] chore(right_pane): missing key for custom widgets --- apps/client/src/widgets/sidebar/RightPanelContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 31743a4462..498013c1e5 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -28,7 +28,7 @@ export default function RightPanelContainer({ customWidgets }: { customWidgets: const items = (rightPaneVisible ? [ (noteType === "text" || noteType === "doc") && , noteType === "text" && highlightsList.length > 0 && , - ...customWidgets.map((w) => ) + ...customWidgets.map((w) => ) ] : []).filter(Boolean); return ( From dced799976996f83baba6380af4dabbf1c490b7f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 11:39:46 +0200 Subject: [PATCH 683/873] feat(right_pane): add context menu with go to source for custom widgets --- .../src/translations/en/translation.json | 3 ++- .../widgets/sidebar/RightPanelContainer.tsx | 8 +++++++ .../src/widgets/sidebar/RightPanelWidget.tsx | 24 +++++++++++++++++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 458ea6b0c1..a9ba894614 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2203,6 +2203,7 @@ "right_pane": { "empty_message": "Nothing to show for this note", "empty_button": "Hide the panel", - "toggle": "Toggle right panel" + "toggle": "Toggle right panel", + "custom_widget_go_to_source": "Go to source code" } } diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 498013c1e5..c49dc15cf8 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -4,6 +4,7 @@ import "./RightPanelContainer.css"; import Split from "@triliumnext/split.js"; import { useEffect, useRef } from "preact/hooks"; +import appContext from "../../components/app_context"; import { t } from "../../services/i18n"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; @@ -77,6 +78,13 @@ function CustomWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidg id={originalWidget._noteId} title={originalWidget.widgetTitle} containerRef={containerRef} + contextMenuItems={[ + { + title: t("right_pane.custom_widget_go_to_source"), + uiIcon: "bx bx-code-curly", + handler: () => appContext.tabManager.openInNewTab(originalWidget._noteId, null, true) + } + ]} > diff --git a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx index a64b636d61..42d569d648 100644 --- a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx @@ -2,6 +2,8 @@ import clsx from "clsx"; import { ComponentChildren, RefObject } from "preact"; import { useContext, useState } from "preact/hooks"; +import contextMenu, { MenuItem } from "../../menus/context_menu"; +import ActionButton from "../react/ActionButton"; import { useSyncedRef, useTriliumOptionJson } from "../react/hooks"; import Icon from "../react/Icon"; import { ParentComponent } from "../react/react_utils"; @@ -12,9 +14,10 @@ interface RightPanelWidgetProps { children: ComponentChildren; buttons?: ComponentChildren; containerRef?: RefObject; + contextMenuItems?: MenuItem[]; } -export default function RightPanelWidget({ id, title, buttons, children, containerRef: externalContainerRef }: RightPanelWidgetProps) { +export default function RightPanelWidget({ id, title, buttons, children, containerRef: externalContainerRef, contextMenuItems }: RightPanelWidgetProps) { const [ rightPaneCollapsedItems, setRightPaneCollapsedItems ] = useTriliumOptionJson("rightPaneCollapsedItems"); const [ expanded, setExpanded ] = useState(!rightPaneCollapsedItems.includes(id)); const containerRef = useSyncedRef(externalContainerRef, null); @@ -49,7 +52,24 @@ export default function RightPanelWidget({ id, title, buttons, children, contain icon="bx bx-chevron-down" />
    {title}
    -
    {buttons}
    +
    + {buttons} + {contextMenuItems && ( + { + e.stopPropagation(); + contextMenu.show({ + x: e.pageX, + y: e.pageY, + items: contextMenuItems, + selectMenuItemHandler: () => {} + }); + }} + /> + )} +
    From a0577dc20279decf137ad6dcd701d363bd511c8a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 11:42:21 +0200 Subject: [PATCH 684/873] chore(right_pane): use menu instead of button for highlights list --- .../client/src/translations/en/translation.json | 1 + .../src/widgets/sidebar/HighlightsList.tsx | 17 +++++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index a9ba894614..76d674985f 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1725,6 +1725,7 @@ "title": "Highlights List", "options": "Options", "modal_title": "Configure Highlights List", + "menu_configure": "Configure highlights list...", "no_highlights": "No highlights found." }, "quick-search": { diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 7c36053611..e93db959c2 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -32,16 +32,13 @@ export default function HighlightsList() { { - e.stopPropagation(); - setShown(true); - }} - /> - )} + contextMenuItems={[ + { + title: t("highlights_list_2.menu_configure"), + uiIcon: "bx bx-cog", + handler: () => setShown(true) + } + ]} > {noteType === "text" && isReadOnly && } {noteType === "text" && !isReadOnly && } From cd49c365290f431556fb67aabddaf5c48c096517 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 11:45:08 +0200 Subject: [PATCH 685/873] chore(right_pane): decrease context menu size slightly --- apps/client/src/widgets/sidebar/RightPanelContainer.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 595656c290..7e3072d42f 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -8,9 +8,10 @@ body.experimental-feature-new-layout #right-pane { border-radius: 0; .card-header { - padding-block: 0.2em; + padding: 0; cursor: pointer; justify-content: flex-start; + --icon-button-size: 26px; .card-header-title { padding-inline: 0.5em; From bc8c852a4d6e90e641b0d089cd08343d47e9ff7e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 11:48:47 +0200 Subject: [PATCH 686/873] chore(right_pane): align collapse icon with menu item --- apps/client/src/widgets/sidebar/RightPanelContainer.css | 9 +++++++-- apps/client/src/widgets/sidebar/RightPanelWidget.tsx | 4 +--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index 7e3072d42f..ce33b84303 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -8,17 +8,22 @@ body.experimental-feature-new-layout #right-pane { border-radius: 0; .card-header { - padding: 0; + padding: 1px 0 0 0; cursor: pointer; justify-content: flex-start; --icon-button-size: 26px; .card-header-title { - padding-inline: 0.5em; + padding-inline: 0; flex-grow: 1; } } + .card-header-buttons { + transform: none; + top: 0; + } + &:last-of-type { border-bottom: 0; } diff --git a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx index 42d569d648..3c3c600d7c 100644 --- a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx @@ -48,9 +48,7 @@ export default function RightPanelWidget({ id, title, buttons, children, contain } }} > - +
    {title}
    {buttons} From 2b827991ef48396562b1a478ac7e9f0e2768ebe4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 11:52:40 +0200 Subject: [PATCH 687/873] feat(right_pane): only grow table of contents & highlights --- apps/client/src/widgets/sidebar/HighlightsList.tsx | 1 + apps/client/src/widgets/sidebar/RightPanelContainer.css | 2 +- apps/client/src/widgets/sidebar/RightPanelWidget.tsx | 8 ++++++-- apps/client/src/widgets/sidebar/TableOfContents.tsx | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index e93db959c2..2fb19e38b2 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -39,6 +39,7 @@ export default function HighlightsList() { handler: () => setShown(true) } ]} + grow > {noteType === "text" && isReadOnly && } {noteType === "text" && !isReadOnly && } diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.css b/apps/client/src/widgets/sidebar/RightPanelContainer.css index ce33b84303..4e1a921479 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.css +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.css @@ -33,7 +33,7 @@ body.experimental-feature-new-layout #right-pane { } } - .card:not(.collapsed) { + .card.grow:not(.collapsed) { flex-grow: 1; } diff --git a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx index 3c3c600d7c..4cb8aedf30 100644 --- a/apps/client/src/widgets/sidebar/RightPanelWidget.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelWidget.tsx @@ -15,9 +15,10 @@ interface RightPanelWidgetProps { buttons?: ComponentChildren; containerRef?: RefObject; contextMenuItems?: MenuItem[]; + grow?: boolean; } -export default function RightPanelWidget({ id, title, buttons, children, containerRef: externalContainerRef, contextMenuItems }: RightPanelWidgetProps) { +export default function RightPanelWidget({ id, title, buttons, children, containerRef: externalContainerRef, contextMenuItems, grow }: RightPanelWidgetProps) { const [ rightPaneCollapsedItems, setRightPaneCollapsedItems ] = useTriliumOptionJson("rightPaneCollapsedItems"); const [ expanded, setExpanded ] = useState(!rightPaneCollapsedItems.includes(id)); const containerRef = useSyncedRef(externalContainerRef, null); @@ -30,7 +31,10 @@ export default function RightPanelWidget({ id, title, buttons, children, contain return (
    + {((noteType === "text" && isReadOnly) || (noteType === "doc")) && } {noteType === "text" && !isReadOnly && } From 35afd60d0083e7d8cbb32a358c3112bc2855bb7b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 12:17:14 +0200 Subject: [PATCH 688/873] feat(right_pane): respect position --- .../widgets/sidebar/RightPanelContainer.tsx | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index c49dc15cf8..c795604575 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -2,6 +2,7 @@ import "./RightPanelContainer.css"; import Split from "@triliumnext/split.js"; +import { VNode } from "preact"; import { useEffect, useRef } from "preact/hooks"; import appContext from "../../components/app_context"; @@ -19,19 +20,17 @@ import TableOfContents from "./TableOfContents"; const MIN_WIDTH_PERCENT = 5; +interface RightPanelWidgetDefinition { + el: VNode; + enabled: boolean; + position: number; +} + export default function RightPanelContainer({ customWidgets }: { customWidgets: BasicWidget[] }) { const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible"); - const [ highlightsList ] = useTriliumOptionJson("highlightsList"); + const items = useItems(rightPaneVisible, customWidgets); useSplit(rightPaneVisible); - const { note } = useActiveNoteContext(); - const noteType = useNoteProperty(note, "type"); - const items = (rightPaneVisible ? [ - (noteType === "text" || noteType === "doc") && , - noteType === "text" && highlightsList.length > 0 && , - ...customWidgets.map((w) => ) - ] : []).filter(Boolean); - return (
    {rightPaneVisible && ( @@ -52,6 +51,36 @@ export default function RightPanelContainer({ customWidgets }: { customWidgets: ); } +function useItems(rightPaneVisible: boolean, customWidgets: BasicWidget[]) { + const { note } = useActiveNoteContext(); + const noteType = useNoteProperty(note, "type"); + const [ highlightsList ] = useTriliumOptionJson("highlightsList"); + + if (!rightPaneVisible) return []; + const definitions: RightPanelWidgetDefinition[] = [ + { + el: , + enabled: (noteType === "text" || noteType === "doc"), + position: 10, + }, + { + el: , + enabled: noteType === "text" && highlightsList.length > 0, + position: 20, + }, + ...customWidgets.map((w, i) => ({ + el: , + enabled: true, + position: w.position ?? 30 + i * 10 + })) + ]; + + return definitions + .filter(e => e.enabled) + .toSorted((a, b) => a.position - b.position) + .map(e => e.el); +} + function useSplit(visible: boolean) { // Split between right pane and the content pane. useEffect(() => { From 7af5c77bcb27e70e119309cd83be3ffc1820c461 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 20 Dec 2025 12:18:11 +0200 Subject: [PATCH 689/873] style/tab bar: tweak margin --- apps/client/src/stylesheets/theme-next/base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index e3468421e1..f77696dc0f 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -50,7 +50,7 @@ --tab-bar-height: 50px; --tab-height: 36px; - --tab-first-item-horiz-offset: 1px; + --tab-first-item-horiz-offset: 0; --new-tab-button-size: 24px; --center-pane-border-radius: 10px; From b248805905dc10f4b78097733da843d52564a97f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 12:25:43 +0200 Subject: [PATCH 690/873] feat(right_pane): add count to highlights list --- .../src/translations/en/translation.json | 2 + .../src/widgets/sidebar/HighlightsList.tsx | 89 ++++++++++--------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 76d674985f..b45f26cb59 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1723,6 +1723,8 @@ }, "highlights_list_2": { "title": "Highlights List", + "title_with_count_one": "{{count}} highlight", + "title_with_count_other": "{{count}} highlights", "options": "Options", "modal_title": "Configure Highlights List", "menu_configure": "Configure highlights list...", diff --git a/apps/client/src/widgets/sidebar/HighlightsList.tsx b/apps/client/src/widgets/sidebar/HighlightsList.tsx index 2fb19e38b2..0eaeb40dc4 100644 --- a/apps/client/src/widgets/sidebar/HighlightsList.tsx +++ b/apps/client/src/widgets/sidebar/HighlightsList.tsx @@ -3,7 +3,6 @@ import { createPortal } from "preact/compat"; import { useCallback, useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; -import ActionButton from "../react/ActionButton"; import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor, useTriliumOptionJson } from "../react/hooks"; import Modal from "../react/Modal"; import { HighlightsListOptions } from "../type_widgets/options/text_notes"; @@ -25,26 +24,11 @@ export default function HighlightsList() { const { note, noteContext } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); const { isReadOnly } = useIsNoteReadOnly(note, noteContext); - const [ shown, setShown ] = useState(false); return ( <> - setShown(true) - } - ]} - grow - > - {noteType === "text" && isReadOnly && } - {noteType === "text" && !isReadOnly && } - - {createPortal(, document.body)} + {noteType === "text" && isReadOnly && } + {noteType === "text" && !isReadOnly && } ); } @@ -80,33 +64,50 @@ function AbstractHighlightsList({ highlights, scrollToHi ); }); + const [ shown, setShown ] = useState(false); return ( - - {filteredHighlights.length > 0 ? ( -
      - {filteredHighlights.map(highlight => ( -
    1. scrollToHighlight(highlight)} - > - {highlight.text} -
    2. - ))} -
    - ) : ( -
    - {t("highlights_list_2.no_highlights")} -
    - )} -
    + <> + setShown(true) + } + ]} + grow + > + + {filteredHighlights.length > 0 ? ( +
      + {filteredHighlights.map(highlight => ( +
    1. scrollToHighlight(highlight)} + > + {highlight.text} +
    2. + ))} +
    + ) : ( +
    + {t("highlights_list_2.no_highlights")} +
    + )} +
    +
    + {createPortal(, document.body)} + ); } From ea76fd797c32443a19f60231b48052b2ecc1b177 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 12:29:43 +0200 Subject: [PATCH 691/873] chore(right_pane): address requested changes --- apps/client/src/services/utils.ts | 99 ++++++++++++------------- apps/client/src/widgets/react/hooks.tsx | 2 +- 2 files changed, 48 insertions(+), 53 deletions(-) diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 36277bbb15..8c2a12c6ac 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -1,8 +1,7 @@ import { dayjs } from "@triliumnext/commons"; -import { snapdom } from "@zumer/snapdom"; - -import FNote from "../entities/fnote"; import type { ViewMode, ViewScope } from "./link.js"; +import FNote from "../entities/fnote"; +import { snapdom } from "@zumer/snapdom"; const SVG_MIME = "image/svg+xml"; @@ -114,9 +113,9 @@ function formatDateISO(date: Date) { export function formatDateTime(date: Date, userSuppliedFormat?: string): string { if (userSuppliedFormat?.trim()) { return dayjs(date).format(userSuppliedFormat); - } - return `${formatDate(date)} ${formatTime(date)}`; - + } else { + return `${formatDate(date)} ${formatTime(date)}`; + } } function localNowDateTime() { @@ -192,9 +191,9 @@ export function formatSize(size: number | null | undefined) { if (size < 1024) { return `${size} KiB`; - } - return `${Math.round(size / 102.4) / 10} MiB`; - + } else { + return `${Math.round(size / 102.4) / 10} MiB`; + } } function toObject(array: T[], fn: (arg0: T) => [key: string, value: R]) { @@ -298,18 +297,18 @@ function formatHtml(html: string) { let indent = "\n"; const tab = "\t"; let i = 0; - const pre: { indent: string; tag: string }[] = []; + let pre: { indent: string; tag: string }[] = []; html = html - .replace(new RegExp("
    ([\\s\\S]+?)?
    "), (x) => { + .replace(new RegExp("
    ([\\s\\S]+?)?
    "), function (x) { pre.push({ indent: "", tag: x }); - return `<--TEMPPRE${ i++ }/-->`; + return "<--TEMPPRE" + i++ + "/-->"; }) - .replace(new RegExp("<[^<>]+>[^<]?", "g"), (x) => { + .replace(new RegExp("<[^<>]+>[^<]?", "g"), function (x) { let ret; const tagRegEx = /<\/?([^\s/>]+)/.exec(x); - const tag = tagRegEx ? tagRegEx[1] : ""; - const p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x); + let tag = tagRegEx ? tagRegEx[1] : ""; + let p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x); if (p) { const pInd = parseInt(p[1]); @@ -319,22 +318,24 @@ function formatHtml(html: string) { if (["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"].indexOf(tag) >= 0) { // self closing tag ret = indent + x; - } else if (x.indexOf("") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length); - else ret = indent + x; - !p && (indent += tab); } else { - //close tag - indent = indent.substr(0, indent.length - 1); - if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length); - else ret = indent + x; + if (x.indexOf("") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length); + else ret = indent + x; + !p && (indent += tab); + } else { + //close tag + indent = indent.substr(0, indent.length - 1); + if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length); + else ret = indent + x; + } } return ret; }); for (i = pre.length; i--;) { - html = html.replace(`<--TEMPPRE${ i }/-->`, pre[i].tag.replace("
    ", "
    \n").replace("
    ", `${pre[i].indent }
    `)); + html = html.replace("<--TEMPPRE" + i + "/-->", pre[i].tag.replace("
    ", "
    \n").replace("
    ", pre[i].indent + "
    ")); } return html.charAt(0) === "\n" ? html.substr(1, html.length - 1) : html; @@ -363,11 +364,11 @@ type dynamicRequireMappings = { export function dynamicRequire(moduleName: T): Awaited{ if (typeof __non_webpack_require__ !== "undefined") { return __non_webpack_require__(moduleName); - } - // explicitly pass as string and not as expression to suppress webpack warning - // 'Critical dependency: the request of a dependency is an expression' - return require(`${moduleName}`); - + } else { + // explicitly pass as string and not as expression to suppress webpack warning + // 'Critical dependency: the request of a dependency is an expression' + return require(`${moduleName}`); + } } function timeLimit(promise: Promise, limitMs: number, errorMessage?: string) { @@ -508,8 +509,8 @@ export function escapeRegExp(str: string) { function areObjectsEqual(...args: unknown[]) { let i; let l; - let leftChain: object[]; - let rightChain: object[]; + let leftChain: Object[]; + let rightChain: Object[]; function compare2Objects(x: unknown, y: unknown) { let p; @@ -694,9 +695,9 @@ async function downloadAsSvg(nameWithoutExtension: string, svgSource: string | S try { const result = await snapdom(element, { - backgroundColor: "transparent", - scale: 2 - }); + backgroundColor: "transparent", + scale: 2 + }); triggerDownload(`${nameWithoutExtension}.svg`, result.url); } finally { cleanup(); @@ -732,9 +733,9 @@ async function downloadAsPng(nameWithoutExtension: string, svgSource: string | S try { const result = await snapdom(element, { - backgroundColor: "transparent", - scale: 2 - }); + backgroundColor: "transparent", + scale: 2 + }); const pngImg = await result.toPng(); await triggerDownload(`${nameWithoutExtension}.png`, pngImg.src); } finally { @@ -762,11 +763,11 @@ export function getSizeFromSvg(svgContent: string) { return { width: parseFloat(width), height: parseFloat(height) - }; - } - console.warn("SVG export error", svgDocument.documentElement); - return null; - + } + } else { + console.warn("SVG export error", svgDocument.documentElement); + return null; + } } /** @@ -895,9 +896,9 @@ export function mapToKeyValueArray(map: R export function getErrorMessage(e: unknown) { if (e && typeof e === "object" && "message" in e && typeof e.message === "string") { return e.message; - } - return "Unknown error"; - + } else { + return "Unknown error"; + } } /** @@ -912,12 +913,6 @@ export function handleRightToLeftPlacement(placement: T) { return placement; } -export function clamp(value: number, min: number, max: number) { - if (value < min) return min; - if (value > max) return max; - return value; -} - export default { reloadFrontendApp, restartDesktopApp, diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 898e425ade..034e77daba 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -636,7 +636,7 @@ export function useLegacyWidget(widgetFactory: () => T, { parentContainer.replaceChildren(); renderedWidget.appendTo(parentContainer); } - }); + }, [ renderedWidget ]); // Inject the note context. useEffect(() => { From f1ca8881a1d952c70ac7c9640a4fad43ac2a88e1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 12:32:20 +0200 Subject: [PATCH 692/873] chore(right_pane): fix typecheck --- apps/client/src/widgets/highlights_list.ts | 62 +++++++++++----------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/apps/client/src/widgets/highlights_list.ts b/apps/client/src/widgets/highlights_list.ts index 5023facb90..af5844b488 100644 --- a/apps/client/src/widgets/highlights_list.ts +++ b/apps/client/src/widgets/highlights_list.ts @@ -5,14 +5,14 @@ * - For example, if there is a formula in the middle of the highlighted text, the two ends of the formula will be regarded as two entries */ -import { t } from "../services/i18n.js"; -import attributeService from "../services/attributes.js"; -import RightPanelWidget from "./right_panel_widget.js"; -import options from "../services/options.js"; -import OnClickButtonWidget from "./buttons/onclick_button.js"; import appContext, { type EventData } from "../components/app_context.js"; import type FNote from "../entities/fnote.js"; +import attributeService from "../services/attributes.js"; +import { t } from "../services/i18n.js"; import katex from "../services/math.js"; +import options from "../services/options.js"; +import OnClickButtonWidget from "./buttons/onclick_button.js"; +import RightPanelWidget from "./right_panel_widget.js"; const TPL = /*html*/`