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/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
new file mode 100644
index 000000000..325d9ea47
--- /dev/null
+++ b/apps/client/src/widgets/dialogs/PopupEditor.css
@@ -0,0 +1,64 @@
+/** 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;
+ display: flex;
+ flex-direction: column;
+}
+
+.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..c2d98b544
--- /dev/null
+++ b/apps/client/src/widgets/dialogs/PopupEditor.tsx
@@ -0,0 +1,85 @@
+import { useContext, useEffect, useRef, useState } from "preact/hooks";
+import Modal from "../react/Modal";
+import "./PopupEditor.css";
+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);
+ 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);
+ });
+
+ // 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 (
+
+
+ }
+ 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 } = useNoteContext();
+ const wrapperRef = useRef(null);
+ 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 ]);
+
+ 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 80f8f5915..000000000
--- a/apps/client/src/widgets/dialogs/popup_editor.ts
+++ /dev/null
@@ -1,187 +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";
-
-const TPL = /*html*/`\
-
-`;
-
-export default class PopupEditorDialog extends Container {
-
- private noteContext: NoteContext;
- private $modalHeader!: JQuery;
- private $modalBody!: JQuery;
- private $wrapper!: JQuery;
-
- constructor() {
- super();
- this.noteContext = new NoteContext("_popup-editor");
- }
-
- 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 $dialog = await openDialog(this.$widget, false, {
- focus: false
- });
-
- await this.noteContext.setNote(noteIdOrPath, {
- viewScope: {
- readOnlyTemporarilyDisabled: true
- }
- });
-
- 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 () => {
- await this.handleEventInChildren("activeContextChanged", { noteContext: this.noteContext });
- this.setVisibility(true);
- await this.handleEventInChildren("focusOnDetail", { ntxId: this.noteContext.ntxId });
- });
- $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 856896f2c..34a7c9ed8 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,18 +257,29 @@ export function useUniqueName(prefix?: string) {
}
export function useNoteContext() {
- const [ noteContext, setNoteContext ] = useState();
+ const noteContextContext = useContext(NoteContextContext);
+ const [ noteContext, setNoteContext ] = useState(noteContextContext ?? undefined);
const [ notePath, setNotePath ] = useState();
const [ note, setNote ] = useState();
const [ , setViewScope ] = useState();
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);
+ setIsReadOnlyTemporarilyDisabled(noteContextContext?.viewScope?.readOnlyTemporarilyDisabled);
+ }, [ noteContextContext ]);
+
useEffect(() => {
setNote(noteContext?.note);
}, [ notePath ]);
useTriliumEvents([ "setNoteContext", "activeContextChanged", "noteSwitchedAndActivated", "noteSwitched" ], ({ noteContext }) => {
+ if (noteContextContext) return;
setNoteContext(noteContext);
setNotePath(noteContext.notePath);
setViewScope(noteContext.viewScope);
@@ -282,6 +293,7 @@ export function useNoteContext() {
}
});
useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => {
+ if (noteContextContext) return;
if (eventNoteContext.ntxId === noteContext?.ntxId) {
setIsReadOnlyTemporarilyDisabled(eventNoteContext?.viewScope?.readOnlyTemporarilyDisabled);
}
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.
*
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 ]);