From 8fa9c25f2af09b47ac340cf957c2890ecb16292b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:07:38 +0200 Subject: [PATCH 01/28] feat(widgets): menu item with toggle --- apps/client/src/widgets/react/FormList.css | 31 +++++++++++++++----- apps/client/src/widgets/react/FormList.tsx | 13 ++++++++ apps/client/src/widgets/react/FormToggle.tsx | 8 ++--- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.css b/apps/client/src/widgets/react/FormList.css index ae5c3340f..33922e7c6 100644 --- a/apps/client/src/widgets/react/FormList.css +++ b/apps/client/src/widgets/react/FormList.css @@ -1,9 +1,24 @@ -.dropdown-item .description { - font-size: small; - color: var(--muted-text-color); - white-space: normal; -} +.dropdown-item { + .description { + font-size: small; + color: var(--muted-text-color); + white-space: normal; + } -.dropdown-item span.bx { - flex-shrink: 0; -} \ No newline at end of file + span.bx { + flex-shrink: 0; + } + + .switch-widget { + flex-grow: 1; + width: 100%; + --switch-track-width: 40px; + --switch-track-height: 20px; + --switch-thumb-width: 12px; + --switch-thumb-height: var(--switch-thumb-width); + + .switch-name { + flex-grow: 1; + } + } +} diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 0eb6108b8..3f8147241 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -7,6 +7,7 @@ import { CommandNames } from "../../components/app_context"; import { useStaticTooltip } from "./hooks"; import { handleRightToLeftPlacement, isMobile } from "../../services/utils"; import clsx from "clsx"; +import FormToggle from "./FormToggle"; interface FormListOpts { children: ComponentChildren; @@ -132,6 +133,18 @@ export function FormListItem({ className, icon, value, title, active, disabled, ); } +export function FormListToggleableItem({ title, currentValue, onChange, ...props }: Omit & { + title: string; + currentValue: boolean; + onChange(newValue: boolean): void; +}) { + return ( + + + + ); +} + function FormListContent({ children, badges, description, disabled, disabledTooltip }: Pick) { return <> {children} diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx index 62069b3fe..ba7499f36 100644 --- a/apps/client/src/widgets/react/FormToggle.tsx +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -5,9 +5,9 @@ interface FormToggleProps { currentValue: boolean | null; onChange(newValue: boolean): void; switchOnName: string; - switchOnTooltip: string; + switchOnTooltip?: string; switchOffName: string; - switchOffTooltip: string; + switchOffTooltip?: string; helpPage?: string; disabled?: boolean; } @@ -37,5 +37,5 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc { helpPage && } - ) -} \ No newline at end of file + ); +} From b39a6bcc97cfd7672da295dbf19c2d6494430339 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:17:39 +0200 Subject: [PATCH 02/28] feat(widgets): prevent clicks in toggle from dismissing menu --- apps/client/src/widgets/react/FormList.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 3f8147241..95a105262 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -139,8 +139,12 @@ export function FormListToggleableItem({ title, currentValue, onChange, ...props onChange(newValue: boolean): void; }) { return ( - - + e.stopPropagation()}> + ); } From f18ac3a9234a912eb2657d9593193fcb6dcd0c55 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:20:23 +0200 Subject: [PATCH 03/28] feat(note_actions): integrate bookmark into new layout --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 54 +++++++++++-------- .../client/src/widgets/ribbon/NoteActions.tsx | 14 ++++- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 11a843233..dc14284f3 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -21,6 +21,9 @@ import Modal from "../react/Modal"; import { CodeMimeTypesList } from "../type_widgets/options/code_notes"; import { ContentLanguagesList } from "../type_widgets/options/i18n"; import { LocaleSelector } from "../type_widgets/options/components/LocaleSelector"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; + +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export default function BasicPropertiesTab({ note }: TabContext) { return ( @@ -28,7 +31,7 @@ export default function BasicPropertiesTab({ note }: TabContext) { - + {!isNewLayout && } @@ -191,18 +194,7 @@ function EditabilitySelect({ note }: { note?: FNote | null }) { } function BookmarkSwitch({ note }: { note?: FNote | null }) { - const [ isBookmarked, setIsBookmarked ] = useState(false); - const refreshState = useCallback(() => { - const isBookmarked = note && !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks"); - setIsBookmarked(!!isBookmarked); - }, [ note ]); - - useEffect(() => refreshState(), [ note ]); - useTriliumEvent("entitiesReloaded", ({ loadResults }) => { - if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { - refreshState(); - } - }); + const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); return (
@@ -210,18 +202,36 @@ function BookmarkSwitch({ note }: { note?: FNote | null }) { switchOnName={t("bookmark_switch.bookmark")} switchOnTooltip={t("bookmark_switch.bookmark_this_note")} switchOffName={t("bookmark_switch.bookmark")} switchOffTooltip={t("bookmark_switch.remove_bookmark")} currentValue={isBookmarked} - onChange={async (shouldBookmark) => { - if (!note) return; - const resp = await server.put(`notes/${note.noteId}/toggle-in-parent/_lbBookmarks/${shouldBookmark}`); - - if (!resp.success && "message" in resp) { - toast.showError(resp.message); - } - }} + onChange={setIsBookmarked} disabled={["root", "_hidden"].includes(note?.noteId ?? "")} />
- ) + ); +} + +export function useNoteBookmarkState(note: FNote | null | undefined) { + const [ isBookmarked, setIsBookmarked ] = useState(false); + const refreshState = useCallback(() => { + const isBookmarked = note && !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks"); + setIsBookmarked(!!isBookmarked); + }, [ note ]); + + const changeHandler = useCallback(async (shouldBookmark: boolean) => { + if (!note) return; + const resp = await server.put(`notes/${note.noteId}/toggle-in-parent/_lbBookmarks/${shouldBookmark}`); + + if (!resp.success && "message" in resp) { + toast.showError(resp.message); + } + }, [ note ]); + + useEffect(() => refreshState(), [ refreshState ]); + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { + if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { + refreshState(); + } + }); + return [ isBookmarked, changeHandler ] as const; } function TemplateSwitch({ note }: { note?: FNote | null }) { diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 12655262e..ee5c949d7 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -13,10 +13,11 @@ import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/u import ws from "../../services/ws"; import ActionButton from "../react/ActionButton"; import Dropdown from "../react/Dropdown"; -import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList"; +import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; +import { useNoteBookmarkState } from "./BasicPropertiesTab"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -79,6 +80,9 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not {isElectron && } + {isNewLayout && } + + parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} /> @@ -112,6 +116,14 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not ); } +function NoteBasicProperties({ note }: { note: FNote }) { + const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); + + return <> + + ; +} + function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { return ( From e0f7d65f77440188866b9ea68edc7312544c3c5c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:24:31 +0200 Subject: [PATCH 04/28] feat(widgets): toggle from label --- apps/client/src/widgets/react/FormList.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 95a105262..92c021c55 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -139,7 +139,10 @@ export function FormListToggleableItem({ title, currentValue, onChange, ...props onChange(newValue: boolean): void; }) { return ( - e.stopPropagation()}> + { + e.stopPropagation(); + onChange(!currentValue); + }}> Date: Wed, 10 Dec 2025 18:33:29 +0200 Subject: [PATCH 05/28] feat(widgets/toggle): disable transitions on first render --- apps/client/src/widgets/react/FormToggle.css | 10 +++++++++- apps/client/src/widgets/react/FormToggle.tsx | 13 ++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/react/FormToggle.css b/apps/client/src/widgets/react/FormToggle.css index 941839143..bfe5bdc61 100644 --- a/apps/client/src/widgets/react/FormToggle.css +++ b/apps/client/src/widgets/react/FormToggle.css @@ -24,6 +24,14 @@ border-radius: 24px; background-color: var(--switch-off-track-background); transition: background 200ms ease-in; + + &.disable-transitions { + transition: none !important; + + &:after { + transition: none !important; + } + } } .switch-widget .switch-button.on { @@ -103,4 +111,4 @@ body[dir=rtl] .switch-widget .switch-button.on:after { .switch-widget .switch-help-button:hover { color: var(--main-text-color); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx index ba7499f36..39be5cd70 100644 --- a/apps/client/src/widgets/react/FormToggle.tsx +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -1,5 +1,7 @@ +import clsx from "clsx"; import "./FormToggle.css"; import HelpButton from "./HelpButton"; +import { useEffect, useState } from "preact/hooks"; interface FormToggleProps { currentValue: boolean | null; @@ -13,13 +15,22 @@ interface FormToggleProps { } export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled }: FormToggleProps) { + const [ disableTransition, setDisableTransition ] = useState(true); + + useEffect(() => { + const timeout = setTimeout(() => { + setDisableTransition(false); + }, 100); + return () => clearTimeout(timeout); + }, []); + return (
{ currentValue ? switchOffName : switchOnName }
); @@ -247,7 +247,7 @@ function TemplateSwitch({ note }: { note?: FNote | null }) { currentValue={isTemplate} onChange={setIsTemplate} /> - ) + ); } function SharedSwitch({ note }: { note?: FNote | null }) { diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 8cf1ffbeb..321ccda89 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -14,7 +14,7 @@ import ws from "../../services/ws"; import ActionButton from "../react/ActionButton"; import Dropdown from "../react/Dropdown"; import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; -import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteProperty, useTriliumOption } from "../react/hooks"; +import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import { useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; @@ -119,10 +119,12 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not function NoteBasicProperties({ note }: { note: FNote }) { const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); const [ isShared, switchShareState ] = useShareState(note); + const [ isTemplate, setIsTemplate ] = useNoteLabelBoolean(note, "template"); return <> + ; } From cfbd2bf53ac899789d4a189a3c69c5e662ef601e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 18:58:46 +0200 Subject: [PATCH 09/28] feat(note_actions): integrate editability menu into new layout --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 4 ++-- .../client/src/widgets/ribbon/NoteActions.tsx | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 0f186998b..94aa6b314 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -30,7 +30,7 @@ export default function BasicPropertiesTab({ note }: TabContext) {
- + {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } @@ -190,7 +190,7 @@ function EditabilitySelect({ note }: { note?: FNote | null }) { }} />
- ) + ); } function BookmarkSwitch({ note }: { note?: FNote | null }) { diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 321ccda89..22e913b16 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -125,9 +125,28 @@ function NoteBasicProperties({ note }: { note: FNote }) { + ; } +function EditabilityDropdown({ note }: { note: FNote }) { + const [ readOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); + const [ autoReadOnlyDisabled, setAutoReadOnlyDisabled ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled"); + + function setState(readOnly: boolean, autoReadOnlyDisabled: boolean) { + setReadOnly(readOnly); + setAutoReadOnlyDisabled(autoReadOnlyDisabled); + } + + return ( + + setState(false, false)} description={t("editability_select.note_is_editable")}>{t("editability_select.auto")} + setState(true, false)} description={t("editability_select.note_is_read_only")}>{t("editability_select.read_only")} + setState(false, true)} description={t("editability_select.note_is_always_editable")}>{t("editability_select.always_editable")} + + ); +} + function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { return ( From 01978dabf0d932a2315808774ad53b73c270cbad Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 19:05:33 +0200 Subject: [PATCH 10/28] fix(breadcrumb_badges): doesn't refresh when switching editability --- apps/client/src/widgets/react/hooks.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 38f0a1967..65448eb92 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -845,6 +845,8 @@ export function useGlobalShortcut(keyboardShortcut: string | null | undefined, h */ export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: NoteContext | undefined) { const [ isReadOnly, setIsReadOnly ] = useState(undefined); + const [ readOnlyAttr ] = useNoteLabelBoolean(note, "readOnly"); + const [ autoReadOnlyDisabledAttr ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled"); const enableEditing = useCallback((enabled = true) => { if (noteContext?.viewScope) { @@ -859,7 +861,7 @@ export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: N setIsReadOnly(readOnly); }); } - }, [ note, noteContext, noteContext?.viewScope ]); + }, [ note, noteContext, noteContext?.viewScope, readOnlyAttr, autoReadOnlyDisabledAttr ]); useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => { if (noteContext?.ntxId === eventNoteContext.ntxId) { From efb2f9a048ab6608ff2bdf9abdaee6c8dc483ce0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 20:20:21 +0200 Subject: [PATCH 11/28] chore(note_actions): reintroduce disabled logic for toggles --- apps/client/src/widgets/react/FormList.tsx | 4 +++- .../client/src/widgets/ribbon/NoteActions.tsx | 21 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 92c021c55..8be283605 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -141,7 +141,9 @@ export function FormListToggleableItem({ title, currentValue, onChange, ...props return ( { e.stopPropagation(); - onChange(!currentValue); + if (!props.disabled) { + onChange(!currentValue); + } }}> - - - + + + ; } From 483327c8087e9e71a59cfd9f8889be8c23a43ac4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 20:30:55 +0200 Subject: [PATCH 12/28] fix(widgets/toggle): double event triggering when in menu --- apps/client/src/widgets/react/FormList.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 8be283605..f1374712c 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -146,9 +146,10 @@ export function FormListToggleableItem({ title, currentValue, onChange, ...props } }}> {}} /> ); From 36b1182565b065efcf231e269ae9ef19307791d6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 20:32:58 +0200 Subject: [PATCH 13/28] feat(widgets/toggle): disable if going too fast --- apps/client/src/widgets/react/FormList.tsx | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index f1374712c..fac69efde 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -133,18 +133,25 @@ export function FormListItem({ className, icon, value, title, active, disabled, ); } -export function FormListToggleableItem({ title, currentValue, onChange, ...props }: Omit & { +export function FormListToggleableItem({ title, currentValue, onChange, disabled, ...props }: Omit & { title: string; currentValue: boolean; - onChange(newValue: boolean): void; + onChange(newValue: boolean): void | Promise; }) { + const isWaiting = useRef(false); + return ( - { - e.stopPropagation(); - if (!props.disabled) { - onChange(!currentValue); - } - }}> + { + e.stopPropagation(); + if (!disabled && !isWaiting.current) { + isWaiting.current = true; + await onChange(!currentValue); + isWaiting.current = false; + } + }}> Date: Wed, 10 Dec 2025 20:46:58 +0200 Subject: [PATCH 14/28] fix(note_actions): editability context menu is too narrow --- apps/client/src/stylesheets/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 871b84bdc..55398be8c 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1315,7 +1315,8 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu { top: 0; inset-inline-start: calc(100% - 2px); /* -2px, otherwise there's a small gap between menu and submenu where the hover can disappear */ margin-top: -10px; - min-width: 15rem; + min-width: max-content; + max-width: 300px; /* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */ max-height: 600px; overflow: auto; From 77f5770bff5c2e056a48e48cee31a7c7ed67272a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 20:57:15 +0200 Subject: [PATCH 15/28] feat(note_actions): protect note switch --- apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx | 2 +- apps/client/src/widgets/ribbon/NoteActions.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 94aa6b314..d42617dae 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -29,7 +29,7 @@ export default function BasicPropertiesTab({ note }: TabContext) { return (
- + {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index d4b5e235a..282bb6b9c 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -18,6 +18,7 @@ import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, u import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import { useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; +import protected_session from "../../services/protected_session"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -120,6 +121,7 @@ function NoteBasicProperties({ note }: { note: FNote }) { const [ isBookmarked, setIsBookmarked ] = useNoteBookmarkState(note); const [ isShared, switchShareState ] = useShareState(note); const [ isTemplate, setIsTemplate ] = useNoteLabelBoolean(note, "template"); + const isProtected = useNoteProperty(note, "isProtected"); return <> + protected_session.protectNote(note.noteId, shouldProtect, false)} + /> ; } @@ -154,7 +161,7 @@ function EditabilityDropdown({ note }: { note: FNote }) { } return ( - + setState(false, false)} description={t("editability_select.note_is_editable")}>{t("editability_select.auto")} setState(true, false)} description={t("editability_select.note_is_read_only")}>{t("editability_select.read_only")} setState(false, true)} description={t("editability_select.note_is_always_editable")}>{t("editability_select.always_editable")} From 6f85b7cc0966fa77a7f59542358607702b7062bb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 21:54:17 +0200 Subject: [PATCH 16/28] feat(note_actions): integrate note type --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 142 +++++++++--------- .../client/src/widgets/ribbon/NoteActions.tsx | 18 ++- 2 files changed, 91 insertions(+), 69 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index d42617dae..9c6f656d4 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; +import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/hooks"; import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; @@ -28,7 +28,7 @@ const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export default function BasicPropertiesTab({ note }: TabContext) { return (
- + {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } {!isNewLayout && } @@ -40,18 +40,42 @@ export default function BasicPropertiesTab({ note }: TabContext) { } function NoteTypeWidget({ note }: { note?: FNote | null }) { - const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); - const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes"); - const mimeTypes = useMemo(() => { - mime_types.loadMimeTypes(); - return mime_types.getMimeTypes().filter(mimeType => mimeType.enabled) - }, [ codeNotesMimeTypes ]); const notSelectableNoteTypes = useMemo(() => NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type), []); const currentNoteType = useNoteProperty(note, "type") ?? undefined; const currentNoteMime = useNoteProperty(note, "mime"); const [ modalShown, setModalShown ] = useState(false); + return ( +
+ {t("basic_properties.note_type")}:   + {findTypeTitle(currentNoteType, currentNoteMime)}} + disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")} + > + + + + setModalShown(false)} + size="xl" scrollable + > + + +
+ ); +} + +export function NoteTypeDropdownContent({ currentNoteType, currentNoteMime, note, setModalShown }: { currentNoteType?: NoteType, currentNoteMime?: string | null, note?: FNote | null, setModalShown: Dispatch> }) { + const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes"); + const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); + const mimeTypes = useMemo(() => { + mime_types.loadMimeTypes(); + return mime_types.getMimeTypes().filter(mimeType => mimeType.enabled); + }, [ codeNotesMimeTypes ]); const changeNoteType = useCallback(async (type: NoteType, mime?: string) => { if (!note || (type === currentNoteType && mime === currentNoteMime)) { return; @@ -71,70 +95,54 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) { }, [ note, currentNoteType, currentNoteMime ]); return ( -
- {t("basic_properties.note_type")}:   - {findTypeTitle(currentNoteType, currentNoteMime)}} - disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")} - > - {noteTypes.map(({ isNew, isBeta, type, mime, title }) => { - const badges: FormListBadge[] = []; - if (isNew) { - badges.push({ - className: "new-note-type-badge", - text: t("note_types.new-feature") - }); - } - if (isBeta) { - badges.push({ - text: t("note_types.beta-feature") - }); - } + <> + {noteTypes.map(({ isNew, isBeta, type, mime, title }) => { + const badges: FormListBadge[] = []; + if (isNew) { + badges.push({ + className: "new-note-type-badge", + text: t("note_types.new-feature") + }); + } + if (isBeta) { + badges.push({ + text: t("note_types.beta-feature") + }); + } - const checked = (type === currentNoteType); - if (type !== "code") { - return ( + const checked = (type === currentNoteType); + if (type !== "code") { + return ( + changeNoteType(type, mime)} + >{title} + ); + } else { + return ( + <> + changeNoteType(type, mime)} - >{title} - ); - } else { - return ( - <> - - - {title} - - - ) - } - })} + disabled + > + {title} + + + ); + } + })} - {mimeTypes.map(({ title, mime }) => ( - changeNoteType("code", mime)}> - {title} - - ))} + {mimeTypes.map(({ title, mime }) => ( + changeNoteType("code", mime)}> + {title} + + ))} - - setModalShown(true)}>{t("basic_properties.configure_code_notes")} - - - setModalShown(false)} - size="xl" scrollable - > - - -
+ + setModalShown(true)}>{t("basic_properties.configure_code_notes")} + ) } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 282bb6b9c..515535ba8 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -1,5 +1,5 @@ import { ConvertToAttachmentResponse } from "@triliumnext/commons"; -import { useContext } from "preact/hooks"; +import { useContext, useState } from "preact/hooks"; import appContext, { CommandNames } from "../../components/app_context"; import NoteContext from "../../components/note_context"; @@ -17,7 +17,7 @@ import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListTogglea import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; -import { useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; +import { NoteTypeDropdownContent, useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; import protected_session from "../../services/protected_session"; const isNewLayout = isExperimentalFeatureEnabled("new-layout"); @@ -148,6 +148,8 @@ function NoteBasicProperties({ note }: { note: FNote }) { title={t("protect_note.toggle-on")} currentValue={!!isProtected} onChange={shouldProtect => protected_session.protectNote(note.noteId, shouldProtect, false)} /> + + ; } @@ -169,6 +171,18 @@ function EditabilityDropdown({ note }: { note: FNote }) { ); } +function NoteTypeDropdown({ note }: { note: FNote }) { + const currentNoteType = useNoteProperty(note, "type") ?? undefined; + const currentNoteMime = useNoteProperty(note, "mime"); + const [ modalShown, setModalShown ] = useState(false); + + return ( + + + + ); +} + function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { return ( From 8d8ff25bae18cac4650957ccd0fc02fbe7b26f28 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 22:18:53 +0200 Subject: [PATCH 17/28] feat(note_actions): reintroduce help pages --- apps/client/src/stylesheets/style.css | 6 ++--- apps/client/src/stylesheets/theme-dark.css | 4 ++-- apps/client/src/stylesheets/theme-light.css | 4 ++-- .../src/stylesheets/theme-next-dark.css | 12 +++++----- .../src/stylesheets/theme-next-light.css | 10 ++++---- apps/client/src/widgets/react/FormList.css | 7 +++++- apps/client/src/widgets/react/FormList.tsx | 23 +++++++++++++++---- apps/client/src/widgets/react/FormToggle.tsx | 5 +++- .../client/src/widgets/ribbon/NoteActions.tsx | 2 ++ 9 files changed, 49 insertions(+), 24 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 55398be8c..0a1971f11 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -423,16 +423,16 @@ body.desktop .tabulator-popup-container, pointer-events: none; } -.dropdown-menu .disabled .disabled-tooltip { +.dropdown-menu .disabled .contextual-help { pointer-events: all; margin-inline-start: 8px; font-size: 0.75rem; - color: var(--disabled-tooltip-icon-color); + color: var(--contextual-help-icon-color); cursor: help; opacity: 0.75; } -.dropdown-menu .disabled .disabled-tooltip:hover { +.dropdown-menu .disabled .contextual-help:hover { opacity: 1; } diff --git a/apps/client/src/stylesheets/theme-dark.css b/apps/client/src/stylesheets/theme-dark.css index 690d49ecd..d4702d6c5 100644 --- a/apps/client/src/stylesheets/theme-dark.css +++ b/apps/client/src/stylesheets/theme-dark.css @@ -19,7 +19,7 @@ --dropdown-border-color: #555; --dropdown-shadow-opacity: 0.4; --dropdown-item-icon-destructive-color: #de6e5b; - --disabled-tooltip-icon-color: #7fd2ef; + --contextual-help-icon-color: #7fd2ef; --accented-background-color: #555; --more-accented-background-color: #777; @@ -114,4 +114,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before { .use-note-color { --custom-color: var(--dark-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-light.css b/apps/client/src/stylesheets/theme-light.css index 0c14a2d92..777ccbd61 100644 --- a/apps/client/src/stylesheets/theme-light.css +++ b/apps/client/src/stylesheets/theme-light.css @@ -23,7 +23,7 @@ html { --dropdown-border-color: #ccc; --dropdown-shadow-opacity: 0.2; --dropdown-item-icon-destructive-color: #ec5138; - --disabled-tooltip-icon-color: #004382; + --contextual-help-icon-color: #004382; --accented-background-color: #f5f5f5; --more-accented-background-color: #ddd; @@ -98,4 +98,4 @@ html { .use-note-color { --custom-color: var(--light-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 8358de09b..b4c3def74 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -6,7 +6,7 @@ */ :root { - /* + /* * ⚠️ NOTICE: This theme is currently in the beta stage of development. * The names and purposes of these CSS variables are subject to frequent changes. */ @@ -22,7 +22,7 @@ --dropdown-border-color: #404040; --dropdown-shadow-opacity: 0.6; --dropdown-item-icon-destructive-color: #de6e5b; - --disabled-tooltip-icon-color: #7fd2ef; + --contextual-help-icon-color: #7fd2ef; --accented-background-color: #555; @@ -182,7 +182,7 @@ --tab-close-button-hover-background: #a45353; --tab-close-button-hover-color: white; - + --active-tab-background-color: #ffffff1c; --active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-icon-color: #a9a9a9; @@ -201,7 +201,7 @@ --promoted-attribute-card-background-color: #ffffff21; --promoted-attribute-card-shadow: none; - + --floating-button-shadow-color: #00000080; --floating-button-background-color: #494949d2; --floating-button-color: var(--button-text-color); @@ -226,7 +226,7 @@ --scrollbar-border-color: unset; /* Deprecated */ --selection-background-color: #3399FF70; - + --link-color: lightskyblue; --mermaid-theme: dark; @@ -320,4 +320,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before { .use-note-color { --custom-color: var(--dark-theme-custom-color); -} \ No newline at end of file +} diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 60e7d55b2..620672499 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -6,7 +6,7 @@ */ :root { - /* + /* * ⚠️ NOTICE: This theme is currently in the beta stage of development. * The names and purposes of these CSS variables are subject to frequent changes. */ @@ -22,7 +22,7 @@ --dropdown-border-color: #ccc; --dropdown-shadow-opacity: 0.2; --dropdown-item-icon-destructive-color: #ec5138; - --disabled-tooltip-icon-color: #004382; + --contextual-help-icon-color: #004382; --accented-background-color: #f5f5f5; @@ -138,7 +138,7 @@ /* Deprecated: now local variables in #launcher, with the values dependent on the current layout. */ --launcher-pane-background-color: unset; --launcher-pane-text-color: unset; - + --launcher-pane-vert-background-color: #e8e8e8; --launcher-pane-vert-text-color: #000000bd; --launcher-pane-vert-button-hover-color: black; @@ -174,7 +174,7 @@ --tab-close-button-hover-background: #c95a5a; --tab-close-button-hover-color: white; - + --active-tab-background-color: white; --active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-icon-color: gray; @@ -291,4 +291,4 @@ --modal-background-color: hsl(var(--custom-color-hue), 56%, 96%); --modal-border-color: hsl(var(--custom-color-hue), 33%, 41%); --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/react/FormList.css b/apps/client/src/widgets/react/FormList.css index 33922e7c6..643b5132a 100644 --- a/apps/client/src/widgets/react/FormList.css +++ b/apps/client/src/widgets/react/FormList.css @@ -17,7 +17,12 @@ --switch-thumb-width: 12px; --switch-thumb-height: var(--switch-thumb-width); - .switch-name { + .contextual-help { + margin-inline-start: 0.25em; + cursor: pointer; + } + + .switch-spacer { flex-grow: 1; } } diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index fac69efde..dd5948cb3 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -5,7 +5,7 @@ import { useEffect, useMemo, useRef, useState, type CSSProperties } from "preact import "./FormList.css"; import { CommandNames } from "../../components/app_context"; import { useStaticTooltip } from "./hooks"; -import { handleRightToLeftPlacement, isMobile } from "../../services/utils"; +import { handleRightToLeftPlacement, isMobile, openInAppHelpFromUrl } from "../../services/utils"; import clsx from "clsx"; import FormToggle from "./FormToggle"; @@ -95,12 +95,13 @@ interface FormListItemOpts { description?: string; className?: string; rtl?: boolean; + postContent?: ComponentChildren; } const TOOLTIP_CONFIG: Partial = { placement: handleRightToLeftPlacement("right"), fallbackPlacements: [ handleRightToLeftPlacement("right") ] -} +}; export function FormListItem({ className, icon, value, title, active, disabled, checked, container, onClick, selected, rtl, triggerCommand, description, ...contentProps }: FormListItemOpts) { const itemRef = useRef(null); @@ -133,9 +134,10 @@ export function FormListItem({ className, icon, value, title, active, disabled, ); } -export function FormListToggleableItem({ title, currentValue, onChange, disabled, ...props }: Omit & { +export function FormListToggleableItem({ title, currentValue, onChange, disabled, helpPage, ...props }: Omit & { title: string; currentValue: boolean; + helpPage?: string; onChange(newValue: boolean): void | Promise; }) { const isWaiting = useRef(false); @@ -145,6 +147,10 @@ export function FormListToggleableItem({ title, currentValue, onChange, disabled {...props} disabled={disabled} onClick={async (e) => { + if ((e.target as HTMLElement | null)?.classList.contains("contextual-help")) { + return; + } + e.stopPropagation(); if (!disabled && !isWaiting.current) { isWaiting.current = true; @@ -157,6 +163,15 @@ export function FormListToggleableItem({ title, currentValue, onChange, disabled switchOffName={title} currentValue={currentValue} onChange={() => {}} + afterName={<> + {helpPage && ( + openInAppHelpFromUrl(helpPage)} + /> + )} + + } /> ); @@ -169,7 +184,7 @@ function FormListContent({ children, badges, description, disabled, disabledTool {text} ))} {disabled && disabledTooltip && ( - + )} {description &&
{description}
} ; diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx index 39be5cd70..e08c1e3c2 100644 --- a/apps/client/src/widgets/react/FormToggle.tsx +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -2,6 +2,7 @@ import clsx from "clsx"; import "./FormToggle.css"; import HelpButton from "./HelpButton"; import { useEffect, useState } from "preact/hooks"; +import { ComponentChildren } from "preact"; interface FormToggleProps { currentValue: boolean | null; @@ -12,9 +13,10 @@ interface FormToggleProps { switchOffTooltip?: string; helpPage?: string; disabled?: boolean; + afterName?: ComponentChildren; } -export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled }: FormToggleProps) { +export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled, afterName }: FormToggleProps) { const [ disableTransition, setDisableTransition ] = useState(true); useEffect(() => { @@ -27,6 +29,7 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc return (
{ currentValue ? switchOffName : switchOnName } + { afterName }
); } -export function NoteLanguageSelector({ note }: { note?: FNote | null }) { +export function NoteLanguageSelector({ note, extraChildren }: { note?: FNote | null, extraChildren?: ComponentChildren }) { const [ modalShown, setModalShown ] = useState(false); const [ languages ] = useTriliumOption("languages"); const DEFAULT_LOCALE = { @@ -351,12 +352,13 @@ export function NoteLanguageSelector({ note }: { note?: FNote | null }) { locales={locales} defaultLocale={DEFAULT_LOCALE} currentValue={currentNoteLanguage ?? ""} onChange={setCurrentNoteLanguage} - extraChildren={( + extraChildren={<> + {extraChildren} setModalShown(true)} icon="bx bx-cog" >{t("note_language.configure-languages")} - )} + } /> {createPortal( , From 58bc5dc66a2ba81b656fba6c61e265d014c117ea Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 23:07:35 +0200 Subject: [PATCH 26/28] chore(ribbon): hide basic properties from the ribbon on new layout --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 50 +++++++++---------- .../src/widgets/ribbon/RibbonDefinition.ts | 43 ++++++++-------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 8ea9d6f7d..04e8334ee 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -1,41 +1,39 @@ +import { NoteType, ToggleInParentResponse } from "@triliumnext/commons"; +import { ComponentChildren } from "preact"; +import { createPortal } from "preact/compat"; import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/hooks"; -import Dropdown from "../react/Dropdown"; -import { NOTE_TYPES } from "../../services/note_types"; -import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; -import { getAvailableLocales, t } from "../../services/i18n"; -import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks"; -import mime_types from "../../services/mime_types"; -import { Locale, LOCALES, NoteType, ToggleInParentResponse } from "@triliumnext/commons"; -import server from "../../services/server"; -import dialog from "../../services/dialog"; -import FormToggle from "../react/FormToggle"; + import FNote from "../../entities/fnote"; -import protected_session from "../../services/protected_session"; -import FormDropdownList from "../react/FormDropdownList"; -import toast from "../../services/toast"; import branches from "../../services/branches"; +import dialog from "../../services/dialog"; +import { getAvailableLocales, t } from "../../services/i18n"; +import mime_types from "../../services/mime_types"; +import { NOTE_TYPES } from "../../services/note_types"; +import protected_session from "../../services/protected_session"; +import server from "../../services/server"; import sync from "../../services/sync"; +import toast from "../../services/toast"; +import Dropdown from "../react/Dropdown"; +import FormDropdownList from "../react/FormDropdownList"; +import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; +import FormToggle from "../react/FormToggle"; import HelpButton from "../react/HelpButton"; -import { TabContext } from "./ribbon-interface"; +import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks"; import Modal from "../react/Modal"; import { CodeMimeTypesList } from "../type_widgets/options/code_notes"; -import { ContentLanguagesList } from "../type_widgets/options/i18n"; import { LocaleSelector } from "../type_widgets/options/components/LocaleSelector"; -import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; -import { createPortal } from "preact/compat"; -import { ComponentChildren } from "preact"; - -const isNewLayout = isExperimentalFeatureEnabled("new-layout"); +import { ContentLanguagesList } from "../type_widgets/options/i18n"; +import { TabContext } from "./ribbon-interface"; export default function BasicPropertiesTab({ note }: TabContext) { return (
- {!isNewLayout && } - {!isNewLayout && } - {!isNewLayout && } - {!isNewLayout && } - {!isNewLayout && } - {!isNewLayout && } + + + + + +
); diff --git a/apps/client/src/widgets/ribbon/RibbonDefinition.ts b/apps/client/src/widgets/ribbon/RibbonDefinition.ts index ab351637d..a76e97140 100644 --- a/apps/client/src/widgets/ribbon/RibbonDefinition.ts +++ b/apps/client/src/widgets/ribbon/RibbonDefinition.ts @@ -1,22 +1,24 @@ -import ScriptTab from "./ScriptTab"; -import EditedNotesTab from "./EditedNotesTab"; -import NotePropertiesTab from "./NotePropertiesTab"; -import NoteInfoTab from "./NoteInfoTab"; -import SimilarNotesTab from "./SimilarNotesTab"; -import FilePropertiesTab from "./FilePropertiesTab"; -import ImagePropertiesTab from "./ImagePropertiesTab"; -import NotePathsTab from "./NotePathsTab"; -import NoteMapTab from "./NoteMapTab"; -import OwnedAttributesTab from "./OwnedAttributesTab"; -import InheritedAttributesTab from "./InheritedAttributesTab"; -import CollectionPropertiesTab from "./CollectionPropertiesTab"; -import SearchDefinitionTab from "./SearchDefinitionTab"; -import BasicPropertiesTab from "./BasicPropertiesTab"; -import FormattingToolbar from "./FormattingToolbar"; -import options from "../../services/options"; -import { t } from "../../services/i18n"; -import { TabConfiguration } from "./ribbon-interface"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; +import { t } from "../../services/i18n"; +import options from "../../services/options"; +import BasicPropertiesTab from "./BasicPropertiesTab"; +import CollectionPropertiesTab from "./CollectionPropertiesTab"; +import EditedNotesTab from "./EditedNotesTab"; +import FilePropertiesTab from "./FilePropertiesTab"; +import FormattingToolbar from "./FormattingToolbar"; +import ImagePropertiesTab from "./ImagePropertiesTab"; +import InheritedAttributesTab from "./InheritedAttributesTab"; +import NoteInfoTab from "./NoteInfoTab"; +import NoteMapTab from "./NoteMapTab"; +import NotePathsTab from "./NotePathsTab"; +import NotePropertiesTab from "./NotePropertiesTab"; +import OwnedAttributesTab from "./OwnedAttributesTab"; +import { TabConfiguration } from "./ribbon-interface"; +import ScriptTab from "./ScriptTab"; +import SearchDefinitionTab from "./SearchDefinitionTab"; +import SimilarNotesTab from "./SimilarNotesTab"; + +const isNewLayout = isExperimentalFeatureEnabled("new-layout"); export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ { @@ -28,7 +30,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ toggleCommand: "toggleRibbonTabClassicEditor", content: FormattingToolbar, activate: ({ note }) => !options.is("editedNotesOpenInRibbon") || !note?.hasOwnedLabel("dateNote"), - stayInDom: !isExperimentalFeatureEnabled("new-layout"), + stayInDom: !isNewLayout, avoidInNewLayout: true }, { @@ -85,11 +87,10 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [ activate: true, }, { - // BasicProperties title: t("basic_properties.basic_properties"), icon: "bx bx-slider", content: BasicPropertiesTab, - show: ({note}) => !note?.isLaunchBarConfig(), + show: ({note}) => !isNewLayout && !note?.isLaunchBarConfig(), toggleCommand: "toggleRibbonTabBasicProperties" }, { From 6e8e10323f4f5b0e5897b6741fff015075c8c9f5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 23:19:17 +0200 Subject: [PATCH 27/28] chore(client): address requested changes --- apps/client/src/services/experimental_features.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index 9295aa8c7..5f7e5d010 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -28,15 +28,13 @@ export function getEnabledExperimentalFeatureIds() { } export async function toggleExperimentalFeature(featureId: ExperimentalFeatureId, enable: boolean) { - let features = Array.from(getEnabledFeatures()); + const features = new Set(getEnabledFeatures()); if (enable) { - if (!features.includes(featureId)) { - features.push(featureId); - } + features.add(featureId); } else { - features = features.filter(f => f !== featureId); + features.delete(featureId); } - await options.save("experimentalFeatures", JSON.stringify(features)); + await options.save("experimentalFeatures", JSON.stringify(Array.from(features))); } function getEnabledFeatures() { From 1ab89d0db024e436c9dc80f55b2aeacbd41be727 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Dec 2025 23:36:47 +0200 Subject: [PATCH 28/28] fix(status_bar): language selector not updating properly --- apps/client/src/widgets/NoteStatusBar.tsx | 22 ++++++++++++------- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 2 +- .../options/components/LocaleSelector.tsx | 8 +++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/client/src/widgets/NoteStatusBar.tsx b/apps/client/src/widgets/NoteStatusBar.tsx index 9255fb8fd..3b686ff84 100644 --- a/apps/client/src/widgets/NoteStatusBar.tsx +++ b/apps/client/src/widgets/NoteStatusBar.tsx @@ -1,19 +1,25 @@ +import "./NoteStatusBar.css"; + import { t } from "../services/i18n"; import { openInAppHelpFromUrl } from "../services/utils"; -import "./NoteStatusBar.css"; import { FormListItem } from "./react/FormList"; - +import { useNoteContext } from "./react/hooks"; import { NoteLanguageSelector } from "./ribbon/BasicPropertiesTab"; export default function NoteStatusBar() { + const { note } = useNoteContext(); + return (
- openInAppHelpFromUrl("veGu4faJErEM")} - icon="bx bx-help-circle" - >{t("note_language.help-on-languages")} - )} /> + openInAppHelpFromUrl("veGu4faJErEM")} + icon="bx bx-help-circle" + >{t("note_language.help-on-languages")} + )} + />
); } diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 04e8334ee..479994ca3 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -330,7 +330,7 @@ function NoteLanguageSwitch({ note }: { note?: FNote | null }) { ); } -export function NoteLanguageSelector({ note, extraChildren }: { note?: FNote | null, extraChildren?: ComponentChildren }) { +export function NoteLanguageSelector({ note, extraChildren }: { note: FNote | null | undefined, extraChildren?: ComponentChildren }) { const [ modalShown, setModalShown ] = useState(false); const [ languages ] = useTriliumOption("languages"); const DEFAULT_LOCALE = { diff --git a/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx b/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx index 5628cc798..b9b1d8758 100644 --- a/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx +++ b/apps/client/src/widgets/type_widgets/options/components/LocaleSelector.tsx @@ -1,8 +1,9 @@ import { Locale } from "@triliumnext/commons"; +import { ComponentChildren } from "preact"; +import { useMemo } from "preact/hooks"; + import Dropdown from "../../../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../../../react/FormList"; -import { ComponentChildren } from "preact"; -import { useMemo, useState } from "preact/hooks"; export function LocaleSelector({ id, locales, currentValue, onChange, defaultLocale, extraChildren }: { id?: string; @@ -12,7 +13,7 @@ export function LocaleSelector({ id, locales, currentValue, onChange, defaultLoc defaultLocale?: Locale, extraChildren?: ComponentChildren }) { - const [ activeLocale, setActiveLocale ] = useState(defaultLocale?.id === currentValue ? defaultLocale : locales.find(l => l.id === currentValue)); + const activeLocale = defaultLocale?.id === currentValue ? defaultLocale : locales.find(l => l.id === currentValue); const processedLocales = useMemo(() => { const leftToRightLanguages = locales.filter((l) => !l.rtl); @@ -48,7 +49,6 @@ export function LocaleSelector({ id, locales, currentValue, onChange, defaultLoc rtl={locale.rtl} checked={locale.id === currentValue} onClick={() => { - setActiveLocale(locale); onChange(locale.id); }} >{locale.name}