From 9942950710cde6ad2ae9579e93b078e1f4398fc3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 8 Dec 2025 14:54:57 +0200 Subject: [PATCH 01/44] 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 50dc05d99..90b8566e9 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 dd56edf96..477d93209 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 02/44] 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 90b8566e9..0347acf1e 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 000000000..fe081ee3f --- /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 000000000..0dd306868 --- /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 468a0e73e..3bbce9066 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 03/44] 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 0dd306868..9055a1e53 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 04/44] 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 9055a1e53..24a859eac 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 05/44] 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 fe081ee3f..1993e57ca 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 24a859eac..dc1b07233 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 06/44] 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 dc1b07233..eacf79ed7 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 07/44] 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 1993e57ca..12ee1c759 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 eacf79ed7..3adf46b0a 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 24a7948dc..0c9883df2 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 08/44] 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 3adf46b0a..945f55e64 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 09/44] 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 a596e7136..79bc18c2a 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 945f55e64..18ec9e845 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 10/44] 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 18ec9e845..1b3ae4888 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 11/44] 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 1b3ae4888..a4874599d 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 12/44] 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 a4874599d..a624e2e58 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 13/44] 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 a624e2e58..e24704e0a 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 14/44] 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 12ee1c759..df9f5c6f0 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 15/44] 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 df9f5c6f0..fee2e9799 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 e24704e0a..632d2c971 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 16/44] 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 632d2c971..8f09fdd2b 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 17/44] 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 fee2e9799..c64b6b517 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 8f09fdd2b..bf26fef04 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 18/44] 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 bf26fef04..1c4f6a4dc 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 19/44] 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 c64b6b517..810d54797 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 20/44] 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 810d54797..7a78982b2 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 1c4f6a4dc..940f62405 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 21/44] 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 940f62405..29d5cc7f0 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 22/44] 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 3718602cf..3101339be 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 23/44] 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 3a2254d69..8d9119044 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 24/44] 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 0347acf1e..7464844dc 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 3101339be..a518e05dc 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 25/44] 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 7464844dc..725e050ba 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 26/44] 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 29d5cc7f0..0c08cf160 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 27/44] 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 0c08cf160..24d2ba9d4 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 28/44] 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 725e050ba..8cef9fe2a 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 7a78982b2..9739f33db 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 29/44] 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 24d2ba9d4..5af9caabe 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 30/44] 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 000000000..322c88da9 --- /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 ac001d40a..96b9ba5b8 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 290d1b30e..a14d5bc96 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 31/44] 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 322c88da9..97b2c965c 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 cf9774e0b..4a7a859db 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 32/44] 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 f2f554629..ce76ad286 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 33/44] 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 b61df46da..dbed6bdf7 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 60b2e2667..55ade760c 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 34/44] 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 dbed6bdf7..46ed6dd80 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 96b9ba5b8..0bfdd8b3e 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 35/44] 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 5cd4eecbe..bd6f0473a 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 f38039afd..85b6d5817 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 0bfdd8b3e..c4b38bbfd 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 36/44] 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 97b2c965c..060abde12 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 a14d5bc96..95c8791eb 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 37/44] 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 95c8791eb..752a26863 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 38/44] 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 752a26863..bfe964a2f 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 39/44] 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 c4b38bbfd..17ec4651f 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 40/44] 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 bfe964a2f..07619c89d 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 41/44] 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 8cef9fe2a..41a6bb68f 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 a3be50dc6..2163de8e5 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 42/44] 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 5aafffd9f..ac3d628c3 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 43/44] 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 5af9caabe..c3ec93504 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 ( -