chore(react/type_widgets): react to snippet changes

This commit is contained in:
Elian Doran 2025-09-25 14:21:12 +03:00
parent 3673162a48
commit a975576214
No known key found for this signature in database
5 changed files with 45 additions and 28 deletions

View File

@ -1,5 +1,5 @@
import { HTMLProps, RefObject, useEffect, useImperativeHandle, useRef, useState } from "preact/compat"; import { HTMLProps, RefObject, useEffect, useImperativeHandle, useRef, useState } from "preact/compat";
import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKTextEditor } from "@triliumnext/ckeditor5"; import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKTextEditor, TemplateDefinition } from "@triliumnext/ckeditor5";
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";
@ -29,9 +29,10 @@ interface CKEditorWithWatchdogProps extends Pick<HTMLProps<HTMLDivElement>, "cla
/** Called upon whenever a new CKEditor instance is initialized, whether it's the first initialization, after a crash or after a config change that requires it (e.g. content language). */ /** Called upon whenever a new CKEditor instance is initialized, whether it's the first initialization, after a crash or after a config change that requires it (e.g. content language). */
onEditorInitialized?: (editor: CKTextEditor) => void; onEditorInitialized?: (editor: CKTextEditor) => void;
editorApi: RefObject<CKEditorApi>; editorApi: RefObject<CKEditorApi>;
templates: TemplateDefinition[];
} }
export default function CKEditorWithWatchdog({ content, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, onNotificationWarning, onWatchdogStateChange, onChange, onEditorInitialized, editorApi }: CKEditorWithWatchdogProps) { export default function CKEditorWithWatchdog({ content, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, onNotificationWarning, onWatchdogStateChange, onChange, onEditorInitialized, editorApi, templates }: CKEditorWithWatchdogProps) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const watchdogRef = useRef<EditorWatchdog>(null); const watchdogRef = useRef<EditorWatchdog>(null);
const [ editor, setEditor ] = useState<CKTextEditor>(); const [ editor, setEditor ] = useState<CKTextEditor>();
@ -130,7 +131,8 @@ export default function CKEditorWithWatchdog({ content, contentLanguage, classNa
const editor = await buildEditor(container, !!isClassicEditor, { const editor = await buildEditor(container, !!isClassicEditor, {
forceGplLicense: false, forceGplLicense: false,
isClassicEditor: !!isClassicEditor, isClassicEditor: !!isClassicEditor,
contentLanguage: contentLanguage ?? null contentLanguage: contentLanguage ?? null,
templates
}); });
setEditor(editor); setEditor(editor);
@ -153,7 +155,7 @@ export default function CKEditorWithWatchdog({ content, contentLanguage, classNa
watchdog.create(container); watchdog.create(container);
return () => watchdog.destroy(); return () => watchdog.destroy();
}, [ contentLanguage ]); }, [ contentLanguage, templates ]);
// React to content changes. // React to content changes.
useEffect(() => editor?.setData(content ?? ""), [ editor, content ]); useEffect(() => editor?.setData(content ?? ""), [ editor, content ]);

View File

@ -1,4 +1,4 @@
import { useRef, useState } from "preact/hooks"; import { useEffect, 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";
@ -6,10 +6,11 @@ import { useEditorSpacedUpdate, useKeyboardShortcuts, useLegacyImperativeHandler
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, TemplateDefinition } 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"; import { loadIncludedNote, refreshIncludedNote } from "./utils";
import getTemplates, { updateTemplateCache } from "./snippets.js";
/** /**
* The editor can operate into two distinct modes: * The editor can operate into two distinct modes:
@ -47,7 +48,8 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
onContentChange(newContent) { onContentChange(newContent) {
setContent(newContent); setContent(newContent);
} }
}) });
const templates = useTemplates();
useTriliumEvent("scrollToEnd", () => { useTriliumEvent("scrollToEnd", () => {
const editor = watchdogRef.current?.editor; const editor = watchdogRef.current?.editor;
@ -127,7 +129,7 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
return ( return (
<div ref={containerRef} class={`note-detail-editable-text note-detail-printable ${codeBlockWordWrap ? "word-wrap" : ""}`}> <div ref={containerRef} class={`note-detail-editable-text note-detail-printable ${codeBlockWordWrap ? "word-wrap" : ""}`}>
{note && <CKEditorWithWatchdog {note && !!templates && <CKEditorWithWatchdog
className="note-detail-editable-text-editor use-tn-links" className="note-detail-editable-text-editor use-tn-links"
tabIndex={300} tabIndex={300}
content={content} content={content}
@ -143,6 +145,7 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
// A minimum number of milliseconds between saving the editor data internally (defaults to 5000). Note that for large documents, this might impact the editor performance. // A minimum number of milliseconds between saving the editor data internally (defaults to 5000). Note that for large documents, this might impact the editor performance.
saveInterval: 5000 saveInterval: 5000
}} }}
templates={templates}
onNotificationWarning={onNotificationWarning} onNotificationWarning={onNotificationWarning}
onWatchdogStateChange={onWatchdogStateChange} onWatchdogStateChange={onWatchdogStateChange}
onChange={() => spacedUpdate.scheduleUpdate()} onChange={() => spacedUpdate.scheduleUpdate()}
@ -160,6 +163,25 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
) )
} }
function useTemplates() {
const [ templates, setTemplates ] = useState<TemplateDefinition[]>();
useEffect(() => {
getTemplates().then(setTemplates);
}, []);
useTriliumEvent("entitiesReloaded", async ({ loadResults }) => {
console.log("Reloaded ", loadResults);
const newTemplates = await updateTemplateCache(loadResults);
if (newTemplates) {
console.log("Got new templates!", newTemplates);
setTemplates(newTemplates);
}
});
return templates;
}
function onWatchdogStateChange(watchdog: EditorWatchdog) { function onWatchdogStateChange(watchdog: EditorWatchdog) {
const currentState = watchdog.state; const currentState = watchdog.state;
logInfo(`CKEditor state changed to ${currentState}`); logInfo(`CKEditor state changed to ${currentState}`);

View File

@ -1,12 +1,11 @@
import { ALLOWED_PROTOCOLS } from "../../../services/link.js"; import { ALLOWED_PROTOCOLS } from "../../../services/link.js";
import { MIME_TYPE_AUTO } from "@triliumnext/commons"; import { MIME_TYPE_AUTO } from "@triliumnext/commons";
import { buildExtraCommands, type EditorConfig, PREMIUM_PLUGINS } from "@triliumnext/ckeditor5"; import { buildExtraCommands, type EditorConfig, PREMIUM_PLUGINS, TemplateDefinition } from "@triliumnext/ckeditor5";
import { getHighlightJsNameForMime } from "../../../services/mime_types.js"; import { getHighlightJsNameForMime } from "../../../services/mime_types.js";
import options from "../../../services/options.js"; import options from "../../../services/options.js";
import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js"; import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
import emojiDefinitionsUrl from "@triliumnext/ckeditor5/src/emoji_definitions/en.json?url"; import emojiDefinitionsUrl from "@triliumnext/ckeditor5/src/emoji_definitions/en.json?url";
import { copyTextWithToast } from "../../../services/clipboard_ext.js"; import { copyTextWithToast } from "../../../services/clipboard_ext.js";
import getTemplates from "./snippets.js";
import { t } from "../../../services/i18n.js"; import { t } from "../../../services/i18n.js";
import { getMermaidConfig } from "../../../services/mermaid.js"; import { getMermaidConfig } from "../../../services/mermaid.js";
import noteAutocompleteService, { type Suggestion } from "../../../services/note_autocomplete.js"; import noteAutocompleteService, { type Suggestion } from "../../../services/note_autocomplete.js";
@ -20,6 +19,7 @@ export interface BuildEditorOptions {
forceGplLicense: boolean; forceGplLicense: boolean;
isClassicEditor: boolean; isClassicEditor: boolean;
contentLanguage: string | null; contentLanguage: string | null;
templates: TemplateDefinition[];
} }
export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfig> { export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfig> {
@ -157,7 +157,7 @@ export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfi
extraCommands: buildExtraCommands() extraCommands: buildExtraCommands()
}, },
template: { template: {
definitions: await getTemplates() definitions: opts.templates
}, },
htmlSupport: { htmlSupport: {
allow: JSON.parse(options.get("allowedHtmlTags")) allow: JSON.parse(options.get("allowedHtmlTags"))

View File

@ -96,18 +96,22 @@ async function handleContentUpdate(affectedNoteIds: string[]) {
} }
} }
export function updateTemplateCache(loadResults: LoadResults): boolean { export async function updateTemplateCache(loadResults: LoadResults): Promise<TemplateDefinition[] | null> {
const affectedNoteIds = loadResults.getNoteIds(); const affectedNoteIds = loadResults.getNoteIds();
// React to creation or deletion of text snippets. // React to creation or deletion of text snippets.
if (loadResults.getAttributeRows().find((attr) => if (loadResults.getAttributeRows().find((attr) => {
attr.type === "label" && if (attr.type === "label") {
(attr.name === "textSnippet" || attr.name === "textSnippetDescription"))) { return (attr.name === "textSnippet" || attr.name === "textSnippetDescription");
handleFullReload(); } else if (attr.type === "relation") {
return (attr.value === "_template_text_snippet");
}
})) {
return await getTemplates();
} else if (affectedNoteIds.length > 0) { } else if (affectedNoteIds.length > 0) {
// Update content and titles if one of the template notes were updated. // Update content and titles if one of the template notes were updated.
debouncedHandleContentUpdate(affectedNoteIds); debouncedHandleContentUpdate(affectedNoteIds);
} }
return false; return null;
} }

View File

@ -12,7 +12,6 @@ import { buildSelectedBackgroundColor } from "../../components/touch_bar.js";
import { buildConfig, BuildEditorOptions, OPEN_SOURCE_LICENSE_KEY } from "./ckeditor/config.js"; import { buildConfig, BuildEditorOptions, OPEN_SOURCE_LICENSE_KEY } from "./ckeditor/config.js";
import type FNote from "../../entities/fnote.js"; 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";
export default class EditableTextTypeWidget extends AbstractTextTypeWidget { export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
@ -54,8 +53,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
return this.watchdog?.editor; return this.watchdog?.editor;
} }
}
async executeWithTextEditorEvent({ callback, resolve, ntxId }: EventData<"executeWithTextEditor">) { async executeWithTextEditorEvent({ callback, resolve, ntxId }: EventData<"executeWithTextEditor">) {
if (!this.isNoteContext(ntxId)) { if (!this.isNoteContext(ntxId)) {
return; return;
@ -130,14 +127,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
await this.reinitialize(); await this.reinitialize();
} }
async entitiesReloadedEvent(e: EventData<"entitiesReloaded">) {
await super.entitiesReloadedEvent(e);
if (updateTemplateCache(e.loadResults)) {
await this.reinitialize();
}
}
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) { buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
const { TouchBar, buildIcon } = data; const { TouchBar, buildIcon } = data;
const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar; const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar;