chore(react/type_widgets): bring back add include to note

This commit is contained in:
Elian Doran 2025-09-25 13:51:07 +03:00
parent 0ac428b57a
commit 3673162a48
No known key found for this signature in database
6 changed files with 76 additions and 59 deletions

View File

@ -34,6 +34,7 @@ import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx
import type RootContainer from "../widgets/containers/root_container.js"; import type RootContainer from "../widgets/containers/root_container.js";
import { SqlExecuteResults } from "@triliumnext/commons"; import { SqlExecuteResults } from "@triliumnext/commons";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx"; import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
interface Layout { interface Layout {
getRootWidget: (appContext: AppContext) => RootContainer; getRootWidget: (appContext: AppContext) => RootContainer;
@ -223,7 +224,7 @@ export type CommandMappings = {
showPasswordNotSet: CommandData; showPasswordNotSet: CommandData;
showProtectedSessionPasswordDialog: CommandData; showProtectedSessionPasswordDialog: CommandData;
showUploadAttachmentsDialog: CommandData & { noteId: string }; showUploadAttachmentsDialog: CommandData & { noteId: string };
showIncludeNoteDialog: CommandData & { textTypeWidget: EditableTextTypeWidget }; showIncludeNoteDialog: CommandData & IncludeNoteOpts;
showAddLinkDialog: CommandData & AddLinkOpts; showAddLinkDialog: CommandData & AddLinkOpts;
closeProtectedSessionPasswordDialog: CommandData; closeProtectedSessionPasswordDialog: CommandData;
copyImageReferenceToClipboard: CommandData; copyImageReferenceToClipboard: CommandData;

View File

@ -10,15 +10,20 @@ import tree from "../../services/tree";
import froca from "../../services/froca"; import froca from "../../services/froca";
import EditableTextTypeWidget, { type BoxSize } from "../type_widgets_old/editable_text"; import EditableTextTypeWidget, { type BoxSize } from "../type_widgets_old/editable_text";
import { useTriliumEvent } from "../react/hooks"; import { useTriliumEvent } from "../react/hooks";
import { CKEditorApi } from "../type_widgets/text/CKEditorWithWatchdog";
export interface IncludeNoteOpts {
editorApi: CKEditorApi;
}
export default function IncludeNoteDialog() { export default function IncludeNoteDialog() {
const [textTypeWidget, setTextTypeWidget] = useState<EditableTextTypeWidget>(); const editorApiRef = useRef<CKEditorApi>(null);
const [suggestion, setSuggestion] = useState<Suggestion | null>(null); const [suggestion, setSuggestion] = useState<Suggestion | null>(null);
const [boxSize, setBoxSize] = useState("medium"); const [boxSize, setBoxSize] = useState("medium");
const [shown, setShown] = useState(false); const [shown, setShown] = useState(false);
useTriliumEvent("showIncludeNoteDialog", ({ textTypeWidget }) => { useTriliumEvent("showIncludeNoteDialog", ({ editorApi }) => {
setTextTypeWidget(textTypeWidget); editorApiRef.current = editorApi;
setShown(true); setShown(true);
}); });
@ -32,12 +37,9 @@ export default function IncludeNoteDialog() {
onShown={() => triggerRecentNotes(autoCompleteRef.current)} onShown={() => triggerRecentNotes(autoCompleteRef.current)}
onHidden={() => setShown(false)} onHidden={() => setShown(false)}
onSubmit={() => { onSubmit={() => {
if (!suggestion?.notePath || !textTypeWidget) { if (!suggestion?.notePath || !editorApiRef.current) return;
return;
}
setShown(false); setShown(false);
includeNote(suggestion.notePath, textTypeWidget, boxSize as BoxSize); includeNote(suggestion.notePath, editorApiRef.current, boxSize as BoxSize);
}} }}
footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />} footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />}
show={shown} show={shown}
@ -69,7 +71,7 @@ export default function IncludeNoteDialog() {
) )
} }
async function includeNote(notePath: string, textTypeWidget: EditableTextTypeWidget, boxSize: BoxSize) { async function includeNote(notePath: string, editorApi: CKEditorApi, boxSize: BoxSize) {
const noteId = tree.getNoteIdFromUrl(notePath); const noteId = tree.getNoteIdFromUrl(notePath);
if (!noteId) { if (!noteId) {
return; return;
@ -79,8 +81,8 @@ async function includeNote(notePath: string, textTypeWidget: EditableTextTypeWid
if (["image", "canvas", "mermaid"].includes(note?.type ?? "")) { if (["image", "canvas", "mermaid"].includes(note?.type ?? "")) {
// there's no benefit to use insert note functionlity for images, // there's no benefit to use insert note functionlity for images,
// so we'll just add an IMG tag // so we'll just add an IMG tag
textTypeWidget.addImage(noteId); editorApi.addImage(noteId);
} else { } else {
textTypeWidget.addIncludeNote(noteId, boxSize); editorApi.addIncludeNote(noteId, boxSize);
} }
} }

View File

@ -3,6 +3,9 @@ import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKText
import { buildConfig, BuildEditorOptions } from "./config"; import { buildConfig, BuildEditorOptions } from "./config";
import { useLegacyImperativeHandlers } from "../../react/hooks"; import { useLegacyImperativeHandlers } from "../../react/hooks";
import link from "../../../services/link"; import link from "../../../services/link";
import froca from "../../../services/froca";
export type BoxSize = "small" | "medium" | "full";
export interface CKEditorApi { export interface CKEditorApi {
/** returns true if user selected some text, false if there's no selection */ /** returns true if user selected some text, false if there's no selection */
@ -10,6 +13,8 @@ export interface CKEditorApi {
getSelectedText(): string; getSelectedText(): string;
addLink(notePath: string, linkTitle: string | null, externalLink?: boolean): void; addLink(notePath: string, linkTitle: string | null, externalLink?: boolean): void;
addLinkToEditor(linkHref: string, linkTitle: string): void; addLinkToEditor(linkHref: string, linkTitle: string): void;
addIncludeNote(noteId: string, boxSize?: BoxSize): void;
addImage(noteId: string): Promise<void>;
} }
interface CKEditorWithWatchdogProps extends Pick<HTMLProps<HTMLDivElement>, "className" | "tabIndex"> { interface CKEditorWithWatchdogProps extends Pick<HTMLProps<HTMLDivElement>, "className" | "tabIndex"> {
@ -78,6 +83,35 @@ export default function CKEditorWithWatchdog({ content, contentLanguage, classNa
} }
}); });
}, },
addIncludeNote(noteId, boxSize) {
const editor = watchdogRef.current?.editor;
if (!editor) return;
editor?.model.change((writer) => {
// Insert <includeNote>*</includeNote> at the current selection position
// in a way that will result in creating a valid model structure
editor?.model.insertContent(
writer.createElement("includeNote", {
noteId: noteId,
boxSize: boxSize
})
);
});
},
async addImage(noteId) {
const editor = watchdogRef.current?.editor;
if (!editor) return;
const note = await froca.getNote(noteId);
if (!note) return;
editor.model.change(() => {
const encodedTitle = encodeURIComponent(note.title);
const src = `api/images/${note.noteId}/${encodedTitle}`;
editor?.execute("insertImage", { source: src });
});
},
})); }));
useLegacyImperativeHandlers({ useLegacyImperativeHandlers({

View File

@ -2,13 +2,14 @@ import { useRef, useState } from "preact/hooks";
import dialog from "../../../services/dialog"; import dialog from "../../../services/dialog";
import toast from "../../../services/toast"; import toast from "../../../services/toast";
import utils, { deferred, isMobile } from "../../../services/utils"; import utils, { deferred, isMobile } from "../../../services/utils";
import { useEditorSpacedUpdate, useKeyboardShortcuts, useNoteLabel, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks"; import { useEditorSpacedUpdate, useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteLabel, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import { TypeWidgetProps } from "../type_widget"; import { TypeWidgetProps } from "../type_widget";
import CKEditorWithWatchdog, { CKEditorApi } from "./CKEditorWithWatchdog"; import CKEditorWithWatchdog, { CKEditorApi } from "./CKEditorWithWatchdog";
import "./EditableText.css"; import "./EditableText.css";
import { CKTextEditor, ClassicEditor, EditorWatchdog } from "@triliumnext/ckeditor5"; import { CKTextEditor, ClassicEditor, EditorWatchdog } from "@triliumnext/ckeditor5";
import Component from "../../../components/component"; import Component from "../../../components/component";
import options from "../../../services/options"; import options from "../../../services/options";
import { loadIncludedNote, refreshIncludedNote } from "./utils";
/** /**
* The editor can operate into two distinct modes: * The editor can operate into two distinct modes:
@ -67,16 +68,30 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
editor?.editing.view.focus(); editor?.editing.view.focus();
}); });
useTriliumEvent("addLinkToText", ({ ntxId: eventNtxId }) => { useLegacyImperativeHandlers({
if (eventNtxId !== ntxId || !editorApiRef.current) return; addLinkToTextCommand() {
parentComponent?.triggerCommand("showAddLinkDialog", { if (!editorApiRef.current) return;
text: editorApiRef.current.getSelectedText(), parentComponent?.triggerCommand("showAddLinkDialog", {
hasSelection: editorApiRef.current.hasSelection(), text: editorApiRef.current.getSelectedText(),
async addLink(notePath, linkTitle, externalLink) { hasSelection: editorApiRef.current.hasSelection(),
await waitForEditor(); async addLink(notePath, linkTitle, externalLink) {
return editorApiRef.current?.addLink(notePath, linkTitle, externalLink); await waitForEditor();
} return editorApiRef.current?.addLink(notePath, linkTitle, externalLink);
}); }
});
},
addIncludeNoteToTextCommand() {
if (!editorApiRef.current) return;
parentComponent?.triggerCommand("showIncludeNoteDialog", {
editorApi: editorApiRef.current,
});
},
loadIncludedNote
});
useTriliumEvent("refreshIncludedNote", ({ noteId }) => {
if (!containerRef.current) return;
refreshIncludedNote(containerRef.current, noteId);
}); });
async function waitForEditor() { async function waitForEditor() {

View File

@ -39,6 +39,7 @@ export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetPro
// React to included note changes. // React to included note changes.
useTriliumEvent("refreshIncludedNote", ({ noteId }) => { useTriliumEvent("refreshIncludedNote", ({ noteId }) => {
console.log("Refresh ", noteId);
if (!contentRef.current) return; if (!contentRef.current) return;
refreshIncludedNote(contentRef.current, noteId); refreshIncludedNote(contentRef.current, noteId);
}); });

View File

@ -14,7 +14,6 @@ import type FNote from "../../entities/fnote.js";
import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig, EditorConfig } from "@triliumnext/ckeditor5"; import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig, EditorConfig } from "@triliumnext/ckeditor5";
import { updateTemplateCache } from "./ckeditor/snippets.js"; import { updateTemplateCache } from "./ckeditor/snippets.js";
export type BoxSize = "small" | "medium" | "full";
export default class EditableTextTypeWidget extends AbstractTextTypeWidget { export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
@ -105,37 +104,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
} }
} }
addIncludeNoteToTextCommand() {
this.triggerCommand("showIncludeNoteDialog", { textTypeWidget: this });
}
addIncludeNote(noteId: string, boxSize?: BoxSize) {
this.watchdog.editor?.model.change((writer) => {
// Insert <includeNote>*</includeNote> at the current selection position
// in a way that will result in creating a valid model structure
this.watchdog.editor?.model.insertContent(
writer.createElement("includeNote", {
noteId: noteId,
boxSize: boxSize
})
);
});
}
async addImage(noteId: string) {
const note = await froca.getNote(noteId);
if (!note || !this.watchdog.editor) {
return;
}
this.watchdog.editor.model.change((writer) => {
const encodedTitle = encodeURIComponent(note.title);
const src = `api/images/${note.noteId}/${encodedTitle}`;
this.watchdog.editor?.execute("insertImage", { source: src });
});
}
async createNoteForReferenceLink(title: string) { async createNoteForReferenceLink(title: string) {
if (!this.notePath) { if (!this.notePath) {
return; return;
@ -153,10 +121,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
return resp.note.getBestNotePathString(); return resp.note.getBestNotePathString();
} }
async refreshIncludedNoteEvent({ noteId }: EventData<"refreshIncludedNote">) {
this.refreshIncludedNote(this.$editor, noteId);
}
async reinitialize() { async reinitialize() {
const data = this.watchdog.editor?.getData(); const data = this.watchdog.editor?.getData();
await this.reinitializeWithData(data ?? ""); await this.reinitializeWithData(data ?? "");