From db687197de06e0efd982b621130aa081e815435b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 20:31:00 +0300 Subject: [PATCH] chore(react/ribbon): add focus to attribute editor --- .../attribute_widgets/attribute_editor.ts | 22 ---------------- apps/client/src/widgets/react/CKEditor.tsx | 23 +++++++++++++++-- apps/client/src/widgets/ribbon/Ribbon.tsx | 5 ++-- .../ribbon/components/AttributeEditor.tsx | 25 ++++++++++++++++--- .../src/widgets/ribbon/ribbon-interface.ts | 1 + 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 719c77b85..6c732b779 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -18,14 +18,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem } } - async save() { - if (this.lastUpdatedNoteId !== this.noteId) { - // https://github.com/zadam/trilium/issues/3090 - console.warn("Ignoring blur event because a different note is loaded."); - return; - } - } - dataChanged() { this.lastUpdatedNoteId = this.noteId; } @@ -49,20 +41,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem focus() { this.$editor.trigger("focus"); - this.textEditor.model.change((writer) => { - const documentRoot = this.textEditor.editing.model.document.getRoot(); - if (!documentRoot) { - return; - } - const positionAt = writer.createPositionAt(documentRoot, "end"); - writer.setSelection(positionAt); - }); - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.getAttributeRows(this.componentId).find((attr) => attributeService.isAffecting(attr, this.note))) { - this.refresh(); - } } } diff --git a/apps/client/src/widgets/react/CKEditor.tsx b/apps/client/src/widgets/react/CKEditor.tsx index fcd61179b..a9f49d887 100644 --- a/apps/client/src/widgets/react/CKEditor.tsx +++ b/apps/client/src/widgets/react/CKEditor.tsx @@ -1,7 +1,13 @@ import { CKTextEditor, type AttributeEditor, type EditorConfig, type ModelPosition } from "@triliumnext/ckeditor5"; -import { useEffect, useRef } from "preact/compat"; +import { useEffect, useImperativeHandle, useRef } from "preact/compat"; +import { MutableRef } from "preact/hooks"; + +export interface CKEditorApi { + focus: () => void; +} interface CKEditorOpts { + apiRef: MutableRef; currentValue?: string; className: string; tabIndex?: number; @@ -15,9 +21,22 @@ interface CKEditorOpts { onBlur?: () => void; } -export default function CKEditor({ currentValue, editor, config, disableNewlines, disableSpellcheck, onChange, onClick, ...restProps }: CKEditorOpts) { +export default function CKEditor({ apiRef, currentValue, editor, config, disableNewlines, disableSpellcheck, onChange, onClick, ...restProps }: CKEditorOpts) { const editorContainerRef = useRef(null); const textEditorRef = useRef(null); + useImperativeHandle(apiRef, () => { + return { + focus() { + editorContainerRef.current?.focus(); + textEditorRef.current?.model.change((writer) => { + const documentRoot = textEditorRef.current?.editing.model.document.getRoot(); + if (documentRoot) { + writer.setSelection(writer.createPositionAt(documentRoot, "end")); + } + }); + } + }; + }, [ editorContainerRef ]); useEffect(() => { if (!editorContainerRef.current) return; diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index 032a905a6..74e1d19b4 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -148,7 +148,7 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ ]); export default function Ribbon() { - const { note, ntxId, hoistedNoteId, notePath, noteContext } = useNoteContext(); + const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext(); const titleContext: TitleContext = { note }; const [ activeTabIndex, setActiveTabIndex ] = useState(); const filteredTabs = useMemo(() => TAB_CONFIGURATION.filter(tab => typeof tab.show === "boolean" ? tab.show : tab.show?.(titleContext)), [ titleContext, note ]); @@ -190,7 +190,8 @@ export default function Ribbon() { ntxId, hoistedNoteId, notePath, - noteContext + noteContext, + componentId }); })} diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 0e60d847e..7a82962cc 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -3,8 +3,8 @@ import { AttributeEditor as CKEditorAttributeEditor, MentionFeed, ModelElement, import { t } from "../../../services/i18n"; import server from "../../../services/server"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; -import CKEditor from "../../react/CKEditor"; -import { useLegacyWidget, useTooltip } from "../../react/hooks"; +import CKEditor, { CKEditorApi } from "../../react/CKEditor"; +import { useLegacyWidget, useTooltip, useTriliumEventBeta } from "../../react/hooks"; import FAttribute from "../../../entities/fattribute"; import attribute_renderer from "../../../services/attribute_renderer"; import FNote from "../../../entities/fnote"; @@ -19,6 +19,7 @@ import froca from "../../../services/froca"; import contextMenu from "../../../menus/context_menu"; import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; import { AttributeType } from "@triliumnext/commons"; +import attributes from "../../../services/attributes"; type AttributeCommandNames = FilteredCommandNames; @@ -86,6 +87,7 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co const lastSavedContent = useRef(); const currentValueRef = useRef(initialValue); const wrapperRef = useRef(null); + const editorRef = useRef(); const { showTooltip, hideTooltip } = useTooltip(wrapperRef, { trigger: "focus", @@ -207,9 +209,23 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co }, 100); } - useEffect(() => { + // Refresh with note + function refresh() { renderOwnedAttributes(note.getOwnedAttributes(), true); - }, [ note ]); + } + + useEffect(() => refresh(), [ note ]); + useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) { + console.log("Trigger due to entities reloaded"); + refresh(); + } + }); + + // Focus on show. + useEffect(() => { + setTimeout(() => editorRef.current?.focus(), 0); + }, []); return ( <> @@ -224,6 +240,7 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co }} >