From 17db2a6b382619569782e11bda6451d63a66c77e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 07:46:36 +0200 Subject: [PATCH 01/23] Revert "fix(context_menu): clicking submenu dismisses the menu" This reverts commit 34bc444b186df01461d7547e140116a49425d554. --- apps/client/src/widgets/react/FormList.tsx | 52 +++++++++++----------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 87a047821..42f43a044 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -1,15 +1,13 @@ -import "./FormList.css"; - import { Dropdown as BootstrapDropdown, Tooltip } from "bootstrap"; -import clsx from "clsx"; import { ComponentChildren } from "preact"; -import { type CSSProperties,useEffect, useMemo, useRef, useState } from "preact/compat"; - -import { CommandNames } from "../../components/app_context"; -import { handleRightToLeftPlacement, isMobile, openInAppHelpFromUrl } from "../../services/utils"; -import FormToggle from "./FormToggle"; -import { useStaticTooltip } from "./hooks"; import Icon from "./Icon"; +import { useEffect, useMemo, useRef, useState, type CSSProperties } from "preact/compat"; +import "./FormList.css"; +import { CommandNames } from "../../components/app_context"; +import { useStaticTooltip } from "./hooks"; +import { handleRightToLeftPlacement, isMobile, openInAppHelpFromUrl } from "../../services/utils"; +import clsx from "clsx"; +import FormToggle from "./FormToggle"; interface FormListOpts { children: ComponentChildren; @@ -35,7 +33,7 @@ export default function FormList({ children, onSelect, style, fullHeight, wrappe return () => { $wrapperRef.off("hide.bs.dropdown"); dropdown.dispose(); - }; + } }, [ triggerRef, wrapperRef ]); const builtinStyles = useMemo(() => { @@ -53,7 +51,8 @@ export default function FormList({ children, onSelect, style, fullHeight, wrappe - ) + ); } From ba7969dad447fdfee5b2a8dd42b26155275c9157 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:08:45 +0200 Subject: [PATCH 06/23] style(backend-log): remove extra padding & decrease font size --- apps/client/src/stylesheets/style.css | 1 + apps/client/src/widgets/type_widgets/code/code.css | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 788cda8c0..cb286317d 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -629,6 +629,7 @@ pre:not(.hljs) { pre:has(> .cm-editor) { padding: 0; + margin: 0; } pre > button.copy-button { diff --git a/apps/client/src/widgets/type_widgets/code/code.css b/apps/client/src/widgets/type_widgets/code/code.css index 1a4eea588..db5a9d2e8 100644 --- a/apps/client/src/widgets/type_widgets/code/code.css +++ b/apps/client/src/widgets/type_widgets/code/code.css @@ -23,13 +23,9 @@ height: 100%; display: flex; flex-direction: column; -} -.backend-log-editor { - flex-grow: 1; - width: 100%; - border: none; - resize: none; - margin-bottom: 0; + .cm-editor { + font-size: 0.85em; + } } -/* #endregion */ \ No newline at end of file +/* #endregion */ From 4cfe59271f6036713007be85383ab599ecf0617d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:12:39 +0200 Subject: [PATCH 07/23] feat(layout/note_actions): integrate switch split orientation --- .../widgets/FloatingButtonsDefinitions.tsx | 2 +- .../src/widgets/ribbon/NoteActionsCustom.tsx | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index ce650aa70..644e87f5e 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -90,7 +90,7 @@ function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefault } function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: FloatingButtonContext) { - const isEnabled = note.type === "mermaid" && note.isContentAvailable() && !isReadOnly && isDefaultViewMode; + const isEnabled = !isNewLayout && note.type === "mermaid" && note.isContentAvailable() && !isReadOnly && isDefaultViewMode; const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation"); const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal"; diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index dadfe9a3b..68f4ab1bd 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -8,7 +8,7 @@ import { t } from "../../services/i18n"; import { downloadFileNote, openNoteExternally } from "../../services/open"; import ActionButton from "../react/ActionButton"; import { FormFileUploadActionButton } from "../react/FormFileUpload"; -import { useNoteProperty } from "../react/hooks"; +import { useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { buildUploadNewFileRevisionListener } from "./FilePropertiesTab"; import { buildUploadNewImageRevisionListener } from "./ImagePropertiesTab"; @@ -21,6 +21,7 @@ interface NoteActionsCustomProps { interface NoteActionsCustomInnerProps extends NoteActionsCustomProps { noteType: NoteType; + isReadOnly: boolean; isDefaultViewMode: boolean; parentComponent: Component; } @@ -30,17 +31,21 @@ interface NoteActionsCustomInnerProps extends NoteActionsCustomProps { * from the rest of the note items and the buttons differ based on the note type. */ export default function NoteActionsCustom(props: NoteActionsCustomProps) { - const noteType = useNoteProperty(props.note, "type"); + const { note } = props; + const noteType = useNoteProperty(note, "type"); const parentComponent = useContext(ParentComponent); + const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly"); const innerProps: NoteActionsCustomInnerProps | null | undefined = noteType && parentComponent && { ...props, noteType, isDefaultViewMode: props.noteContext.viewScope?.viewMode === "default", - parentComponent + parentComponent, + isReadOnly }; return (innerProps &&
+ @@ -139,3 +144,15 @@ function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, not /> ); } + +function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: NoteActionsCustomInnerProps) { + const isEnabled = note.type === "mermaid" && note.isContentAvailable() && !isReadOnly && isDefaultViewMode; + const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation"); + const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal"; + + return isEnabled && setSplitEditorOrientation(upcomingOrientation)} + />; +} From 50cbad22d061a3f288c273b1c0defa67ab98397a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:15:00 +0200 Subject: [PATCH 08/23] feat(layout/note_actions): integrate toggle read-only button --- .../src/widgets/ribbon/NoteActionsCustom.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index 68f4ab1bd..ece48ee90 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -6,9 +6,10 @@ import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; import { downloadFileNote, openNoteExternally } from "../../services/open"; +import { ViewTypeOptions } from "../collections/interface"; import ActionButton from "../react/ActionButton"; import { FormFileUploadActionButton } from "../react/FormFileUpload"; -import { useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; +import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { buildUploadNewFileRevisionListener } from "./FilePropertiesTab"; import { buildUploadNewImageRevisionListener } from "./ImagePropertiesTab"; @@ -24,6 +25,7 @@ interface NoteActionsCustomInnerProps extends NoteActionsCustomProps { isReadOnly: boolean; isDefaultViewMode: boolean; parentComponent: Component; + viewType: ViewTypeOptions | null | undefined; } /** @@ -33,11 +35,13 @@ interface NoteActionsCustomInnerProps extends NoteActionsCustomProps { export default function NoteActionsCustom(props: NoteActionsCustomProps) { const { note } = props; const noteType = useNoteProperty(note, "type"); + const [ viewType ] = useNoteLabel(note, "viewType"); const parentComponent = useContext(ParentComponent); const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly"); const innerProps: NoteActionsCustomInnerProps | null | undefined = noteType && parentComponent && { ...props, noteType, + viewType: viewType as ViewTypeOptions | null | undefined, isDefaultViewMode: props.noteContext.viewScope?.viewMode === "default", parentComponent, isReadOnly @@ -46,6 +50,7 @@ export default function NoteActionsCustom(props: NoteActionsCustomProps) { return (innerProps &&
+ @@ -156,3 +161,15 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: N onClick={() => setSplitEditorOrientation(upcomingOrientation)} />; } + +function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActionsCustomInnerProps) { + const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); + const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap") + && note.isContentAvailable() && isDefaultViewMode; + + return isEnabled && setReadOnly(!isReadOnly)} + />; +} From 04a641199bf66b9e7ca36d4941a614ad51c60eb2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:17:01 +0200 Subject: [PATCH 09/23] feat(layout/note_actions): prevent layout shift by disabling button --- apps/client/src/widgets/ribbon/NoteActionsCustom.tsx | 5 +++-- apps/client/src/widgets/ribbon/style.css | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index ece48ee90..8e0a34cd0 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -151,14 +151,15 @@ function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, not } function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: NoteActionsCustomInnerProps) { - const isEnabled = note.type === "mermaid" && note.isContentAvailable() && !isReadOnly && isDefaultViewMode; + const isShown = note.type === "mermaid" && note.isContentAvailable() && isDefaultViewMode; const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation"); const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal"; - return isEnabled && setSplitEditorOrientation(upcomingOrientation)} + disabled={isReadOnly} />; } diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index eafcc7879..198929544 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -451,6 +451,14 @@ body.experimental-feature-new-layout { margin: 0; gap: var(--button-gap); + button { + transition: opacity 250ms ease-in; + + &.disabled { + opacity: 0.4; + } + } + .note-actions-custom { display: flex; align-items: center; From 906fe4f8daa538abb29de2fb5325ce9a1766cf9e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:19:40 +0200 Subject: [PATCH 10/23] feat(layout/note_actions): integrate execute script button --- apps/client/src/widgets/FloatingButtonsDefinitions.tsx | 2 +- apps/client/src/widgets/ribbon/NoteActionsCustom.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 644e87f5e..496532c06 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -173,7 +173,7 @@ function ShowHighlightsListWidgetButton({ note, noteContext, isDefaultViewMode } } function RunActiveNoteButton({ note }: FloatingButtonContext) { - const isEnabled = note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium"; + const isEnabled = !isNewLayout && (note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium"); return isEnabled && + @@ -174,3 +175,12 @@ function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActions onClick={() => setReadOnly(!isReadOnly)} />; } + +function RunActiveNoteButton({ note }: NoteActionsCustomInnerProps) { + const isEnabled = note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium"; + return isEnabled && ; +} From cb0efe25f5d9b24f22f0c452eb00b9179b89defa Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:24:59 +0200 Subject: [PATCH 11/23] feat(layout/note_actions): integrate save to note button --- .../widgets/FloatingButtonsDefinitions.tsx | 28 +++++++++++-------- .../src/widgets/ribbon/NoteActionsCustom.tsx | 11 ++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 496532c06..ecd7c9b92 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -1,5 +1,5 @@ import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons"; -import { VNode } from "preact"; +import { TargetedMouseEvent, VNode } from "preact"; import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import appContext, { EventData, EventNames } from "../components/app_context"; @@ -191,23 +191,27 @@ function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) { } function SaveToNoteButton({ note }: FloatingButtonContext) { - const isEnabled = note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely(); + const isEnabled = !isNewLayout && note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely(); return isEnabled && { - e.preventDefault(); - const { notePath } = await server.post("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId }); - if (notePath) { - toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) })); - // TODO: This hangs the navigation, for some reason. - //await ws.waitForMaxKnownEntityChangeId(); - await appContext.tabManager.getActiveContext()?.setNote(notePath); - } - }} + onClick={buildSaveSqlToNoteHandler(note)} />; } +export function buildSaveSqlToNoteHandler(note: FNote) { + return async (e: MouseEvent) => { + e.preventDefault(); + const { notePath } = await server.post("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId }); + if (notePath) { + toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) })); + // TODO: This hangs the navigation, for some reason. + //await ws.waitForMaxKnownEntityChangeId(); + await appContext.tabManager.getActiveContext()?.setNote(notePath); + } + }; +} + function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingButtonContext) { const isEnabled = (note.type === "relationMap" && isDefaultViewMode); return isEnabled && ( diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index d4f96b2d2..c041ad787 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -7,6 +7,7 @@ import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; import { downloadFileNote, openNoteExternally } from "../../services/open"; import { ViewTypeOptions } from "../collections/interface"; +import { buildSaveSqlToNoteHandler } from "../FloatingButtonsDefinitions"; import ActionButton from "../react/ActionButton"; import { FormFileUploadActionButton } from "../react/FormFileUpload"; import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; @@ -52,6 +53,7 @@ export default function NoteActionsCustom(props: NoteActionsCustomProps) { + @@ -184,3 +186,12 @@ function RunActiveNoteButton({ note }: NoteActionsCustomInnerProps) { triggerCommand="runActiveNote" />; } + +function SaveToNoteButton({ note }: NoteActionsCustomInnerProps) { + const isEnabled = note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely(); + return isEnabled && ; +} From ed284fbc5f9c4cafe5b4046446ed69f144d55c7e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:32:01 +0200 Subject: [PATCH 12/23] feat(layout/note_actions): integrate open API docs --- .../src/widgets/FloatingButtonsDefinitions.tsx | 2 +- .../src/widgets/ribbon/NoteActionsCustom.tsx | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index ecd7c9b92..bd7372d66 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -182,7 +182,7 @@ function RunActiveNoteButton({ note }: FloatingButtonContext) { } function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) { - const isEnabled = note.mime.startsWith("application/javascript;env="); + const isEnabled = !isNewLayout && note.mime.startsWith("application/javascript;env="); return isEnabled && + @@ -195,3 +200,12 @@ function SaveToNoteButton({ note }: NoteActionsCustomInnerProps) { onClick={buildSaveSqlToNoteHandler(note)} />; } + +function OpenTriliumApiDocsButton({ note }: NoteActionsCustomInnerProps) { + const isEnabled = note.mime.startsWith("application/javascript;env="); + return isEnabled && openInAppHelpFromUrl(note.mime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")} + />; +} From d6cc4bfa9c991f1bdf1e2d91bcdf68ac095cea7e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:34:40 +0200 Subject: [PATCH 13/23] fix(layout/note_actions): buttons not reacting to mime type changes --- .../src/widgets/ribbon/NoteActionsCustom.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index c31014faa..d774e5589 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -113,13 +113,13 @@ function UploadNewRevisionButton({ note, onChange }: NoteActionsCustomInnerProps ); } -function OpenExternallyButton({ note }: NoteActionsCustomInnerProps) { +function OpenExternallyButton({ note, noteMime }: NoteActionsCustomInnerProps) { return ( openNoteExternally(note.noteId, note.mime)} + onClick={() => openNoteExternally(note.noteId, noteMime)} /> ); } @@ -183,8 +183,8 @@ function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActions />; } -function RunActiveNoteButton({ note }: NoteActionsCustomInnerProps) { - const isEnabled = note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium"; +function RunActiveNoteButton({ noteMime }: NoteActionsCustomInnerProps) { + const isEnabled = noteMime.startsWith("application/javascript") || noteMime === "text/x-sqlite;schema=trilium"; return isEnabled && ; } -function SaveToNoteButton({ note }: NoteActionsCustomInnerProps) { - const isEnabled = note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely(); +function SaveToNoteButton({ note, noteMime }: NoteActionsCustomInnerProps) { + const isEnabled = noteMime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely(); return isEnabled && ; } -function OpenTriliumApiDocsButton({ note }: NoteActionsCustomInnerProps) { - const isEnabled = note.mime.startsWith("application/javascript;env="); +function OpenTriliumApiDocsButton({ noteMime }: NoteActionsCustomInnerProps) { + const isEnabled = noteMime.startsWith("application/javascript;env="); return isEnabled && openInAppHelpFromUrl(note.mime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")} + onClick={() => openInAppHelpFromUrl(noteMime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")} />; } From 192190d6853901b2472a07ade5b9e2a989842a14 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:46:27 +0200 Subject: [PATCH 14/23] fix(layout/note_actions): save to note not disappearing after save --- .../src/widgets/ribbon/NoteActionsCustom.tsx | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index d774e5589..f1b178daf 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -1,5 +1,5 @@ import { NoteType } from "@triliumnext/commons"; -import { useContext } from "preact/hooks"; +import { useContext, useEffect, useState } from "preact/hooks"; import Component from "../../components/component"; import NoteContext from "../../components/note_context"; @@ -11,7 +11,7 @@ import { ViewTypeOptions } from "../collections/interface"; import { buildSaveSqlToNoteHandler } from "../FloatingButtonsDefinitions"; import ActionButton from "../react/ActionButton"; import { FormFileUploadActionButton } from "../react/FormFileUpload"; -import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; +import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks"; import { ParentComponent } from "../react/react_utils"; import { buildUploadNewFileRevisionListener } from "./FilePropertiesTab"; import { buildUploadNewImageRevisionListener } from "./ImagePropertiesTab"; @@ -135,6 +135,7 @@ function DownloadFileButton({ note }: NoteActionsCustomInnerProps) { ); } +//#region Floating buttons function CopyReferenceToClipboardButton({ ntxId, noteType, parentComponent }: NoteActionsCustomInnerProps) { return (["mermaid", "canvas", "mindMap", "image"].includes(noteType) && ); } -//#endregion function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, noteContext }: NoteActionsCustomInnerProps) { const isEnabled = (note.noteId === "_backendLog" || noteType === "render") && isDefaultViewMode; @@ -193,7 +193,19 @@ function RunActiveNoteButton({ noteMime }: NoteActionsCustomInnerProps) { } function SaveToNoteButton({ note, noteMime }: NoteActionsCustomInnerProps) { - const isEnabled = noteMime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely(); + const [ isEnabled, setIsEnabled ] = useState(false); + + function refresh() { + setIsEnabled(noteMime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely()); + } + + useEffect(refresh, [ note, noteMime ]); + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { + if (loadResults.getBranchRows().find(b => b.noteId === note.noteId)) { + refresh(); + } + }); + return isEnabled && openInAppHelpFromUrl(noteMime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")} />; } +//#endregion From 35cfcc59f68951f4e25c4e45373a64b5d3e00b7f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 08:59:01 +0200 Subject: [PATCH 15/23] feat(layout/note_actions): integrate zoom buttons into relation map --- apps/client/src/stylesheets/style.css | 5 ++ .../widgets/FloatingButtonsDefinitions.tsx | 2 +- .../type_widgets/relation_map/RelationMap.tsx | 72 ++++++++++++++----- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index cb286317d..b7c056248 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -2474,6 +2474,11 @@ footer.webview-footer button { inset-inline-start: 10px; } +.content-floating-buttons.top-right { + top: 10px; + inset-inline-end: 10px; +} + .content-floating-buttons.bottom-left { bottom: 10px; inset-inline-start: 10px; diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index bd7372d66..72cd37f5d 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -213,7 +213,7 @@ export function buildSaveSqlToNoteHandler(note: FNote) { } function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingButtonContext) { - const isEnabled = (note.type === "relationMap" && isDefaultViewMode); + const isEnabled = (!isNewLayout && note.type === "relationMap" && isDefaultViewMode); return isEnabled && ( <> (); const containerRef = useRef(null); const mapApiRef = useRef(null); @@ -119,9 +125,9 @@ export default function RelationMap({ note, noteContext, ntxId }: TypeWidgetProp options: { maxZoom: 2, minZoom: 0.3, - smoothScroll: false, + smoothScroll: false, //@ts-expect-error Upstream incorrectly mentions no arguments. - filterKey: function (e: KeyboardEvent) { + filterKey (e: KeyboardEvent) { // if ALT is pressed, then panzoom should bubble the event up // this is to preserve ALT-LEFT, ALT-RIGHT navigation working return e.altKey; @@ -156,6 +162,34 @@ export default function RelationMap({ note, noteContext, ntxId }: TypeWidgetProp ))} + + {isNewLayout && ( +
+ parentComponent?.triggerEvent("relationMapResetZoomIn", { ntxId })} + className="tn-tool-button" + noIconActionClass + /> + + parentComponent?.triggerEvent("relationMapResetZoomOut", { ntxId })} + className="tn-tool-button" + noIconActionClass + /> + + parentComponent?.triggerEvent("relationMapResetPanZoom", { ntxId })} + className="tn-tool-button" + noIconActionClass + /> +
+ )}
); } @@ -380,7 +414,7 @@ function useRelationCreation({ mapApiRef, jsPlumbApiRef }: { mapApiRef: RefObjec // if there's no event, then this has been triggered programmatically if (!originalEvent || !mapApiRef.current) return; - let name = await dialog.prompt({ + const name = await dialog.prompt({ message: t("relation_map.specify_new_relation_name"), shown: ({ $answer }) => { if (!$answer) { From d7722a1e0555e69339e272a0eb5bd32393385d6c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 09:07:45 +0200 Subject: [PATCH 16/23] feat(layout/note_actions): integrate add child for relation map --- apps/client/src/widgets/ribbon/NoteActionsCustom.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx index f1b178daf..497e1ae14 100644 --- a/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx +++ b/apps/client/src/widgets/ribbon/NoteActionsCustom.tsx @@ -54,6 +54,7 @@ export default function NoteActionsCustom(props: NoteActionsCustomProps) { return (innerProps &&
+ @@ -221,4 +222,13 @@ function OpenTriliumApiDocsButton({ noteMime }: NoteActionsCustomInnerProps) { onClick={() => openInAppHelpFromUrl(noteMime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")} />; } + +function AddChildButton({ parentComponent, noteType, ntxId }: NoteActionsCustomInnerProps) { + const isEnabled = noteType === "relationMap"; + return isEnabled && parentComponent.triggerEvent("relationMapCreateChildNote", { ntxId })} + />; +} //#endregion From 4b80eec000e94ad2aee49524fa0a1801e3268e17 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 09:14:19 +0200 Subject: [PATCH 17/23] feat(layout/note_actions): integrate geo map add button --- .../widgets/FloatingButtonsDefinitions.tsx | 4 +-- .../src/widgets/ribbon/NoteActionsCustom.tsx | 25 +++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 72cd37f5d..ec5db379c 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -103,7 +103,7 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: F function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) { const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); - const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap") + const isEnabled = !isNewLayout && ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap") && note.isContentAvailable() && isDefaultViewMode; return isEnabled && ; } -function AddChildButton({ parentComponent, noteType, ntxId }: NoteActionsCustomInnerProps) { - const isEnabled = noteType === "relationMap"; - return isEnabled && parentComponent.triggerEvent("relationMapCreateChildNote", { ntxId })} - />; +function AddChildButton({ parentComponent, noteType, viewType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) { + if (noteType === "book" && viewType === "geoMap") { + return parentComponent.triggerEvent("geoMapCreateChildNote", { ntxId })} + disabled={isReadOnly} + />; + } else if (noteType === "relationMap") { + return parentComponent.triggerEvent("relationMapCreateChildNote", { ntxId })} + disabled={isReadOnly} + />; + } } //#endregion From b725dbea7ea7f4cf44fcaf0225ee8e12101417af Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 10:06:01 +0200 Subject: [PATCH 18/23] feat(layout/note_actions): export as image --- .../src/translations/en/translation.json | 3 +++ .../widgets/FloatingButtonsDefinitions.tsx | 2 +- .../client/src/widgets/ribbon/NoteActions.tsx | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 9ecdcd640..e404651e6 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -696,6 +696,9 @@ "convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.", "convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?", "print_pdf": "Export as PDF...", + "export_as_image": "Export as image", + "export_as_image_png": "PNG (raster)", + "export_as_image_svg": "SVG (vector)", "note_map": "Note map" }, "onclick_button": { diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index ec5db379c..114b02ed3 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -287,7 +287,7 @@ function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonCon } function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingButtonContext) { - const isEnabled = ["mermaid", "mindMap"].includes(note?.type ?? "") + const isEnabled = !isNewLayout && ["mermaid", "mindMap"].includes(note?.type ?? "") && note?.isContentAvailable() && isDefaultViewMode; return isEnabled && ( <> diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index cf2af8e09..bb6d9a8d8 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -2,6 +2,7 @@ import { ConvertToAttachmentResponse } from "@triliumnext/commons"; import { useContext } from "preact/hooks"; import appContext, { CommandNames } from "../../components/app_context"; +import Component from "../../components/component"; import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; import branches from "../../services/branches"; @@ -66,6 +67,8 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(noteType); const isInOptionsOrHelp = note?.noteId.startsWith("_options") || note?.noteId.startsWith("_help"); const isPrintable = ["text", "code"].includes(noteType) || (noteType === "book" && ["presentation", "list", "table"].includes(viewType ?? "")); + const isExportableToImage = ["mermaid", "mindMap"].includes(noteType); + const isContentAvailable = note.isContentAvailable(); const isElectron = getIsElectron(); const isMac = getIsMac(); const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "aiChat"].includes(noteType); @@ -110,6 +113,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not defaultType: "single" })} /> {isElectron && } + {isExportableToImage && isNormalViewMode && isContentAvailable && } @@ -280,3 +284,23 @@ function ConvertToAttachment({ note }: { note: FNote }) { >{t("note_actions.convert_into_attachment")} ); } + +function ExportAsImage({ ntxId, parentComponent }: { ntxId: string | null | undefined, parentComponent: Component | null | undefined }) { + return ( + + parentComponent?.triggerEvent("exportPng", { ntxId })} + >{t("note_actions.export_as_image_png")} + + parentComponent?.triggerEvent("exportSvg", { ntxId })} + >{t("note_actions.export_as_image_svg")} + + ); +} From adae7fa03be683c674aaa4162a3826645b631b9e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 10:24:24 +0200 Subject: [PATCH 19/23] feat(layout/note_actions): integrate in-app help button --- .../src/widgets/FloatingButtonsDefinitions.tsx | 2 +- .../src/widgets/ribbon/NoteActionsCustom.tsx | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 114b02ed3..141f17863 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -308,7 +308,7 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB function InAppHelpButton({ note }: FloatingButtonContext) { const helpUrl = getHelpUrlForNote(note); - const isEnabled = !!helpUrl && (!isNewLayout || (note?.type !== "book")); + const isEnabled = !!helpUrl && !isNewLayout; return isEnabled && ( +
); @@ -223,6 +225,19 @@ function OpenTriliumApiDocsButton({ noteMime }: NoteActionsCustomInnerProps) { />; } +function InAppHelpButton({ note, noteType }: NoteActionsCustomInnerProps) { + const helpUrl = getHelpUrlForNote(note); + const isEnabled = !!helpUrl && (noteType !== "book"); + + return isEnabled && ( + helpUrl && openInAppHelpFromUrl(helpUrl)} + /> + ); +} + function AddChildButton({ parentComponent, noteType, viewType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) { if (noteType === "book" && viewType === "geoMap") { return Date: Mon, 15 Dec 2025 10:41:58 +0200 Subject: [PATCH 20/23] chore(layout/note_actions): address requested changes --- apps/client/src/widgets/FloatingButtonsDefinitions.tsx | 2 +- apps/client/src/widgets/react/FormList.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx index 141f17863..8670fbcdc 100644 --- a/apps/client/src/widgets/FloatingButtonsDefinitions.tsx +++ b/apps/client/src/widgets/FloatingButtonsDefinitions.tsx @@ -1,5 +1,5 @@ import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons"; -import { TargetedMouseEvent, VNode } from "preact"; +import { VNode } from "preact"; import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import appContext, { EventData, EventNames } from "../components/app_context"; diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index f76c4f743..dd40bd08c 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -225,9 +225,7 @@ export function FormDropdownSubmenu({ icon, title, children, dropStart, onDropdo if (isMobile()) { setOpenOnMobile(!openOnMobile); - } - - if (onDropdownToggleClicked) { + } else if (onDropdownToggleClicked) { onDropdownToggleClicked(); } }} From c1df2c45de70505f4794db0ef213a44b41dc5f45 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 10:47:28 +0200 Subject: [PATCH 21/23] fix(context_menu): regression on mobile sub-menu --- apps/client/src/stylesheets/theme-next/base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index 60cc0230c..e3468421e 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -172,7 +172,7 @@ body.desktop .dropdown-submenu .dropdown-menu { } .dropdown-item:not(.dropdown-submenu), -.dropdown-item.dropdown-submenu .dropdown-toggle, +body.desktop .dropdown-item.dropdown-submenu .dropdown-toggle, .excalidraw .context-menu .context-menu-item { padding-top: var(--menu-item-vertical-padding) !important; padding-bottom: var(--menu-item-vertical-padding) !important; From 862ddf3a71c3c47a01beda4d70c9bbdc6dec6915 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 10:51:54 +0200 Subject: [PATCH 22/23] fix(mermaid): 1px border visible in read-only mode --- apps/client/src/widgets/type_widgets/helpers/SplitEditor.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/helpers/SplitEditor.css b/apps/client/src/widgets/type_widgets/helpers/SplitEditor.css index 7ea0a003c..09821931c 100644 --- a/apps/client/src/widgets/type_widgets/helpers/SplitEditor.css +++ b/apps/client/src/widgets/type_widgets/helpers/SplitEditor.css @@ -43,7 +43,7 @@ /* Horizontal layout */ -.note-detail-split.split-horizontal > .note-detail-split-preview-col { +.note-detail-split.split-horizontal:not(.split-read-only) > .note-detail-split-preview-col { border-inline-start: 1px solid var(--main-border-color); } @@ -93,4 +93,4 @@ height: 100%; max-width: 100%; } -/* #endregion */ \ No newline at end of file +/* #endregion */ From a9b34792167ef7ff4bca8b684eea22c905f826a6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 10:54:39 +0200 Subject: [PATCH 23/23] fix(mermaid): preview not 100% height on vertical layout --- .../type_widgets/helpers/SplitEditor.css | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/helpers/SplitEditor.css b/apps/client/src/widgets/type_widgets/helpers/SplitEditor.css index 09821931c..72f680b01 100644 --- a/apps/client/src/widgets/type_widgets/helpers/SplitEditor.css +++ b/apps/client/src/widgets/type_widgets/helpers/SplitEditor.css @@ -43,44 +43,48 @@ /* Horizontal layout */ -.note-detail-split.split-horizontal:not(.split-read-only) > .note-detail-split-preview-col { - border-inline-start: 1px solid var(--main-border-color); -} +.note-detail-split.split-horizontal:not(.split-read-only) { + &> .note-detail-split-preview-col { + border-inline-start: 1px solid var(--main-border-color); + } -.note-detail-split.split-horizontal > .note-detail-split-editor-col, -.note-detail-split.split-horizontal > .note-detail-split-preview-col { - height: 100%; - width: 50%; -} + &> .note-detail-split-editor-col, + &> .note-detail-split-preview-col { + height: 100%; + width: 50%; + } -.note-detail-split.split-horizontal .note-detail-split-preview { - height: 100%; + .note-detail-split-preview { + height: 100%; + } } /* Vertical layout */ .note-detail-split.split-vertical { flex-direction: column; + + &> .note-detail-split-editor-col, + &> .note-detail-split-preview-col { + width: 100%; + height: 50%; + } + + &> .note-detail-split-editor-col { + border-top: 1px solid var(--main-border-color); + } + + &> .note-detail-split-preview-col { + order: -1; + } } -.note-detail-split.split-vertical > .note-detail-split-editor-col, -.note-detail-split.split-vertical > .note-detail-split-preview-col { - width: 100%; - height: 50%; -} - -.note-detail-split.split-vertical > .note-detail-split-editor-col { - border-top: 1px solid var(--main-border-color); -} - -.note-detail-split.split-vertical .note-detail-split-preview-col { - order: -1; -} /* Read-only view */ .note-detail-split.split-read-only .note-detail-split-preview-col { width: 100%; + height: 100%; } /* #region SVG */