From 31180afbd1199580f57786b9d0592a0bdf7fcbeb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 19:07:50 +0200 Subject: [PATCH 01/11] react(quick_edit): set up empty dialog --- apps/client/src/layouts/layout_commons.tsx | 22 +---- .../src/widgets/dialogs/PopupEditor.css | 62 ++++++++++++++ .../src/widgets/dialogs/PopupEditor.tsx | 31 +++++++ .../src/widgets/dialogs/popup_editor.ts | 80 ------------------- .../text/CKEditorWithWatchdog.tsx | 1 + 5 files changed, 96 insertions(+), 100 deletions(-) create mode 100644 apps/client/src/widgets/dialogs/PopupEditor.css create mode 100644 apps/client/src/widgets/dialogs/PopupEditor.tsx diff --git a/apps/client/src/layouts/layout_commons.tsx b/apps/client/src/layouts/layout_commons.tsx index 031ef03de..62f810430 100644 --- a/apps/client/src/layouts/layout_commons.tsx +++ b/apps/client/src/layouts/layout_commons.tsx @@ -22,16 +22,8 @@ import RevisionsDialog from "../widgets/dialogs/revisions.js"; import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js"; import InfoDialog from "../widgets/dialogs/info.js"; import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js"; -import PopupEditorDialog from "../widgets/dialogs/popup_editor.js"; -import FlexContainer from "../widgets/containers/flex_container.js"; -import NoteIconWidget from "../widgets/note_icon"; -import PromotedAttributesWidget from "../widgets/promoted_attributes.js"; import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx"; -import NoteTitleWidget from "../widgets/note_title.jsx"; -import FormattingToolbar from "../widgets/ribbon/FormattingToolbar.js"; -import NoteList from "../widgets/collections/NoteList.jsx"; -import NoteDetail from "../widgets/NoteDetail.jsx"; -import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx"; +import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx"; export function applyModals(rootContainer: RootContainer) { rootContainer @@ -57,16 +49,6 @@ export function applyModals(rootContainer: RootContainer) { .child() .child() .child() - .child(new PopupEditorDialog() - .child(new FlexContainer("row") - .class("title-row") - .css("align-items", "center") - .cssBlock(".title-row > * { margin: 5px; }") - .child() - .child()) - .child() - .child(new PromotedAttributesWidget()) - .child() - .child()) + .child() .child(); } diff --git a/apps/client/src/widgets/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css new file mode 100644 index 000000000..ce6494f33 --- /dev/null +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -0,0 +1,62 @@ +/** Reduce the z-index of modals so that ckeditor popups are properly shown on top of it. */ +body.popup-editor-open > .modal-backdrop { z-index: 998; } +body.popup-editor-open .popup-editor-dialog { z-index: 999; } +body.popup-editor-open .ck-clipboard-drop-target-line { z-index: 1000; } + +body.desktop .modal.popup-editor-dialog .modal-dialog { + max-width: 75vw; +} + +.modal.popup-editor-dialog .modal-header .modal-title { + font-size: 1.1em; +} + +.modal.popup-editor-dialog .modal-header .title-row { + flex-grow: 1; + display: flex; + align-items: center; +} + +.modal.popup-editor-dialog .modal-header .title-row > * { + margin: 5px; +} + +.modal.popup-editor-dialog .modal-body { + padding: 0; + height: 75vh; + overflow: auto; +} + +.modal.popup-editor-dialog .note-detail-editable-text { + padding: 0 1em; +} + +.modal.popup-editor-dialog .title-row, +.modal.popup-editor-dialog .modal-title, +.modal.popup-editor-dialog .note-icon-widget { + height: 32px; +} + +.modal.popup-editor-dialog .note-icon-widget { + width: 32px; + margin: 0; + padding: 0; +} + +.modal.popup-editor-dialog .note-icon-widget button.note-icon, +.modal.popup-editor-dialog .note-title-widget input.note-title { + font-size: 1em; +} + +.modal.popup-editor-dialog .classic-toolbar-widget { + position: sticky; + top: 0; + inset-inline-start: 0; + inset-inline-end: 0; + background: var(--modal-background-color); + z-index: 998; +} + +.modal.popup-editor-dialog .note-detail-file { + padding: 0; +} \ No newline at end of file diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx new file mode 100644 index 000000000..7969e3ac0 --- /dev/null +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -0,0 +1,31 @@ +import { useState } from "preact/hooks"; +import Modal from "../react/Modal"; +import "./PopupEditor.css"; +import { useTriliumEvent } from "../react/hooks"; +import NoteTitleWidget from "../note_title"; +import NoteIcon from "../note_icon"; + +export default function PopupEditor() { + const [ shown, setShown ] = useState(false); + + useTriliumEvent("openInPopup", () => { + setShown(true); + }); + + return ( + + + + + )} + className="popup-editor-dialog" + size="lg" + show={shown} + onHidden={() => setShown(false)} + > + Body goes here + + ) +} diff --git a/apps/client/src/widgets/dialogs/popup_editor.ts b/apps/client/src/widgets/dialogs/popup_editor.ts index 80f8f5915..bc862822e 100644 --- a/apps/client/src/widgets/dialogs/popup_editor.ts +++ b/apps/client/src/widgets/dialogs/popup_editor.ts @@ -4,82 +4,6 @@ import { openDialog } from "../../services/dialog.js"; import BasicWidget, { ReactWrappedWidget } from "../basic_widget.js"; import Container from "../containers/container.js"; -const TPL = /*html*/`\ - -`; - export default class PopupEditorDialog extends Container { private noteContext: NoteContext; @@ -110,10 +34,6 @@ export default class PopupEditorDialog extends Container { } async openInPopupEvent({ noteIdOrPath }: EventData<"openInPopup">) { - const $dialog = await openDialog(this.$widget, false, { - focus: false - }); - await this.noteContext.setNote(noteIdOrPath, { viewScope: { readOnlyTemporarilyDisabled: true diff --git a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx index e19032d46..789e0d006 100644 --- a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx +++ b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx @@ -41,6 +41,7 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe const [ editor, setEditor ] = useState(); const { parentComponent, ntxId } = useNoteContext(); + console.log("Register with ntxId", ntxId); useKeyboardShortcuts("text-detail", containerRef, parentComponent, ntxId); useImperativeHandle(editorApi, () => ({ From 29f049c4118d098fd297327c50b3b7c1855e7ca3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 19:11:20 +0200 Subject: [PATCH 02/11] chore(quick_edit): inject note context --- .../src/widgets/dialogs/PopupEditor.tsx | 21 +++++++++++++++++-- .../src/widgets/dialogs/popup_editor.ts | 9 ++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index 7969e3ac0..6b305fbb2 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -1,17 +1,34 @@ -import { useState } from "preact/hooks"; +import { useContext, useEffect, useState } from "preact/hooks"; import Modal from "../react/Modal"; import "./PopupEditor.css"; import { useTriliumEvent } from "../react/hooks"; import NoteTitleWidget from "../note_title"; import NoteIcon from "../note_icon"; +import NoteContext from "../../components/note_context"; +import { ParentComponent } from "../react/react_utils"; + +const noteContext = new NoteContext("_popup-editor"); export default function PopupEditor() { const [ shown, setShown ] = useState(false); + const parentComponent = useContext(ParentComponent); + + useTriliumEvent("openInPopup", async ({ noteIdOrPath }) => { + await noteContext.setNote(noteIdOrPath, { + viewScope: { + readOnlyTemporarilyDisabled: true + } + }); - useTriliumEvent("openInPopup", () => { setShown(true); }); + // Inject the note context + useEffect(() => { + if (!shown || !parentComponent) return; + parentComponent.handleEventInChildren("activeContextChanged", { noteContext }); + }, [ shown ]); + return ( { constructor() { super(); - this.noteContext = new NoteContext("_popup-editor"); + this.noteContext = } doRender() { @@ -34,11 +34,7 @@ export default class PopupEditorDialog extends Container { } async openInPopupEvent({ noteIdOrPath }: EventData<"openInPopup">) { - await this.noteContext.setNote(noteIdOrPath, { - viewScope: { - readOnlyTemporarilyDisabled: true - } - }); + const colorClass = this.noteContext.note?.getColorClass(); const wrapperElement = this.$wrapper.get(0)!; @@ -61,7 +57,6 @@ export default class PopupEditorDialog extends Container { } $dialog.on("shown.bs.modal", async () => { - await this.handleEventInChildren("activeContextChanged", { noteContext: this.noteContext }); this.setVisibility(true); await this.handleEventInChildren("focusOnDetail", { ntxId: this.noteContext.ntxId }); }); From 5531c15126b18857eae3f78233f07307c9fd71ee Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 19:17:50 +0200 Subject: [PATCH 03/11] chore(quick_edit): get note content to render --- apps/client/src/stylesheets/style.css | 2 +- .../src/widgets/dialogs/PopupEditor.css | 2 ++ .../src/widgets/dialogs/PopupEditor.tsx | 19 ++++++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 9ef1c5e2d..36805a3d4 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -2591,7 +2591,7 @@ iframe.print-iframe { flex-direction: column; } -.scrolling-container > .note-detail.full-height, +.note-detail.full-height, .scrolling-container > .note-list-widget.full-height { position: relative; flex-grow: 1; diff --git a/apps/client/src/widgets/dialogs/PopupEditor.css b/apps/client/src/widgets/dialogs/PopupEditor.css index ce6494f33..325d9ea47 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.css +++ b/apps/client/src/widgets/dialogs/PopupEditor.css @@ -25,6 +25,8 @@ body.desktop .modal.popup-editor-dialog .modal-dialog { padding: 0; height: 75vh; overflow: auto; + display: flex; + flex-direction: column; } .modal.popup-editor-dialog .note-detail-editable-text { diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index 6b305fbb2..d28e7d6d8 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -6,6 +6,7 @@ import NoteTitleWidget from "../note_title"; import NoteIcon from "../note_icon"; import NoteContext from "../../components/note_context"; import { ParentComponent } from "../react/react_utils"; +import NoteDetail from "../NoteDetail"; const noteContext = new NoteContext("_popup-editor"); @@ -31,18 +32,22 @@ export default function PopupEditor() { return ( - - - - )} + title={} className="popup-editor-dialog" size="lg" show={shown} onHidden={() => setShown(false)} > - Body goes here + ) } + +export function TitleRow() { + return ( +
+ + +
+ ) +} From bb9cb2fb75e3fbde1835c82881804ead43c33e8a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 19:25:27 +0200 Subject: [PATCH 04/11] fix(quick_edit): note context not injected on first render --- .../src/widgets/dialogs/PopupEditor.tsx | 29 ++++++++----------- apps/client/src/widgets/react/hooks.tsx | 5 ++-- apps/client/src/widgets/react/react_utils.tsx | 3 ++ 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index d28e7d6d8..b19851bb9 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -5,14 +5,13 @@ import { useTriliumEvent } from "../react/hooks"; import NoteTitleWidget from "../note_title"; import NoteIcon from "../note_icon"; import NoteContext from "../../components/note_context"; -import { ParentComponent } from "../react/react_utils"; +import { NoteContextContext, ParentComponent } from "../react/react_utils"; import NoteDetail from "../NoteDetail"; const noteContext = new NoteContext("_popup-editor"); export default function PopupEditor() { const [ shown, setShown ] = useState(false); - const parentComponent = useContext(ParentComponent); useTriliumEvent("openInPopup", async ({ noteIdOrPath }) => { await noteContext.setNote(noteIdOrPath, { @@ -24,22 +23,18 @@ export default function PopupEditor() { setShown(true); }); - // Inject the note context - useEffect(() => { - if (!shown || !parentComponent) return; - parentComponent.handleEventInChildren("activeContextChanged", { noteContext }); - }, [ shown ]); - return ( - } - className="popup-editor-dialog" - size="lg" - show={shown} - onHidden={() => setShown(false)} - > - - + + } + className="popup-editor-dialog" + size="lg" + show={shown} + onHidden={() => setShown(false)} + > + + + ) } diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 856896f2c..63fa4fff1 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -2,7 +2,7 @@ import { CSSProperties } from "preact/compat"; import { DragData } from "../note_tree"; import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons"; import { MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; -import { ParentComponent, refToJQuerySelector } from "./react_utils"; +import { NoteContextContext, ParentComponent, refToJQuerySelector } from "./react_utils"; import { RefObject, VNode } from "preact"; import { Tooltip } from "bootstrap"; import { ViewMode, ViewScope } from "../../services/link"; @@ -257,7 +257,8 @@ export function useUniqueName(prefix?: string) { } export function useNoteContext() { - const [ noteContext, setNoteContext ] = useState(); + const noteContextContext = useContext(NoteContextContext); + const [ noteContext, setNoteContext ] = useState(noteContextContext); const [ notePath, setNotePath ] = useState(); const [ note, setNote ] = useState(); const [ , setViewScope ] = useState(); diff --git a/apps/client/src/widgets/react/react_utils.tsx b/apps/client/src/widgets/react/react_utils.tsx index d752662f5..468a0e73e 100644 --- a/apps/client/src/widgets/react/react_utils.tsx +++ b/apps/client/src/widgets/react/react_utils.tsx @@ -1,8 +1,11 @@ import { ComponentChild, createContext, render, type JSX, type RefObject } from "preact"; import Component from "../../components/component"; +import NoteContext from "../../components/note_context"; export const ParentComponent = createContext(null); +export const NoteContextContext = createContext(null); + /** * Takes in a React ref and returns a corresponding JQuery selector. * From 2f440eba37f62b1ca098a57b882ef50f6c7e19a0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 19:34:14 +0200 Subject: [PATCH 05/11] chore(quick_edit): bring back focus --- apps/client/src/widgets/dialogs/PopupEditor.tsx | 4 ++++ apps/client/src/widgets/dialogs/popup_editor.ts | 1 - .../src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index b19851bb9..070244b56 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -12,6 +12,7 @@ const noteContext = new NoteContext("_popup-editor"); export default function PopupEditor() { const [ shown, setShown ] = useState(false); + const parentComponent = useContext(ParentComponent); useTriliumEvent("openInPopup", async ({ noteIdOrPath }) => { await noteContext.setNote(noteIdOrPath, { @@ -30,6 +31,9 @@ export default function PopupEditor() { className="popup-editor-dialog" size="lg" show={shown} + onShown={() => { + parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId }); + }} onHidden={() => setShown(false)} > diff --git a/apps/client/src/widgets/dialogs/popup_editor.ts b/apps/client/src/widgets/dialogs/popup_editor.ts index 9899ad163..1cadb3ab6 100644 --- a/apps/client/src/widgets/dialogs/popup_editor.ts +++ b/apps/client/src/widgets/dialogs/popup_editor.ts @@ -58,7 +58,6 @@ export default class PopupEditorDialog extends Container { $dialog.on("shown.bs.modal", async () => { this.setVisibility(true); - await this.handleEventInChildren("focusOnDetail", { ntxId: this.noteContext.ntxId }); }); $dialog.on("hidden.bs.modal", () => { const $typeWidgetEl = $dialog.find(".note-detail-printable"); diff --git a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx index 789e0d006..e19032d46 100644 --- a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx +++ b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx @@ -41,7 +41,6 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe const [ editor, setEditor ] = useState(); const { parentComponent, ntxId } = useNoteContext(); - console.log("Register with ntxId", ntxId); useKeyboardShortcuts("text-detail", containerRef, parentComponent, ntxId); useImperativeHandle(editorApi, () => ({ From 8001d940eb5cd39be6d8a77eafd797da419883b8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 20:08:37 +0200 Subject: [PATCH 06/11] chore(quick_edit): bring back coloring --- .../src/widgets/dialogs/PopupEditor.tsx | 56 +++++++--- .../src/widgets/dialogs/popup_editor.ts | 101 ------------------ apps/client/src/widgets/react/hooks.tsx | 9 ++ 3 files changed, 49 insertions(+), 117 deletions(-) delete mode 100644 apps/client/src/widgets/dialogs/popup_editor.ts diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index 070244b56..583b13abd 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -1,47 +1,71 @@ -import { useContext, useEffect, useState } from "preact/hooks"; +import { useContext, useEffect, useRef, useState } from "preact/hooks"; import Modal from "../react/Modal"; import "./PopupEditor.css"; -import { useTriliumEvent } from "../react/hooks"; +import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks"; import NoteTitleWidget from "../note_title"; import NoteIcon from "../note_icon"; import NoteContext from "../../components/note_context"; import { NoteContextContext, ParentComponent } from "../react/react_utils"; import NoteDetail from "../NoteDetail"; - -const noteContext = new NoteContext("_popup-editor"); +import { ComponentChildren } from "preact"; export default function PopupEditor() { const [ shown, setShown ] = useState(false); const parentComponent = useContext(ParentComponent); + const [ noteContext, setNoteContext ] = useState(new NoteContext("_popup-editor")); useTriliumEvent("openInPopup", async ({ noteIdOrPath }) => { + const noteContext = new NoteContext("_popup-editor"); await noteContext.setNote(noteIdOrPath, { viewScope: { readOnlyTemporarilyDisabled: true } }); + setNoteContext(noteContext); setShown(true); }); return ( - } - className="popup-editor-dialog" - size="lg" - show={shown} - onShown={() => { - parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId }); - }} - onHidden={() => setShown(false)} - > - - + + } + className="popup-editor-dialog" + size="lg" + show={shown} + onShown={() => { + parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId }); + }} + onHidden={() => setShown(false)} + > + + + ) } +export function DialogWrapper({ children }: { children: ComponentChildren }) { + const { note, ntxId } = useNoteContext(); + const wrapperRef = useRef(null); + const colorClass = note?.getColorClass(); + const [ hasTint, setHasTint ] = useState(false); + + // Apply the tinted-dialog class only if the custom color CSS class specifies a hue + useEffect(() => { + if (!wrapperRef.current) return; + const customHue = getComputedStyle(wrapperRef.current).getPropertyValue("--custom-color-hue"); + setHasTint(!!customHue); + }, [ note, colorClass ]); + + return ( +
+ {children} +
+ ) +} + export function TitleRow() { return (
diff --git a/apps/client/src/widgets/dialogs/popup_editor.ts b/apps/client/src/widgets/dialogs/popup_editor.ts deleted file mode 100644 index 1cadb3ab6..000000000 --- a/apps/client/src/widgets/dialogs/popup_editor.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type { EventNames, EventData } from "../../components/app_context.js"; -import NoteContext from "../../components/note_context.js"; -import { openDialog } from "../../services/dialog.js"; -import BasicWidget, { ReactWrappedWidget } from "../basic_widget.js"; -import Container from "../containers/container.js"; - -export default class PopupEditorDialog extends Container { - - private noteContext: NoteContext; - private $modalHeader!: JQuery; - private $modalBody!: JQuery; - private $wrapper!: JQuery; - - constructor() { - super(); - this.noteContext = - } - - doRender() { - // This will populate this.$widget with the content of the children. - super.doRender(); - - // Now we wrap it in the modal. - const $newWidget = $(TPL); - this.$modalHeader = $newWidget.find(".modal-title"); - this.$modalBody = $newWidget.find(".modal-body"); - this.$wrapper = $newWidget.find(".quick-edit-dialog-wrapper"); - - const children = this.$widget.children(); - this.$modalHeader.append(children[0]); - this.$modalBody.append(children.slice(1)); - this.$widget = $newWidget; - this.setVisibility(false); - } - - async openInPopupEvent({ noteIdOrPath }: EventData<"openInPopup">) { - - - const colorClass = this.noteContext.note?.getColorClass(); - const wrapperElement = this.$wrapper.get(0)!; - - if (colorClass) { - wrapperElement.className = "quick-edit-dialog-wrapper " + colorClass; - } else { - wrapperElement.className = "quick-edit-dialog-wrapper"; - } - - const customHue = getComputedStyle(wrapperElement).getPropertyValue("--custom-color-hue"); - if (customHue) { - /* Apply the tinted-dialog class only if the custom color CSS class specifies a hue */ - wrapperElement.classList.add("tinted-quick-edit-dialog"); - } - - const activeEl = document.activeElement; - if (activeEl && "blur" in activeEl) { - (activeEl as HTMLElement).blur(); - } - - $dialog.on("shown.bs.modal", async () => { - this.setVisibility(true); - }); - $dialog.on("hidden.bs.modal", () => { - const $typeWidgetEl = $dialog.find(".note-detail-printable"); - if ($typeWidgetEl.length) { - const typeWidget = glob.getComponentByEl($typeWidgetEl[0]) as ReactWrappedWidget; - typeWidget.cleanup(); - } - - this.setVisibility(false); - }); - } - - setVisibility(visible: boolean) { - const $bodyItems = this.$modalBody.find("> div"); - if (visible) { - $bodyItems.fadeIn(); - this.$modalHeader.children().show(); - document.body.classList.add("popup-editor-open"); - - } else { - $bodyItems.hide(); - this.$modalHeader.children().hide(); - document.body.classList.remove("popup-editor-open"); - } - } - - handleEventInChildren(name: T, data: EventData): Promise | null { - // Avoid events related to the current tab interfere with our popup. - if (["noteSwitched", "noteSwitchedAndActivated", "exportAsPdf", "printActiveNote"].includes(name)) { - return Promise.resolve(); - } - - // Avoid not showing recent notes when creating a new empty tab. - if ("noteContext" in data && data.noteContext.ntxId !== "_popup-editor") { - return Promise.resolve(); - } - - return super.handleEventInChildren(name, data); - } - -} diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 63fa4fff1..82d01750c 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -265,11 +265,20 @@ export function useNoteContext() { const [ isReadOnlyTemporarilyDisabled, setIsReadOnlyTemporarilyDisabled ] = useState(noteContext?.viewScope?.isReadOnly); const [ refreshCounter, setRefreshCounter ] = useState(0); + useEffect(() => { + if (!noteContextContext) return; + setNoteContext(noteContextContext); + setNote(noteContextContext.note); + setNotePath(noteContextContext.notePath); + setViewScope(noteContextContext.viewScope); + }, [ noteContextContext ]); + useEffect(() => { setNote(noteContext?.note); }, [ notePath ]); useTriliumEvents([ "setNoteContext", "activeContextChanged", "noteSwitchedAndActivated", "noteSwitched" ], ({ noteContext }) => { + if (noteContextContext) return; setNoteContext(noteContext); setNotePath(noteContext.notePath); setViewScope(noteContext.viewScope); From 2985c762e62afe7eb088ea06e318d3e2c4b87c15 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 21:00:55 +0200 Subject: [PATCH 07/11] chore(quick_edit): add back most of the components --- apps/client/src/widgets/dialogs/PopupEditor.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index 583b13abd..0d84e5ba5 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -1,13 +1,16 @@ import { useContext, useEffect, useRef, useState } from "preact/hooks"; import Modal from "../react/Modal"; import "./PopupEditor.css"; -import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks"; +import { useNoteContext, useTriliumEvent } from "../react/hooks"; import NoteTitleWidget from "../note_title"; import NoteIcon from "../note_icon"; import NoteContext from "../../components/note_context"; import { NoteContextContext, ParentComponent } from "../react/react_utils"; import NoteDetail from "../NoteDetail"; import { ComponentChildren } from "preact"; +import NoteList from "../collections/NoteList"; +import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter"; +import FormattingToolbar from "../ribbon/FormattingToolbar"; export default function PopupEditor() { const [ shown, setShown ] = useState(false); @@ -39,7 +42,9 @@ export default function PopupEditor() { }} onHidden={() => setShown(false)} > + + From 6946da357185064c34896e6776a4c24774f9ec09 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 21:07:17 +0200 Subject: [PATCH 08/11] chore(mermaid): avoid crash if "Matrix is not invertible" --- .../src/widgets/type_widgets/helpers/SvgSplitEditor.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/type_widgets/helpers/SvgSplitEditor.tsx b/apps/client/src/widgets/type_widgets/helpers/SvgSplitEditor.tsx index 8a9e64a24..7f95bfb1b 100644 --- a/apps/client/src/widgets/type_widgets/helpers/SvgSplitEditor.tsx +++ b/apps/client/src/widgets/type_widgets/helpers/SvgSplitEditor.tsx @@ -163,7 +163,12 @@ function useResizer(containerRef: RefObject, noteId: string, svg pan: zoomInstance.getPan(), zoom: zoomInstance.getZoom() } - zoomInstance.destroy(); + try { + zoomInstance.destroy(); + } catch (e) { + // Sometimes crashes with "Matrix is not invertible" which can cause havoc such as breaking the popup editor from ever showing up again. + console.warn(e); + } }; }, [ svg ]); From 56c82d7f0fc255cc3015b39183ff90de6fcab48e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 21:20:24 +0200 Subject: [PATCH 09/11] chore(quick_edit): address requested changes --- apps/client/src/widgets/dialogs/PopupEditor.tsx | 4 ++-- apps/client/src/widgets/react/hooks.tsx | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index 0d84e5ba5..4950a4103 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -62,10 +62,10 @@ export function DialogWrapper({ children }: { children: ComponentChildren }) { if (!wrapperRef.current) return; const customHue = getComputedStyle(wrapperRef.current).getPropertyValue("--custom-color-hue"); setHasTint(!!customHue); - }, [ note, colorClass ]); + }, [ note ]); return ( -
+
{children}
) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 82d01750c..f30957bef 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -271,6 +271,7 @@ export function useNoteContext() { setNote(noteContextContext.note); setNotePath(noteContextContext.notePath); setViewScope(noteContextContext.viewScope); + setIsReadOnlyTemporarilyDisabled(noteContextContext?.viewScope?.readOnlyTemporarilyDisabled); }, [ noteContextContext ]); useEffect(() => { @@ -292,6 +293,7 @@ export function useNoteContext() { } }); useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => { + if (noteContextContext) return; if (eventNoteContext.ntxId === noteContext?.ntxId) { setIsReadOnlyTemporarilyDisabled(eventNoteContext?.viewScope?.readOnlyTemporarilyDisabled); } From 26f7264f3cb93f658f41bb67c30a259b0e1d163b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 21:37:37 +0200 Subject: [PATCH 10/11] chore(client): fix typecheck --- apps/client/src/widgets/react/hooks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index f30957bef..34a7c9ed8 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -258,7 +258,7 @@ export function useUniqueName(prefix?: string) { export function useNoteContext() { const noteContextContext = useContext(NoteContextContext); - const [ noteContext, setNoteContext ] = useState(noteContextContext); + const [ noteContext, setNoteContext ] = useState(noteContextContext ?? undefined); const [ notePath, setNotePath ] = useState(); const [ note, setNote ] = useState(); const [ , setViewScope ] = useState(); From 5edc4abfb42d8fe1ef71bb2fbca5d882832bfc3a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 21:37:41 +0200 Subject: [PATCH 11/11] chore(quick_edit): address requested changes --- apps/client/src/widgets/dialogs/PopupEditor.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/dialogs/PopupEditor.tsx b/apps/client/src/widgets/dialogs/PopupEditor.tsx index 4950a4103..c2d98b544 100644 --- a/apps/client/src/widgets/dialogs/PopupEditor.tsx +++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx @@ -29,6 +29,11 @@ export default function PopupEditor() { setShown(true); }); + // Add a global class to be able to handle issues with z-index due to rendering in a popup. + useEffect(() => { + document.body.classList.toggle("popup-editor-open", shown); + }, [shown]); + return ( @@ -52,9 +57,8 @@ export default function PopupEditor() { } export function DialogWrapper({ children }: { children: ComponentChildren }) { - const { note, ntxId } = useNoteContext(); + const { note } = useNoteContext(); const wrapperRef = useRef(null); - const colorClass = note?.getColorClass(); const [ hasTint, setHasTint ] = useState(false); // Apply the tinted-dialog class only if the custom color CSS class specifies a hue