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 { 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 { useLegacyImperativeHandlers } from "../../react/hooks";
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). */
onEditorInitialized?: (editor: CKTextEditor) => void;
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 watchdogRef = useRef<EditorWatchdog>(null);
const [ editor, setEditor ] = useState<CKTextEditor>();
@ -130,7 +131,8 @@ export default function CKEditorWithWatchdog({ content, contentLanguage, classNa
const editor = await buildEditor(container, !!isClassicEditor, {
forceGplLicense: false,
isClassicEditor: !!isClassicEditor,
contentLanguage: contentLanguage ?? null
contentLanguage: contentLanguage ?? null,
templates
});
setEditor(editor);
@ -153,7 +155,7 @@ export default function CKEditorWithWatchdog({ content, contentLanguage, classNa
watchdog.create(container);
return () => watchdog.destroy();
}, [ contentLanguage ]);
}, [ contentLanguage, templates ]);
// React to content changes.
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 toast from "../../../services/toast";
import utils, { deferred, isMobile } from "../../../services/utils";
@ -6,10 +6,11 @@ import { useEditorSpacedUpdate, useKeyboardShortcuts, useLegacyImperativeHandler
import { TypeWidgetProps } from "../type_widget";
import CKEditorWithWatchdog, { CKEditorApi } from "./CKEditorWithWatchdog";
import "./EditableText.css";
import { CKTextEditor, ClassicEditor, EditorWatchdog } from "@triliumnext/ckeditor5";
import { CKTextEditor, ClassicEditor, EditorWatchdog, TemplateDefinition } from "@triliumnext/ckeditor5";
import Component from "../../../components/component";
import options from "../../../services/options";
import { loadIncludedNote, refreshIncludedNote } from "./utils";
import getTemplates, { updateTemplateCache } from "./snippets.js";
/**
* The editor can operate into two distinct modes:
@ -47,7 +48,8 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
onContentChange(newContent) {
setContent(newContent);
}
})
});
const templates = useTemplates();
useTriliumEvent("scrollToEnd", () => {
const editor = watchdogRef.current?.editor;
@ -127,7 +129,7 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
return (
<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"
tabIndex={300}
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.
saveInterval: 5000
}}
templates={templates}
onNotificationWarning={onNotificationWarning}
onWatchdogStateChange={onWatchdogStateChange}
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) {
const currentState = watchdog.state;
logInfo(`CKEditor state changed to ${currentState}`);

View File

@ -1,12 +1,11 @@
import { ALLOWED_PROTOCOLS } from "../../../services/link.js";
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 options from "../../../services/options.js";
import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
import emojiDefinitionsUrl from "@triliumnext/ckeditor5/src/emoji_definitions/en.json?url";
import { copyTextWithToast } from "../../../services/clipboard_ext.js";
import getTemplates from "./snippets.js";
import { t } from "../../../services/i18n.js";
import { getMermaidConfig } from "../../../services/mermaid.js";
import noteAutocompleteService, { type Suggestion } from "../../../services/note_autocomplete.js";
@ -20,6 +19,7 @@ export interface BuildEditorOptions {
forceGplLicense: boolean;
isClassicEditor: boolean;
contentLanguage: string | null;
templates: TemplateDefinition[];
}
export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfig> {
@ -157,7 +157,7 @@ export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfi
extraCommands: buildExtraCommands()
},
template: {
definitions: await getTemplates()
definitions: opts.templates
},
htmlSupport: {
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();
// React to creation or deletion of text snippets.
if (loadResults.getAttributeRows().find((attr) =>
attr.type === "label" &&
(attr.name === "textSnippet" || attr.name === "textSnippetDescription"))) {
handleFullReload();
if (loadResults.getAttributeRows().find((attr) => {
if (attr.type === "label") {
return (attr.name === "textSnippet" || attr.name === "textSnippetDescription");
} else if (attr.type === "relation") {
return (attr.value === "_template_text_snippet");
}
})) {
return await getTemplates();
} else if (affectedNoteIds.length > 0) {
// Update content and titles if one of the template notes were updated.
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 type FNote from "../../entities/fnote.js";
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 {
@ -54,8 +53,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
return this.watchdog?.editor;
}
}
async executeWithTextEditorEvent({ callback, resolve, ntxId }: EventData<"executeWithTextEditor">) {
if (!this.isNoteContext(ntxId)) {
return;
@ -130,14 +127,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
await this.reinitialize();
}
async entitiesReloadedEvent(e: EventData<"entitiesReloaded">) {
await super.entitiesReloadedEvent(e);
if (updateTemplateCache(e.loadResults)) {
await this.reinitialize();
}
}
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
const { TouchBar, buildIcon } = data;
const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar;