From a810db36415daf8923729479eb4bf4fe306b96aa Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 09:11:28 +0200 Subject: [PATCH 01/33] feat(breadcrumb_badges): display badge when editing is unlocked --- .../src/translations/en/translation.json | 1 + apps/client/src/widgets/BreadcrumbBadges.tsx | 18 +++++++++++++----- apps/client/src/widgets/react/hooks.tsx | 6 +++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 3dcd231bc..f5f96960a 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2133,6 +2133,7 @@ "breadcrumb_badges": { "read_only_explicit": "Read-only", "read_only_auto": "Auto read-only", + "read_only_temporarily_disabled": "Temporarily editable", "shared_publicly": "Shared publicly", "shared_locally": "Shared locally" } diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 3bb3c06b6..2f00771f8 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -20,14 +20,22 @@ function ReadOnlyBadge() { const { note, noteContext } = useNoteContext(); const { isReadOnly, enableEditing } = useIsNoteReadOnly(note, noteContext); const isExplicitReadOnly = note?.isLabelTruthy("readOnly"); + const isTemporarilyEditable = noteContext?.viewScope?.readOnlyTemporarilyDisabled; - return (isReadOnly && - enableEditing(false)} + > + {t("breadcrumb_badges.read_only_temporarily_disabled")} + ; + } else if (isReadOnly) { + return enableEditing()}> {isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit") : t("breadcrumb_badges.read_only_auto")} - - ); + ; + } } function ShareBadge() { diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index fbcd7095e..5fe9ed883 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -845,9 +845,9 @@ export function useGlobalShortcut(keyboardShortcut: string | null | undefined, h export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: NoteContext | undefined) { const [ isReadOnly, setIsReadOnly ] = useState(undefined); - const enableEditing = useCallback(() => { + const enableEditing = useCallback((enabled = true) => { if (noteContext?.viewScope) { - noteContext.viewScope.readOnlyTemporarilyDisabled = true; + noteContext.viewScope.readOnlyTemporarilyDisabled = enabled; appContext.triggerEvent("readOnlyTemporarilyDisabled", {noteContext}); } }, [noteContext]); @@ -862,7 +862,7 @@ export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: N useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => { if (noteContext?.ntxId === eventNoteContext.ntxId) { - setIsReadOnly(false); + setIsReadOnly(!noteContext.viewScope?.readOnlyTemporarilyDisabled); } }); From efe7fc0ee7983038d767fa4e722f1b94c688f3ef Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 09:12:57 +0200 Subject: [PATCH 02/33] chore(layout): hide breadcrumb badges if not on new layout --- apps/client/src/layouts/desktop_layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 076a76522..e3415bf4b 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -140,7 +140,7 @@ export default class DesktopLayout { .class("breadcrumb-row") .cssBlock(".breadcrumb-row > * { margin: 5px; }") .child() - .child() + .optChild(isNewLayout, ) .child() .child() .child() From 61592716f99f7dd333dbc517aa5ac47a6abbff35 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 09:27:44 +0200 Subject: [PATCH 03/33] feat(breadcrumb_badges): add tooltips for the badges --- .../src/translations/en/translation.json | 7 ++++- apps/client/src/widgets/BreadcrumbBadges.tsx | 30 +++++++++++++++---- apps/client/src/widgets/shared_info.tsx | 6 ++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index f5f96960a..270b58299 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2132,9 +2132,14 @@ }, "breadcrumb_badges": { "read_only_explicit": "Read-only", + "read_only_explicit_description": "This note has been manually set to read-only.\nClick to edit it temporarily.", "read_only_auto": "Auto read-only", + "read_only_auto_description": "This note was set automatically to read-only mode for performance reasons. This automatic limit is adjustable from settings.\n\nClick to edit it temporarily.", "read_only_temporarily_disabled": "Temporarily editable", + "read_only_temporarily_disabled_description": "This note is currently editable, but it is normally read-only. The note will go back to being read-only as soon as you navigate to another note.\n\nClick to re-enable read-only mode.", "shared_publicly": "Shared publicly", - "shared_locally": "Shared locally" + "shared_publicly_description": "This note has been published online at {{- link}}, and is publicly accessible.\n\nClick to navigate to the shared note.", + "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." } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 2f00771f8..e4d0ef55e 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -1,11 +1,13 @@ import "./BreadcrumbBadges.css"; -import { ComponentChildren } from "preact"; -import { useIsNoteReadOnly, useNoteContext } from "./react/hooks"; +import { ComponentChildren, MouseEventHandler } from "preact"; +import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; import { useShareInfo } from "./shared_info"; import clsx from "clsx"; import { t } from "../services/i18n"; +import { useRef } from "preact/hooks"; +import { goToLinkExt } from "../services/link"; export default function BreadcrumbBadges() { return ( @@ -25,6 +27,7 @@ function ReadOnlyBadge() { if (isTemporarilyEditable) { return enableEditing(false)} > {t("breadcrumb_badges.read_only_temporarily_disabled")} @@ -32,7 +35,9 @@ function ReadOnlyBadge() { } else if (isReadOnly) { return enableEditing()}> + tooltip={isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit_description") : t("breadcrumb_badges.read_only_auto_description")} + onClick={() => enableEditing()} + > {isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit") : t("breadcrumb_badges.read_only_auto")} ; } @@ -40,20 +45,35 @@ function ReadOnlyBadge() { function ShareBadge() { const { note } = useNoteContext(); - const { isSharedExternally, link } = useShareInfo(note); + const { isSharedExternally, link, linkHref } = useShareInfo(note); return (link && linkHref && goToLinkExt(e, linkHref)} > {isSharedExternally ? t("breadcrumb_badges.shared_publicly") : t("breadcrumb_badges.shared_locally")} ); } -function Badge({ icon, children, onClick }: { icon: string, children: ComponentChildren, onClick?: () => void }) { +function Badge({ icon, children, tooltip, onClick }: { icon: string, tooltip: string, children: ComponentChildren, onClick?: MouseEventHandler }) { + const containerRef = useRef(null); + useStaticTooltip(containerRef, { + placement: "bottom", + fallbackPlacements: [ "bottom" ], + animation: false, + html: true, + title: tooltip + }); + return (
diff --git a/apps/client/src/widgets/shared_info.tsx b/apps/client/src/widgets/shared_info.tsx index 954ceb5f0..cd6cf78f4 100644 --- a/apps/client/src/widgets/shared_info.tsx +++ b/apps/client/src/widgets/shared_info.tsx @@ -26,6 +26,7 @@ export default function SharedInfo() { export function useShareInfo(note: FNote | null | undefined) { const [ link, setLink ] = useState(); + const [ linkHref, setLinkHref ] = useState(); const [ syncServerHost ] = useTriliumOption("syncServerHost"); function refresh() { @@ -52,9 +53,10 @@ export function useShareInfo(note: FNote | null | undefined) { } setLink(`${link}`); + setLinkHref(link); } - useEffect(refresh, [ note ]); + useEffect(refresh, [ note, syncServerHost ]); useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getAttributeRows().find((attr) => attr.name?.startsWith("_share") && attributes.isAffecting(attr, note))) { refresh(); @@ -63,7 +65,7 @@ export function useShareInfo(note: FNote | null | undefined) { } }); - return { link, isSharedExternally: !!syncServerHost }; + return { link, linkHref, isSharedExternally: !!syncServerHost }; } function getShareId(note: FNote) { From b014ea8950ec15b6cb64ab3f6f366ad08b27c0ae Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 09:38:55 +0200 Subject: [PATCH 04/33] feat(breadcrumb_badges): add colors to the badges --- apps/client/src/widgets/BreadcrumbBadges.css | 10 +++++++--- apps/client/src/widgets/BreadcrumbBadges.tsx | 7 +++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index c7e461113..58ffe2cff 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -9,15 +9,19 @@ padding: 2px 6px; border-radius: 12px; font-size: 0.75em; - background-color: var(--badge-background-color); - color: var(--badge-text-color); + background-color: var(--color, --badge-background-color); + color: white; &.clickable { cursor: pointer; &:hover { - background-color: var(--badge-background-hover-color); + background-color: color-mix(in srgb, var(--color, --badge-background-color) 80%, black); } } + + &.temporarily-editable-badge { --color: #4fa52b; } + &.read-only-badge { --color: #e33f3b; } + &.share-badge { --color: #3b82f6; } } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index e4d0ef55e..ffa922063 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -28,6 +28,7 @@ function ReadOnlyBadge() { return enableEditing(false)} > {t("breadcrumb_badges.read_only_temporarily_disabled")} @@ -36,6 +37,7 @@ function ReadOnlyBadge() { return enableEditing()} > {isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit") : t("breadcrumb_badges.read_only_auto")} @@ -54,6 +56,7 @@ function ShareBadge() { t("breadcrumb_badges.shared_publicly_description", { link }) : t("breadcrumb_badges.shared_locally_description", { link }) } + className="share-badge" onClick={(e) => linkHref && goToLinkExt(e, linkHref)} > {isSharedExternally ? t("breadcrumb_badges.shared_publicly") : t("breadcrumb_badges.shared_locally")} @@ -61,7 +64,7 @@ function ShareBadge() { ); } -function Badge({ icon, children, tooltip, onClick }: { icon: string, tooltip: string, children: ComponentChildren, onClick?: MouseEventHandler }) { +function Badge({ icon, className, children, tooltip, onClick }: { icon: string, className: string, tooltip: string, children: ComponentChildren, onClick?: MouseEventHandler }) { const containerRef = useRef(null); useStaticTooltip(containerRef, { placement: "bottom", @@ -74,7 +77,7 @@ function Badge({ icon, children, tooltip, onClick }: { icon: string, tooltip: st return (
  From 40b5e4d5496036a821f1287c77bdf2b2b425c9d9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 09:47:05 +0200 Subject: [PATCH 05/33] feat(breadcrumb_badges): proper link handling support --- apps/client/src/translations/en/translation.json | 4 ++-- apps/client/src/widgets/BreadcrumbBadges.css | 5 +++++ apps/client/src/widgets/BreadcrumbBadges.tsx | 13 ++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 270b58299..d5b69b37f 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2138,8 +2138,8 @@ "read_only_temporarily_disabled": "Temporarily editable", "read_only_temporarily_disabled_description": "This note is currently editable, but it is normally read-only. The note will go back to being read-only as soon as you navigate to another note.\n\nClick to re-enable read-only mode.", "shared_publicly": "Shared publicly", - "shared_publicly_description": "This note has been published online at {{- link}}, and is publicly accessible.\n\nClick to navigate to the shared note.", + "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." + "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." } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index 58ffe2cff..0090f1cf9 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -23,5 +23,10 @@ &.temporarily-editable-badge { --color: #4fa52b; } &.read-only-badge { --color: #e33f3b; } &.share-badge { --color: #3b82f6; } + + a { + color: inherit; + text-decoration: none; + } } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index ffa922063..8e79aeaff 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -7,7 +7,6 @@ import { useShareInfo } from "./shared_info"; import clsx from "clsx"; import { t } from "../services/i18n"; import { useRef } from "preact/hooks"; -import { goToLinkExt } from "../services/link"; export default function BreadcrumbBadges() { return ( @@ -57,14 +56,14 @@ function ShareBadge() { t("breadcrumb_badges.shared_locally_description", { link }) } className="share-badge" - onClick={(e) => linkHref && goToLinkExt(e, linkHref)} + href={linkHref} > {isSharedExternally ? t("breadcrumb_badges.shared_publicly") : t("breadcrumb_badges.shared_locally")} ); } -function Badge({ icon, className, children, tooltip, onClick }: { icon: string, className: string, tooltip: string, children: ComponentChildren, onClick?: MouseEventHandler }) { +function Badge({ icon, className, children, tooltip, onClick, href }: { icon: string, className: string, tooltip: string, children: ComponentChildren, onClick?: MouseEventHandler, href?: string }) { const containerRef = useRef(null); useStaticTooltip(containerRef, { placement: "bottom", @@ -74,14 +73,18 @@ function Badge({ icon, className, children, tooltip, onClick }: { icon: string, title: tooltip }); + const content = <> +   + {children} + ; + return (
-   - {children} + {href ? {content} : content}
); } From 52bb4d7a0e62313182d93c45cddae216284f3364 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 09:52:46 +0200 Subject: [PATCH 06/33] feat(breadcrumb_badges): make badge not wrap-around --- apps/client/src/widgets/BreadcrumbBadges.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index 0090f1cf9..ecf602a96 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -11,6 +11,10 @@ font-size: 0.75em; background-color: var(--color, --badge-background-color); color: white; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; &.clickable { cursor: pointer; From 16a73b0848e4975f148bdda532dd005aa06c391a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 11:03:12 +0200 Subject: [PATCH 07/33] fix(popup_editor): wrong margin for title --- apps/client/src/widgets/dialogs/PopupEditor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css index 8d1464bed..136bc5015 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.css +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -31,6 +31,7 @@ body.mobile .modal.popup-editor-dialog .modal-dialog { flex-grow: 1; display: flex; align-items: center; + margin-block: 0; } .modal.popup-editor-dialog .modal-header .note-title-widget { From 3262e3490a269dec3c4a09bc2bb465f65d06272d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 11:10:26 +0200 Subject: [PATCH 08/33] feat(breadcrumb_badges): integrate into quick edit --- apps/client/src/layouts/layout_commons.tsx | 2 +- apps/client/src/widgets/BreadcrumbBadges.css | 5 +- .../src/widgets/dialogs/PopupEditor.tsx | 59 +++++++++++-------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/apps/client/src/layouts/layout_commons.tsx b/apps/client/src/layouts/layout_commons.tsx index 3620d495d..50550ea4b 100644 --- a/apps/client/src/layouts/layout_commons.tsx +++ b/apps/client/src/layouts/layout_commons.tsx @@ -52,5 +52,5 @@ export function applyModals(rootContainer: RootContainer) { .child() .child() .child() - .child() + .child(); } diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index ecf602a96..f9f42612f 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -1,7 +1,10 @@ .component.breadcrumb-badges { + contain: none; +} + +.breadcrumb-badges { display: flex; gap: 5px; - contain: none; .breadcrumb-badge { display: flex; diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index c85dcd3b3..152afaa4a 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -1,26 +1,32 @@ -import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; -import Modal from "../react/Modal"; import "./PopupEditor.css"; -import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks"; -import NoteTitleWidget from "../note_title"; -import NoteIcon from "../note_icon"; -import NoteContext from "../../components/note_context"; -import { NoteContextContext, ParentComponent } from "../react/react_utils"; -import NoteDetail from "../NoteDetail"; + import { ComponentChildren } from "preact"; +import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; + +import appContext from "../../components/app_context"; +import NoteContext from "../../components/note_context"; +import froca from "../../services/froca"; +import { t } from "../../services/i18n"; +import tree from "../../services/tree"; +import utils from "../../services/utils"; import NoteList from "../collections/NoteList"; -import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter"; -import FormattingToolbar from "../ribbon/FormattingToolbar"; -import PromotedAttributes from "../PromotedAttributes"; import FloatingButtons from "../FloatingButtons"; import { DESKTOP_FLOATING_BUTTONS, MOBILE_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions"; -import utils from "../../services/utils"; -import tree from "../../services/tree"; -import froca from "../../services/froca"; +import NoteIcon from "../note_icon"; +import NoteTitleWidget from "../note_title"; +import NoteDetail from "../NoteDetail"; +import PromotedAttributes from "../PromotedAttributes"; +import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks"; +import Modal from "../react/Modal"; +import { NoteContextContext, ParentComponent } from "../react/react_utils"; import ReadOnlyNoteInfoBar from "../ReadOnlyNoteInfoBar"; +import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter"; +import FormattingToolbar from "../ribbon/FormattingToolbar"; import MobileEditorToolbar from "../type_widgets/text/mobile_editor_toolbar"; -import { t } from "../../services/i18n"; -import appContext from "../../components/app_context"; +import BreadcrumbBadges from "../BreadcrumbBadges"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; + +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export default function PopupEditor() { const [ shown, setShown ] = useState(false); @@ -61,7 +67,10 @@ export default function PopupEditor() { } + title={<> + + {isNewLayout && } + } customTitleBarButtons={[{ iconClassName: "bx-expand-alt", title: t("popup-editor.maximize"), @@ -75,19 +84,17 @@ export default function PopupEditor() { className="popup-editor-dialog" size="lg" show={shown} - onShown={() => { - parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId }); - }} + onShown={() => parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId })} onHidden={() => setShown(false)} keepInDom // needed for faster loading noFocus // automatic focus breaks block popup > - + {!isNewLayout && } {isMobile - ? - : } + ? + : } @@ -95,7 +102,7 @@ export default function PopupEditor() { - ) + ); } export function DialogWrapper({ children }: { children: ComponentChildren }) { @@ -107,7 +114,7 @@ export function DialogWrapper({ children }: { children: ComponentChildren }) {
{children}
- ) + ); } export function TitleRow() { @@ -116,5 +123,5 @@ export function TitleRow() {
- ) + ); } From 66008489c44c61c1671b5fb14da7214c858eb959 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 11:21:06 +0200 Subject: [PATCH 09/33] chore(breadcrumb_badges): fake backlink widget --- .../src/translations/en/translation.json | 6 +++++- apps/client/src/widgets/BreadcrumbBadges.css | 1 + apps/client/src/widgets/BreadcrumbBadges.tsx | 21 ++++++++++++++----- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index d5b69b37f..dfce263d0 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2140,6 +2140,10 @@ "shared_publicly": "Shared publicly", "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." + "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." } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index f9f42612f..0e0a564e8 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -30,6 +30,7 @@ &.temporarily-editable-badge { --color: #4fa52b; } &.read-only-badge { --color: #e33f3b; } &.share-badge { --color: #3b82f6; } + &.backlinks-badge { color: var(--badge-text-color); } a { color: inherit; diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 8e79aeaff..8945b447b 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -1,18 +1,20 @@ import "./BreadcrumbBadges.css"; +import clsx from "clsx"; import { ComponentChildren, MouseEventHandler } from "preact"; +import { useRef } from "preact/hooks"; + +import { t } from "../services/i18n"; import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; import { useShareInfo } from "./shared_info"; -import clsx from "clsx"; -import { t } from "../services/i18n"; -import { useRef } from "preact/hooks"; export default function BreadcrumbBadges() { return (
+
); } @@ -63,7 +65,16 @@ function ShareBadge() { ); } -function Badge({ icon, className, children, tooltip, onClick, href }: { icon: string, className: string, tooltip: string, children: ComponentChildren, onClick?: MouseEventHandler, href?: string }) { +function BacklinksBadge() { + const count = 1; + return ( + + {t("breadcrumb_badges.backlinks", { count })} + + ); +} + +function Badge({ icon, className, children, tooltip, onClick, href }: { icon?: string, className: string, tooltip: string, children: ComponentChildren, onClick?: MouseEventHandler, href?: string }) { const containerRef = useRef(null); useStaticTooltip(containerRef, { placement: "bottom", @@ -74,7 +85,7 @@ function Badge({ icon, className, children, tooltip, onClick, href }: { icon: st }); const content = <> -   + {icon && <> } {children} ; From b03e6c3b198b3c710bec8a93c5d1de2af77080c2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 11:41:14 +0200 Subject: [PATCH 10/33] chore(breadcrumb_badges/backlinks): display list of backlinks on click --- apps/client/src/widgets/BreadcrumbBadges.css | 12 ++++ apps/client/src/widgets/BreadcrumbBadges.tsx | 66 ++++++++++++++----- .../widgets/FloatingButtonsDefinitions.tsx | 2 +- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index 0e0a564e8..5dbfd74fc 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -37,4 +37,16 @@ text-decoration: none; } } + + .dropdown { + &.dropdown-backlinks-badge .dropdown-menu { + min-width: 500px; + } + + .btn { + border: 0; + margin: 0; + padding: 0; + } + } } diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 8945b447b..9c066f04e 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -5,6 +5,8 @@ import { ComponentChildren, MouseEventHandler } from "preact"; import { useRef } from "preact/hooks"; import { t } from "../services/i18n"; +import { BacklinksList } from "./FloatingButtonsDefinitions"; +import Dropdown, { DropdownProps } from "./react/Dropdown"; import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; import { useShareInfo } from "./shared_info"; @@ -28,21 +30,19 @@ function ReadOnlyBadge() { if (isTemporarilyEditable) { return enableEditing(false)} - > - {t("breadcrumb_badges.read_only_temporarily_disabled")} - ; + />; } else if (isReadOnly) { return enableEditing()} - > - {isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit") : t("breadcrumb_badges.read_only_auto")} - ; + />; } } @@ -53,28 +53,45 @@ function ShareBadge() { return (link && - {isSharedExternally ? t("breadcrumb_badges.shared_publicly") : t("breadcrumb_badges.shared_locally")} - + /> ); } function BacklinksBadge() { + const { note } = useNoteContext(); const count = 1; - return ( - - {t("breadcrumb_badges.backlinks", { count })} - + return (note && count > 0 && + + + ); } -function Badge({ icon, className, children, tooltip, onClick, href }: { icon?: string, className: string, tooltip: string, children: ComponentChildren, onClick?: MouseEventHandler, href?: string }) { +interface BadgeProps { + text: string; + icon?: string; + className: string; + tooltip?: string; + onClick?: MouseEventHandler; + href?: string; +} + +function Badge({ icon, className, text, tooltip, onClick, href }: BadgeProps) { const containerRef = useRef(null); useStaticTooltip(containerRef, { placement: "bottom", @@ -86,7 +103,7 @@ function Badge({ icon, className, children, tooltip, onClick, href }: { icon?: s const content = <> {icon && <> } - {children} + {text} ; return ( @@ -99,3 +116,22 @@ function Badge({ icon, className, children, tooltip, onClick, href }: { icon?: s
); } + +function BadgeWithDropdown({ children, tooltip, className, dropdownOptions, ...props }: BadgeProps & { + children: ComponentChildren, + dropdownOptions?: Partial +}) { + return ( + } + noDropdownListStyle + noSelectButtonStyle + hideToggleArrow + title={tooltip} + titlePosition="bottom" + dropdownOptions={{ popperConfig: { placement: "bottom" } }} + {...dropdownOptions} + >{children} + ); +} diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 65743984e..e1e60ccc3 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -355,7 +355,7 @@ function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) { ); } -function BacklinksList({ note }: { note: FNote }) { +export function BacklinksList({ note }: { note: FNote }) { const [ backlinks, setBacklinks ] = useState([]); function refresh() { From 42fc128f9726156a3b320f1fab244f959df6e83c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 11:49:47 +0200 Subject: [PATCH 11/33] chore(breadcrumb_badges/backlinks): display actual count of backlinks --- apps/client/src/widgets/BreadcrumbBadges.tsx | 6 +-- .../widgets/FloatingButtonsDefinitions.tsx | 47 +++++++++++-------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 9c066f04e..ec9dc2927 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -5,7 +5,7 @@ import { ComponentChildren, MouseEventHandler } from "preact"; import { useRef } from "preact/hooks"; import { t } from "../services/i18n"; -import { BacklinksList } from "./FloatingButtonsDefinitions"; +import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "./react/Dropdown"; import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks"; import Icon from "./react/Icon"; @@ -65,8 +65,8 @@ function ShareBadge() { } function BacklinksBadge() { - const { note } = useNoteContext(); - const count = 1; + const { note, viewScope } = useNoteContext(); + const count = useBacklinkCount(note, viewScope?.viewMode === "default"); return (note && count > 0 && (null); - - function refresh() { - if (!isDefaultViewMode) return; - - server.get(`note-map/${note.noteId}/backlink-count`).then(resp => { - setBacklinkCount(resp.count); - }); - } - - useEffect(() => refresh(), [ note ]); - useTriliumEvent("entitiesReloaded", ({ loadResults }) => { - if (needsRefresh(note, loadResults)) refresh(); - }); + const backlinkCount = useBacklinkCount(note, isDefaultViewMode); // Determine the max height of the container. const { windowHeight } = useWindowSize(); @@ -336,7 +326,7 @@ function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) { } }, [ popupOpen, windowHeight ]); - const isEnabled = isDefaultViewMode && backlinkCount > 0; + const isEnabled = !isNewLayout && isDefaultViewMode && backlinkCount > 0; return (isEnabled &&
{ + if (!note || !isDefaultViewMode) return; + + server.get(`note-map/${note.noteId}/backlink-count`).then(resp => { + setBacklinkCount(resp.count); + }); + }, [ isDefaultViewMode, note ]); + + useEffect(() => refresh(), [ note, isDefaultViewMode, refresh ]); + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { + if (note && needsRefresh(note, loadResults)) refresh(); + }); + + return backlinkCount; +} + export function BacklinksList({ note }: { note: FNote }) { const [ backlinks, setBacklinks ] = useState([]); @@ -362,8 +371,8 @@ export function BacklinksList({ note }: { note: FNote }) { server.get(`note-map/${note.noteId}/backlinks`).then(async (backlinks) => { // prefetch all const noteIds = backlinks - .filter(bl => "noteId" in bl) - .map((bl) => bl.noteId); + .filter(bl => "noteId" in bl) + .map((bl) => bl.noteId); await froca.getNotes(noteIds); setBacklinks(backlinks); }); From 737711e5eb72180fc73c98ffd48f318de8146c35 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 11:56:34 +0200 Subject: [PATCH 12/33] fix(layout): weird title in full-width & attachments --- apps/client/src/widgets/note_title.css | 55 +++++++++++++++----------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 7d39b6b02..566fb72d3 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -29,30 +29,39 @@ body.desktop .note-title-widget input.note-title { font-size: 180%; } -body.experimental-feature-new-layout .title-row, -body.experimental-feature-new-layout .title-details { - max-width: var(--max-content-width); -} +body.experimental-feature-new-layout { + .title-row, + .title-details { + max-width: var(--max-content-width); + } -body.experimental-feature-new-layout .title-row { - margin-top: 2em; - margin-left: 12px; -} + .title-row { + margin-top: 2em; + margin-left: 12px; + } -body.experimental-feature-new-layout .title-details { - margin-top: 0; - contain: none; - padding: 0; - padding-inline-start: 24px; - opacity: 0.85; - display: flex; - gap: 0.25em; - margin: 0; - list-style-type: none; - margin-bottom: 2em; -} + .title-details { + margin-top: 0; + contain: none; + padding: 0; + padding-inline-start: 24px; + opacity: 0.85; + display: flex; + gap: 0.25em; + margin: 0; + list-style-type: none; + margin-bottom: 2em; + } -body.experimental-feature-new-layout.prefers-centered-content .title-row, -body.experimental-feature-new-layout.prefers-centered-content .title-details { - margin-inline: auto; + .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)) { + .title-row, + .title-details { + width: 100%; + } + } + + &.prefers-centered-content .title-row, + &.prefers-centered-content .title-details { + margin-inline: auto; + } } From 4d7d6429524fc5f1d08eef044a32b2ce78722e85 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 11:58:17 +0200 Subject: [PATCH 13/33] fix(layout): floating toolbar displayed in attachments --- 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 280f7cdda..ab351637d 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -22,7 +22,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ { title: t("classic_editor_toolbar.title"), icon: "bx bx-text", - show: async ({ note, noteContext }) => note?.type === "text" + show: async ({ note, noteContext }) => note?.type === "text" && noteContext?.viewScope?.viewMode === "default" && options.get("textNoteEditorType") === "ckeditor-classic" && !(await noteContext?.isReadOnly()), toggleCommand: "toggleRibbonTabClassicEditor", From be190bfe3305b38432500d068aa64040649c0911 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 12:06:05 +0200 Subject: [PATCH 14/33] feat(layout): improve layout for full-height notes --- apps/client/src/widgets/note_title.css | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 566fb72d3..d147e647d 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -57,6 +57,17 @@ body.experimental-feature-new-layout { .title-row, .title-details { width: 100%; + max-width: unset; + } + + .title-row { + margin-top: 0; + } + + .title-details { + margin-bottom: 0.25em; + padding-inline-start: 15px; + opacity: 0.65; } } From 10cb7c8d6a97af86da016c517e0bc1deff7aeb9f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 12:10:32 +0200 Subject: [PATCH 15/33] feat(note_title_details): hide creation dates on hidden notes --- apps/client/src/widgets/NoteTitleDetails.tsx | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/NoteTitleDetails.tsx b/apps/client/src/widgets/NoteTitleDetails.tsx index 793a3a593..e0cfc00c6 100644 --- a/apps/client/src/widgets/NoteTitleDetails.tsx +++ b/apps/client/src/widgets/NoteTitleDetails.tsx @@ -1,3 +1,5 @@ +import { type ComponentChild } from "preact"; + import { t } from "../services/i18n"; import { formatDateTime } from "../utils/formatters"; import { useNoteContext } from "./react/hooks"; @@ -7,17 +9,20 @@ import { useNoteMetadata } from "./ribbon/NoteInfoTab"; export default function NoteTitleDetails() { const { note } = useNoteContext(); const { metadata } = useNoteMetadata(note); + const isHiddenNote = note?.noteId.startsWith("_"); + + const items: ComponentChild[] = [ + (!isHiddenNote && metadata?.dateCreated &&
  • + {t("note_title.created_on", { date: formatDateTime(metadata.dateCreated, "medium", "none")} )} +
  • ), + (!isHiddenNote && metadata?.dateModified &&
  • + {t("note_title.last_modified", { date: formatDateTime(metadata.dateModified, "medium", "none")} )} +
  • ) + ].filter(item => !!item); return (
    - {joinElements([ - metadata?.dateCreated &&
  • - {t("note_title.created_on", { date: formatDateTime(metadata.dateCreated, "medium", "none")} )} -
  • , - metadata?.dateModified &&
  • - {t("note_title.last_modified", { date: formatDateTime(metadata.dateModified, "medium", "none")} )} -
  • - ], " • ")} + {joinElements(items, " • ")}
    ); } From 4031332b98b19955baae59d3299ee657608dfa7f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 12:25:38 +0200 Subject: [PATCH 16/33] feat(note_title_details): tooltips for values --- .../src/translations/en/translation.json | 4 +- apps/client/src/widgets/NoteTitleDetails.tsx | 46 ++++++++++++++++--- apps/client/src/widgets/note_title.css | 4 ++ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index dfce263d0..f11ec921c 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1750,8 +1750,8 @@ }, "note_title": { "placeholder": "type note's title here...", - "created_on": "Created on {{date}}", - "last_modified": "Last modified on {{date}}" + "created_on": "Created on ", + "last_modified": "Last modified on " }, "search_result": { "no_notes_found": "No notes have been found for given search parameters.", diff --git a/apps/client/src/widgets/NoteTitleDetails.tsx b/apps/client/src/widgets/NoteTitleDetails.tsx index e0cfc00c6..9a7bb054b 100644 --- a/apps/client/src/widgets/NoteTitleDetails.tsx +++ b/apps/client/src/widgets/NoteTitleDetails.tsx @@ -2,9 +2,11 @@ import { type ComponentChild } from "preact"; import { t } from "../services/i18n"; import { formatDateTime } from "../utils/formatters"; -import { useNoteContext } from "./react/hooks"; +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"; export default function NoteTitleDetails() { const { note } = useNoteContext(); @@ -12,12 +14,18 @@ export default function NoteTitleDetails() { const isHiddenNote = note?.noteId.startsWith("_"); const items: ComponentChild[] = [ - (!isHiddenNote && metadata?.dateCreated &&
  • - {t("note_title.created_on", { date: formatDateTime(metadata.dateCreated, "medium", "none")} )} -
  • ), - (!isHiddenNote && metadata?.dateModified &&
  • - {t("note_title.last_modified", { date: formatDateTime(metadata.dateModified, "medium", "none")} )} -
  • ) + (!isHiddenNote && metadata?.dateCreated && + ), + (!isHiddenNote && metadata?.dateModified && + ) ].filter(item => !!item); return ( @@ -26,3 +34,27 @@ export default function NoteTitleDetails() {
    ); } + +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_title.css b/apps/client/src/widgets/note_title.css index d147e647d..52ca6cbb7 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -51,6 +51,10 @@ body.experimental-feature-new-layout { margin: 0; list-style-type: none; margin-bottom: 2em; + + span.value { + font-weight: 500; + } } .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)) { From c4f483c25068f6758440183153862780cc424fa5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 12:29:12 +0200 Subject: [PATCH 17/33] feat(options/advanced): automatically refresh --- apps/client/src/widgets/react/hooks.tsx | 9 +++++---- .../client/src/widgets/type_widgets/options/advanced.tsx | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 5fe9ed883..38f0a1967 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -201,7 +201,7 @@ export function useTriliumOptionBool(name: OptionNames, needsRefresh?: boolean): return [ (value === "true"), (newValue) => setValue(newValue ? "true" : "false") - ] + ]; } /** @@ -217,17 +217,18 @@ export function useTriliumOptionInt(name: OptionNames): [number, (newValue: numb return [ (parseInt(value, 10)), (newValue) => setValue(newValue) - ] + ]; } /** * Similar to {@link useTriliumOption}, but the object value is parsed to and from a JSON instead of a string. * * @param name the name of the option to listen for. + * @param needsRefresh whether to reload the frontend whenever the value is changed. * @returns an array where the first value is the current option value and the second value is the setter. */ -export function useTriliumOptionJson(name: OptionNames): [ T, (newValue: T) => Promise ] { - const [ value, setValue ] = useTriliumOption(name); +export function useTriliumOptionJson(name: OptionNames, needsRefresh?: boolean): [ T, (newValue: T) => Promise ] { + const [ value, setValue ] = useTriliumOption(name, needsRefresh); useDebugValue(name); return [ (JSON.parse(value) as T), diff --git a/apps/client/src/widgets/type_widgets/options/advanced.tsx b/apps/client/src/widgets/type_widgets/options/advanced.tsx index 958180063..4024a9d0e 100644 --- a/apps/client/src/widgets/type_widgets/options/advanced.tsx +++ b/apps/client/src/widgets/type_widgets/options/advanced.tsx @@ -158,7 +158,7 @@ function ExistingAnonymizedDatabases({ databases }: { databases: AnonymizedDbRes ))} - ) + ); } function VacuumDatabaseOptions() { @@ -175,11 +175,11 @@ function VacuumDatabaseOptions() { }} /> - ) + ); } function ExperimentalOptions() { - const [ enabledExperimentalFeatures, setEnabledExperimentalFeatures ] = useTriliumOptionJson("experimentalFeatures"); + const [ enabledExperimentalFeatures, setEnabledExperimentalFeatures ] = useTriliumOptionJson("experimentalFeatures", true); return ( From e556c090ffc839cf36e3e0fde816304bfd2d93d9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 12:40:06 +0200 Subject: [PATCH 18/33] fix(ribbon): attribute details not shown in new layout --- .../src/widgets/attribute_widgets/attribute_detail.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_detail.ts b/apps/client/src/widgets/attribute_widgets/attribute_detail.ts index 2a7a55aef..8ae3ae674 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_detail.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_detail.ts @@ -12,6 +12,7 @@ import shortcutService from "../../services/shortcuts.js"; import appContext from "../../components/app_context.js"; import type { Attribute } from "../../services/attribute_parser.js"; import { focusSavedElement, saveFocusedElement } from "../../services/focus.js"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features.js"; const TPL = /*html*/`
    @@ -309,6 +310,8 @@ interface SearchRelatedResponse { count: number; } +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); + export default class AttributeDetailWidget extends NoteContextAwareWidget { private $title!: JQuery; private $inputName!: JQuery; @@ -579,6 +582,13 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { .css("top", y - offset.top + 70) .css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000); + if (isNewLayout) { + this.$widget + .css("top", "unset") + .css("bottom", 70) + .css("max-height", "80vh"); + } + if (focus === "name") { this.$inputName.trigger("focus").trigger("select"); } From 63f7a78d311d8eb14d82af7916361533b32311b7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 12:46:23 +0200 Subject: [PATCH 19/33] chore(note_actions): use dedicated translation for note revisions --- apps/client/src/translations/en/translation.json | 1 + apps/client/src/widgets/ribbon/NoteActions.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index f11ec921c..2298f2f2b 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -689,6 +689,7 @@ "export_note": "Export note", "delete_note": "Delete note", "print_note": "Print note", + "view_revisions": "Note revisions...", "save_revision": "Save revision", "convert_into_attachment_failed": "Converting note '{{title}}' failed.", "convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.", diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 6adaf7f61..c10bda7cc 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -98,7 +98,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not } - + Date: Wed, 10 Dec 2025 12:50:05 +0200 Subject: [PATCH 20/33] feat(layout): minor improvements to title/icon alignment --- apps/client/src/widgets/note_title.css | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 52ca6cbb7..beb1d0fa3 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -33,18 +33,23 @@ body.experimental-feature-new-layout { .title-row, .title-details { max-width: var(--max-content-width); + padding: 0; + padding-inline-start: 24px; } .title-row { margin-top: 2em; margin-left: 12px; + + .note-icon-widget { + padding: 0; + width: 41px; + } } .title-details { margin-top: 0; contain: none; - padding: 0; - padding-inline-start: 24px; opacity: 0.85; display: flex; gap: 0.25em; @@ -62,6 +67,7 @@ body.experimental-feature-new-layout { .title-details { width: 100%; max-width: unset; + padding-inline-start: 15px; } .title-row { @@ -70,7 +76,6 @@ body.experimental-feature-new-layout { .title-details { margin-bottom: 0.25em; - padding-inline-start: 15px; opacity: 0.65; } } From 07b76b80f4f21bb0b7c6d23f04d170cd4865dfd6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 12:52:03 +0200 Subject: [PATCH 21/33] feat(layout): hide note details in attachment view --- apps/client/src/widgets/NoteTitleDetails.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/NoteTitleDetails.tsx b/apps/client/src/widgets/NoteTitleDetails.tsx index 9a7bb054b..58f4da0f7 100644 --- a/apps/client/src/widgets/NoteTitleDetails.tsx +++ b/apps/client/src/widgets/NoteTitleDetails.tsx @@ -1,6 +1,5 @@ import { type ComponentChild } from "preact"; -import { t } from "../services/i18n"; import { formatDateTime } from "../utils/formatters"; import { useNoteContext, useStaticTooltip } from "./react/hooks"; import { joinElements } from "./react/react_utils"; @@ -9,18 +8,19 @@ import { Trans } from "react-i18next"; import { useRef } from "preact/hooks"; export default function NoteTitleDetails() { - const { note } = useNoteContext(); + const { note, noteContext } = useNoteContext(); const { metadata } = useNoteMetadata(note); const isHiddenNote = note?.noteId.startsWith("_"); + const isDefaultView = noteContext?.viewScope?.viewMode === "default"; const items: ComponentChild[] = [ - (!isHiddenNote && metadata?.dateCreated && + (isDefaultView && !isHiddenNote && metadata?.dateCreated && ), - (!isHiddenNote && metadata?.dateModified && + (isDefaultView && !isHiddenNote && metadata?.dateModified && Date: Wed, 10 Dec 2025 13:03:06 +0200 Subject: [PATCH 22/33] feat(breadcrumb_badges): basic shrink support --- apps/client/src/widgets/BreadcrumbBadges.css | 29 ++++++++++++++++---- apps/client/src/widgets/BreadcrumbBadges.tsx | 4 +-- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index 5dbfd74fc..41116aa1e 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -5,19 +5,21 @@ .breadcrumb-badges { display: flex; gap: 5px; + min-width: 0; + flex-shrink: 1; + overflow: hidden; + --badge-radius: 12px; .breadcrumb-badge { display: flex; align-items: center; padding: 2px 6px; - border-radius: 12px; + border-radius: var(--badge-radius); font-size: 0.75em; - background-color: var(--color, --badge-background-color); + background-color: var(--color, var(--badge-background-color)); color: white; min-width: 0; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; + flex-shrink: 1; &.clickable { cursor: pointer; @@ -36,13 +38,30 @@ color: inherit; text-decoration: none; } + + > * { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } .dropdown { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + border-radius: var(--badge-radius); + &.dropdown-backlinks-badge .dropdown-menu { min-width: 500px; } + .breadcrumb-badge { + border-radius: 0; + } + .btn { border: 0; margin: 0; diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index ec9dc2927..35cda87dd 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -112,7 +112,7 @@ function Badge({ icon, className, text, tooltip, onClick, href }: BadgeProps) { className={clsx("breadcrumb-badge", className, { "clickable": !!onClick })} onClick={onClick} > - {href ? {content} : content} + {href ? {content} : {content}}
    ); } @@ -130,7 +130,7 @@ function BadgeWithDropdown({ children, tooltip, className, dropdownOptions, ...p hideToggleArrow title={tooltip} titlePosition="bottom" - dropdownOptions={{ popperConfig: { placement: "bottom" } }} + dropdownOptions={{ popperConfig: { placement: "bottom", strategy: "fixed" } }} {...dropdownOptions} >{children} ); From bd81db41172a1ae01a662d6d0072b3924d3f1434 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 13:33:33 +0200 Subject: [PATCH 23/33] feat(breadcrumb_row): improve badge fit on constrained width --- apps/client/src/widgets/Breadcrumb.css | 31 ++++++++++++++++++++ apps/client/src/widgets/BreadcrumbBadges.css | 2 +- apps/client/src/widgets/BreadcrumbBadges.tsx | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 5f3bc886b..9d702beff 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -4,6 +4,37 @@ min-height: 30px; align-items: center; padding: 10px; + container-type: inline-size; + + @container (max-width: 500px) { + .breadcrumb-badges { + flex-shrink: 0; + + >* { + flex-shrink: 0; + width: 18px; + } + + .dropdown { + button { + flex-shrink: 0; + } + } + + .breadcrumb-badge { + flex-shrink: 0; + padding: 0 2px; + + >* { + text-overflow: clip; + } + + .text { + display: none; + } + } + } + } } body.experimental-feature-new-layout .breadcrumb-row { diff --git a/apps/client/src/widgets/BreadcrumbBadges.css b/apps/client/src/widgets/BreadcrumbBadges.css index 41116aa1e..7d6405159 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.css +++ b/apps/client/src/widgets/BreadcrumbBadges.css @@ -16,7 +16,7 @@ padding: 2px 6px; border-radius: var(--badge-radius); font-size: 0.75em; - background-color: var(--color, var(--badge-background-color)); + background-color: var(--color, transparent); color: white; min-width: 0; flex-shrink: 1; diff --git a/apps/client/src/widgets/BreadcrumbBadges.tsx b/apps/client/src/widgets/BreadcrumbBadges.tsx index 35cda87dd..57377167e 100644 --- a/apps/client/src/widgets/BreadcrumbBadges.tsx +++ b/apps/client/src/widgets/BreadcrumbBadges.tsx @@ -103,7 +103,7 @@ function Badge({ icon, className, text, tooltip, onClick, href }: BadgeProps) { const content = <> {icon && <> } - {text} + {text} ; return ( From 49f008c46f8419a26b55aba92864d80b6de31e18 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 13:37:07 +0200 Subject: [PATCH 24/33] feat(breadcrumb_row): improve button fit on constrained width --- apps/client/src/widgets/Breadcrumb.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 9d702beff..03cad9f5a 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -7,6 +7,12 @@ container-type: inline-size; @container (max-width: 500px) { + .breadcrumb { + .btn.icon-action { + width: 16px; + } + } + .breadcrumb-badges { flex-shrink: 0; @@ -34,6 +40,10 @@ } } } + + .icon-action { + margin: 0; + } } } From aef0b03c34426cefb57b540d3c0e7a406331948d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 13:38:15 +0200 Subject: [PATCH 25/33] feat(breadcrumb_row): collapse badges sooner --- apps/client/src/widgets/Breadcrumb.css | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 03cad9f5a..24cb22009 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -6,13 +6,7 @@ padding: 10px; container-type: inline-size; - @container (max-width: 500px) { - .breadcrumb { - .btn.icon-action { - width: 16px; - } - } - + @container (max-width: 700px) { .breadcrumb-badges { flex-shrink: 0; @@ -40,6 +34,14 @@ } } } + } + + @container (max-width: 500px) { + .breadcrumb { + .btn.icon-action { + width: 16px; + } + } .icon-action { margin: 0; From 999315d3c605c60508d46a665ba8d1ea979e0bed Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 15:12:37 +0200 Subject: [PATCH 26/33] feat(breadcrumb): basic rename note support --- apps/client/src/widgets/Breadcrumb.css | 14 ++++++- apps/client/src/widgets/Breadcrumb.tsx | 54 ++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.css b/apps/client/src/widgets/Breadcrumb.css index 24cb22009..2bb58281f 100644 --- a/apps/client/src/widgets/Breadcrumb.css +++ b/apps/client/src/widgets/Breadcrumb.css @@ -96,11 +96,23 @@ body.experimental-feature-new-layout .breadcrumb-row { } .dropdown-item span, - .dropdown-item strong { + .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/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 38efb1364..651aafcfd 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -1,6 +1,6 @@ import "./Breadcrumb.css"; -import { useMemo } from "preact/hooks"; +import { useMemo, useState } from "preact/hooks"; import { Fragment } from "preact/jsx-runtime"; import NoteContext from "../components/note_context"; @@ -12,6 +12,8 @@ import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty } from "./ 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"; const COLLAPSE_THRESHOLD = 5; const INITIAL_ITEMS = 2; @@ -27,10 +29,7 @@ export default function Breadcrumb() { <> {notePath.slice(0, INITIAL_ITEMS).map((item, index) => ( - {index === 0 - ? - : - } + ))} @@ -38,7 +37,7 @@ export default function Breadcrumb() { {notePath.slice(-FINAL_ITEMS).map((item, index) => ( - + ))} @@ -47,7 +46,7 @@ export default function Breadcrumb() { {index === 0 ? - : + : } {(index < notePath.length - 1 || note?.hasChildren()) && } @@ -76,7 +75,7 @@ function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined ); } -function BreadcrumbItem({ notePath }: { notePath: string }) { +function BreadcrumbLink({ notePath }: { notePath: string }) { return ( froca.getNoteFromCache(noteId!)); + const [ isEditing, setIsEditing ] = useState(false); + const title = useNoteProperty(note, "title"); + + if (!note) return null; + + if (!isEditing) { + return ( + setIsEditing(true)} + >{title} + ); + } + + return ( + { return server.put(`notes/${noteId}/title`, { title: newTitle.trim() }); }} + dismiss={() => setIsEditing(false)} + /> + ); +} + +function BreadcrumbItem({ index, notePath, noteContext, notePathLength }: { index: number, notePathLength: number, notePath: string, noteContext: NoteContext | undefined }) { + if (index === 0) { + return ; + } + + if (index === notePathLength - 1) { + return ; + } + + return ; +} + function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) { return ( Date: Wed, 10 Dec 2025 15:23:42 +0200 Subject: [PATCH 27/33] style(layout): slightly smaller note title in full-height note type --- apps/client/src/widgets/note_title.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index beb1d0fa3..2a56f43eb 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -75,8 +75,9 @@ body.experimental-feature-new-layout { } .title-details { - margin-bottom: 0.25em; + margin-bottom: 0.2em; opacity: 0.65; + font-size: 0.8em; } } From a9b4e7b1e2d0a7ae83c21b81b3a9a720fc6b3c69 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 16:11:17 +0200 Subject: [PATCH 28/33] style(layout): apply heavy padding to title only in normal view --- apps/client/src/widgets/note_title.css | 12 ++++++++++-- apps/client/src/widgets/note_wrapper.ts | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 2a56f43eb..7e34aff23 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -38,7 +38,6 @@ body.experimental-feature-new-layout { } .title-row { - margin-top: 2em; margin-left: 12px; .note-icon-widget { @@ -55,13 +54,22 @@ body.experimental-feature-new-layout { gap: 0.25em; margin: 0; list-style-type: none; - margin-bottom: 2em; span.value { font-weight: 500; } } + .note-split.view-mode-default { + .title-row { + margin-top: 2em; + } + + .title-details { + margin-bottom: 2em; + } + } + .scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)) { .title-row, .title-details { diff --git a/apps/client/src/widgets/note_wrapper.ts b/apps/client/src/widgets/note_wrapper.ts index f3c61859d..d743d9ffa 100644 --- a/apps/client/src/widgets/note_wrapper.ts +++ b/apps/client/src/widgets/note_wrapper.ts @@ -62,6 +62,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.toggleClass(["bgfx", "options"], note.isOptions()); this.$widget.toggleClass("protected", note.isProtected); From 2060bb8cddd6727a192aa279f2519427c1b1099c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 16:14:40 +0200 Subject: [PATCH 29/33] feat(breadcrumb): show note preview --- apps/client/src/widgets/Breadcrumb.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 651aafcfd..1ac3ecb51 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -79,7 +79,6 @@ function BreadcrumbLink({ notePath }: { notePath: string }) { return ( ); } From 7c5df216855e1058ddd4fbe4f33d79426f7bb077 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 16:51:07 +0200 Subject: [PATCH 30/33] feat(note_actions): group development options --- apps/client/src/stylesheets/style.css | 5 ++ .../src/stylesheets/theme-next/base.css | 47 ++++++++++++++----- apps/client/src/widgets/react/FormList.tsx | 11 +++-- .../client/src/widgets/ribbon/NoteActions.tsx | 25 +++++----- 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 984349092..871b84bdc 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1321,6 +1321,11 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu { overflow: auto; } +.dropdown-submenu.dropstart > .dropdown-menu { + inset-inline-start: auto; + inset-inline-end: calc(100% - 2px); +} + body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { inset-inline-start: calc(-100% + 10px); } diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index 2f207ed44..78d432f19 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -89,13 +89,13 @@ * the color is adjusted based on the current color scheme (light or dark). The lightness * component of the color represented in the CIELAB color space, will be * constrained to a certain percentage defined below. - * + * * Note: the tree background may vary when background effects are enabled, so it is recommended * to maintain a higher contrast margin than on the usual note tree solid background. */ /* The maximum perceptual lightness for the custom color in the light theme (%): */ --tree-item-light-theme-max-color-lightness: 60; - + /* The minimum perceptual lightness for the custom color in the dark theme (%): */ --tree-item-dark-theme-min-color-lightness: 65; } @@ -165,7 +165,7 @@ body.desktop .dropdown-submenu .dropdown-menu { --menu-item-start-padding: 8px; --menu-item-end-padding: 22px; --menu-item-vertical-padding: 2px; - + padding-top: var(--menu-item-vertical-padding) !important; padding-bottom: var(--menu-item-vertical-padding) !important; padding-inline-start: var(--menu-item-start-padding) !important; @@ -176,6 +176,11 @@ body.desktop .dropdown-submenu .dropdown-menu { cursor: default !important; } +.dropdown-menu:has(> .dropdown-submenu.dropstart) > .dropdown-item { + padding-inline-end: var(--menu-item-start-padding) !important; + padding-inline-start: var(--menu-item-end-padding) !important; +} + :root .dropdown-item:focus-visible { outline: 2px solid var(--input-focus-outline-color) !important; background-color: transparent; @@ -249,7 +254,7 @@ html body .dropdown-item[disabled] { } /* Menu item arrow */ -.dropdown-menu .dropdown-toggle::after { +.dropdown-submenu:not(.dropstart) .dropdown-toggle::after { content: "\ed3b" !important; position: absolute; display: flex !important; @@ -265,6 +270,22 @@ html body .dropdown-item[disabled] { color: var(--menu-item-arrow-color) !important; } +.dropdown-submenu.dropstart .dropdown-toggle::before { + content: "\ea4d" !important; + position: absolute; + display: flex !important; + align-items: center; + justify-content: center; + top: 0; + inset-inline-start: 0; + margin: unset !important; + border: unset !important; + padding: 0 4px; + font-family: boxicons; + font-size: 1.2em; + color: var(--menu-item-arrow-color) !important; +} + body[dir=rtl] .dropdown-menu:not([data-popper-placement="bottom-start"]) .dropdown-toggle::after { content: "\ea4d" !important; } @@ -339,7 +360,7 @@ body.mobile .dropdown-menu { font-size: 1em !important; backdrop-filter: var(--dropdown-backdrop-filter); position: relative; - + .dropdown-toggle::after { top: 0.5em; right: var(--dropdown-menu-padding-horizontal); @@ -356,7 +377,7 @@ body.mobile .dropdown-menu { padding: var(--dropdown-menu-padding-vertical) var(--dropdown-menu-padding-horizontal) !important; background: var(--card-background-color); border-bottom: 1px solid var(--menu-item-delimiter-color) !important; - border-radius: 0; + border-radius: 0; } .dropdown-item:first-of-type, @@ -367,9 +388,9 @@ body.mobile .dropdown-menu { border-top-right-radius: 6px; } - .dropdown-item:last-of-type, + .dropdown-item:last-of-type, .dropdown-item:has(+ .dropdown-divider), - .dropdown-custom-item:last-of-type, + .dropdown-custom-item:last-of-type, .dropdown-custom-item:has(+ .dropdown-divider) { border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; @@ -392,10 +413,10 @@ body.mobile .dropdown-menu { --menu-background-color: --menu-submenu-mobile-background-color; --bs-dropdown-divider-margin-y: 0.25rem; border-radius: 0; - max-height: 0; + max-height: 0; transition: max-height 100ms ease-in; - display: block !important; - + display: block !important; + &.show { max-height: 1000px; padding: 0.5rem 0.75rem !important; @@ -405,7 +426,7 @@ body.mobile .dropdown-menu { &.submenu-open { .dropdown-toggle { padding-bottom: var(--dropdown-menu-padding-vertical); - } + } } } @@ -743,4 +764,4 @@ li.dropdown-item a.dropdown-item-button:focus-visible { .note-detail-empty .aa-suggestions div.aa-cursor { background: var(--hover-item-background-color); color: var(--hover-item-text-color); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 5faab055f..0eb6108b8 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -161,11 +161,16 @@ export function FormDropdownDivider() { return
    ; } -export function FormDropdownSubmenu({ icon, title, children }: { icon: string, title: ComponentChildren, children: ComponentChildren }) { +export function FormDropdownSubmenu({ icon, title, children, dropStart }: { + icon: string, + title: ComponentChildren, + children: ComponentChildren, + dropStart?: boolean +}) { const [ openOnMobile, setOpenOnMobile ] = useState(false); return ( -
  • +
  • { @@ -184,5 +189,5 @@ export function FormDropdownSubmenu({ icon, title, children }: { icon: string, t {children}
  • - ) + ); } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index c10bda7cc..12655262e 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -13,7 +13,7 @@ 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, FormListHeader, FormListItem } from "../react/FormList"; +import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList"; import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; @@ -114,23 +114,22 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { return ( - <> - + window.open(`/?print=#root/${note.noteId}`, "_blank")} >Open print page - {note.type === "text" && ( - { - noteContext?.getTextEditor(editor => { - editor.editing.view.change(() => { - throw new Error("Editor crashed."); - }); + { + noteContext?.getTextEditor(editor => { + editor.editing.view.change(() => { + throw new Error("Editor crashed."); }); - }}>Crash editor)} - + }); + }}>Crash editor + ); } From f7955a9040d1af73ded0521866e121dc701698f5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 17:02:11 +0200 Subject: [PATCH 31/33] fix(client/dropdown): tooltip flickering due to child elements --- apps/client/src/widgets/react/Dropdown.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/react/Dropdown.tsx b/apps/client/src/widgets/react/Dropdown.tsx index 5416e38ac..dec0660c0 100644 --- a/apps/client/src/widgets/react/Dropdown.tsx +++ b/apps/client/src/widgets/react/Dropdown.tsx @@ -117,8 +117,8 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi aria-expanded="false" id={id ?? ariaId} disabled={disabled} - onMouseOver={() => showTooltip()} - onMouseLeave={() => hideTooltip()} + onMouseEnter={showTooltip} + onMouseLeave={hideTooltip} {...buttonProps} > {text} From f1edf84f4d7e898972eda0ecd46a2632b30822e5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 17:13:52 +0200 Subject: [PATCH 32/33] fix(layout): title background for code notes --- apps/client/src/widgets/note_title.css | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css index 7e34aff23..8769c74ae 100644 --- a/apps/client/src/widgets/note_title.css +++ b/apps/client/src/widgets/note_title.css @@ -46,10 +46,14 @@ body.experimental-feature-new-layout { } } + .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); + } + .title-details { margin-top: 0; contain: none; - opacity: 0.85; display: flex; gap: 0.25em; margin: 0; @@ -62,11 +66,12 @@ body.experimental-feature-new-layout { .note-split.view-mode-default { .title-row { - margin-top: 2em; + padding-top: 2em; + box-sizing: content-box; } .title-details { - margin-bottom: 2em; + padding-bottom: 2em; } } From 5bb4621097db1e49d61f3e4930861b442cee9974 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 17:42:01 +0200 Subject: [PATCH 33/33] chore(layout): address requested changes --- apps/client/src/widgets/Breadcrumb.tsx | 5 ++++- apps/client/src/widgets/FloatingButtonsDefinitions.tsx | 2 +- apps/client/src/widgets/collections/board/index.tsx | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/Breadcrumb.tsx b/apps/client/src/widgets/Breadcrumb.tsx index 1ac3ecb51..83ff966b8 100644 --- a/apps/client/src/widgets/Breadcrumb.tsx +++ b/apps/client/src/widgets/Breadcrumb.tsx @@ -96,7 +96,10 @@ function BreadcrumbLastItem({ notePath }: { notePath: string }) { setIsEditing(true)} + onClick={(e) => { + e.preventDefault(); + setIsEditing(true); + }} >{title} ); } diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index b6ee70229..1c7a904a9 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -356,7 +356,7 @@ export function useBacklinkCount(note: FNote | null | undefined, isDefaultViewMo }); }, [ isDefaultViewMode, note ]); - useEffect(() => refresh(), [ note, isDefaultViewMode, refresh ]); + useEffect(() => refresh(), [ refresh ]); useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (note && needsRefresh(note, loadResults)) refresh(); }); diff --git a/apps/client/src/widgets/collections/board/index.tsx b/apps/client/src/widgets/collections/board/index.tsx index a50213a31..39e715f97 100644 --- a/apps/client/src/widgets/collections/board/index.tsx +++ b/apps/client/src/widgets/collections/board/index.tsx @@ -243,7 +243,7 @@ function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMo export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, isNewItem }: { currentValue?: string; placeholder?: string; - save: (newValue: string) => void; + save: (newValue: string) => void | Promise; dismiss: () => void; isNewItem?: boolean; mode?: "normal" | "multiline" | "relation";