From b2f02962fc977f2316890ac54bd7e9fa8cfabb04 Mon Sep 17 00:00:00 2001 From: Zaki Ur Rehman Date: Thu, 16 Apr 2026 22:54:48 +0500 Subject: [PATCH] refactor: show only modified lines --- .../ribbon/components/AttributeEditor.tsx | 492 ++++++------------ 1 file changed, 171 insertions(+), 321 deletions(-) diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 83d27df1ee..dd5eb8895b 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -1,39 +1,19 @@ -import { - AttributeEditor as CKEditorAttributeEditor, - MentionFeed, - ModelElement, - ModelNode, - ModelPosition, -} from "@triliumnext/ckeditor5"; +import { AttributeEditor as CKEditorAttributeEditor, MentionFeed, ModelElement, ModelNode, ModelPosition } from "@triliumnext/ckeditor5"; import { AttributeType } from "@triliumnext/commons"; import { createPortal } from "preact/compat"; -import { - MutableRef, - useEffect, - useImperativeHandle, - useMemo, - useRef, - useState, -} from "preact/hooks"; +import { MutableRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "preact/hooks"; -import type { - CommandData, - FilteredCommandNames, -} from "../../../components/app_context"; +import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; import FAttribute from "../../../entities/fattribute"; import FNote from "../../../entities/fnote"; import contextMenu from "../../../menus/context_menu"; -import attribute_parser, { - Attribute, -} from "../../../services/attribute_parser"; +import attribute_parser, { Attribute } from "../../../services/attribute_parser"; import attribute_renderer from "../../../services/attribute_renderer"; import attributes from "../../../services/attributes"; import froca from "../../../services/froca"; import { t } from "../../../services/i18n"; import link from "../../../services/link"; -import note_autocomplete, { - Suggestion, -} from "../../../services/note_autocomplete"; +import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; import note_create from "../../../services/note_create"; import server from "../../../services/server"; import { isIMEComposing } from "../../../services/shortcuts"; @@ -41,13 +21,7 @@ import { escapeQuotes, getErrorMessage } from "../../../services/utils"; import AttributeDetailWidget from "../../attribute_widgets/attribute_detail"; import ActionButton from "../../react/ActionButton"; import CKEditor, { CKEditorApi } from "../../react/CKEditor"; -import { - useLegacyImperativeHandlers, - useLegacyWidget, - useTooltip, - useTriliumEvent, - useTriliumOption, -} from "../../react/hooks"; +import { useLegacyImperativeHandlers, useLegacyWidget, useTooltip, useTriliumEvent, useTriliumOption } from "../../react/hooks"; type AttributeCommandNames = FilteredCommandNames; @@ -61,8 +35,7 @@ const HELP_TEXT = ` const mentionSetup: MentionFeed[] = [ { marker: "@", - feed: (queryText) => - note_autocomplete.autocompleteSourceForCKEditor(queryText), + feed: (queryText) => note_autocomplete.autocompleteSourceForCKEditor(queryText), itemRenderer: (_item) => { const item = _item as Suggestion; const itemElement = document.createElement("button"); @@ -71,42 +44,39 @@ const mentionSetup: MentionFeed[] = [ return itemElement; }, - minimumCharacters: 0, + minimumCharacters: 0 }, { marker: "#", feed: async (queryText) => { - const names = await server.get( - `attribute-names/?type=label&query=${encodeURIComponent(queryText)}`, - ); + const names = await server.get(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`); return names.map((name) => { return { id: `#${name}`, - name, + name }; }); }, - minimumCharacters: 0, + minimumCharacters: 0 }, { marker: "~", feed: async (queryText) => { - const names = await server.get( - `attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`, - ); + const names = await server.get(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`); return names.map((name) => { return { id: `~${name}`, - name, + name }; }); }, - minimumCharacters: 0, - }, + minimumCharacters: 0 + } ]; + interface AttributeEditorProps { api: MutableRef; note: FNote; @@ -123,39 +93,29 @@ export interface AttributeEditorImperativeHandlers { renderOwnedAttributes(ownedAttributes: FAttribute[]): Promise; } -export default function AttributeEditor({ - api, - note, - componentId, - notePath, - ntxId, - hidden, -}: AttributeEditorProps) { - const [currentValue, setCurrentValue] = useState(""); - const [state, setState] = useState< - "normal" | "showHelpTooltip" | "showAttributeDetail" - >(); - const [error, setError] = useState(); - const [needsSaving, setNeedsSaving] = useState(false); +export default function AttributeEditor({ api, note, componentId, notePath, ntxId, hidden }: AttributeEditorProps) { + const [ currentValue, setCurrentValue ] = useState(""); + const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); + const [ error, setError ] = useState(); + const [ needsSaving, setNeedsSaving ] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); const suppressNextOnHide = useRef(false); + const lastSavedContent = useRef(); const currentValueRef = useRef(currentValue); const wrapperRef = useRef(null); const editorRef = useRef(); - const [locale] = useTriliumOption("locale"); + const [ locale ] = useTriliumOption("locale"); const { showTooltip, hideTooltip } = useTooltip(wrapperRef, { trigger: "focus", html: true, title: HELP_TEXT, placement: "bottom", - offset: "0,30", + offset: "0,30" }); - const [attributeDetailWidgetEl, attributeDetailWidget] = useLegacyWidget( - () => new AttributeDetailWidget(), - ); + const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget()); useEffect(() => { if (state === "showHelpTooltip") { @@ -163,16 +123,13 @@ export default function AttributeEditor({ } else { hideTooltip(); } - }, [state]); + }, [ state ]); - async function renderOwnedAttributes( - ownedAttributes: FAttribute[], - saved: boolean, - ) { + async function renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) { // attrs are not resorted if position changes after the initial load ownedAttributes.sort((a, b) => a.position - b.position); - let htmlAttrs = `

${(await attribute_renderer.renderAttributes(ownedAttributes, true)).html()}

`; + let htmlAttrs = (`

${(await attribute_renderer.renderAttributes(ownedAttributes, true)).html()}

`); if (saved) { lastSavedContent.current = htmlAttrs; @@ -189,9 +146,7 @@ export default function AttributeEditor({ function parseAttributes() { try { - return attribute_parser.lexAndParse( - getPreprocessedData(currentValueRef.current), - ); + return attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current)); } catch (e: unknown) { setError(e); } @@ -204,11 +159,7 @@ export default function AttributeEditor({ return; } - await server.put( - `notes/${note.noteId}/attributes`, - attributes, - componentId, - ); + await server.put(`notes/${note.noteId}/attributes`, attributes, componentId); setNeedsSaving(false); // blink the attribute text to give a visual hint that save has been executed @@ -222,9 +173,7 @@ export default function AttributeEditor({ } } - async function handleAddNewAttributeCommand( - command: AttributeCommandNames | undefined, - ) { + async function handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) { // TODO: Not sure what the relation between FAttribute[] and Attribute[] is. const attrs = parseAttributes() as FAttribute[]; @@ -261,7 +210,7 @@ export default function AttributeEditor({ type, name, value, - isInheritable: false, + isInheritable: false }); await renderOwnedAttributes(attrs, false); @@ -277,7 +226,7 @@ export default function AttributeEditor({ isOwned: true, x: rect ? (rect.left + rect.right) / 2 : 0, y: rect?.bottom ?? 0, - focus: "name", + focus: "name" }); }, 100); } @@ -287,51 +236,34 @@ export default function AttributeEditor({ renderOwnedAttributes(note.getOwnedAttributes(), true); } - useEffect(() => refresh(), [note]); + useEffect(() => refresh(), [ note ]); useTriliumEvent("entitiesReloaded", ({ loadResults }) => { - if ( - loadResults - .getAttributeRows(componentId) - .find((attr) => attributes.isAffecting(attr, note)) - ) { + if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) { refresh(); } }); // Interaction with CKEditor. - useLegacyImperativeHandlers( - useMemo( - () => ({ - loadReferenceLinkTitle: async ( - $el: JQuery, - href: string, - ) => { - const { noteId } = link.parseNavigationStateFromUrl(href); - const note = noteId - ? await froca.getNote(noteId, true) - : null; - const title = note ? note.title : "[missing]"; + useLegacyImperativeHandlers(useMemo(() => ({ + loadReferenceLinkTitle: async ($el: JQuery, href: string) => { + const { noteId } = link.parseNavigationStateFromUrl(href); + const note = noteId ? await froca.getNote(noteId, true) : null; + const title = note ? note.title : "[missing]"; - $el.text(title); - }, - createNoteForReferenceLink: async (title: string) => { - let result; - if (notePath) { - result = await note_create.createNoteWithTypePrompt( - notePath, - { - activate: false, - title, - }, - ); - } + $el.text(title); + }, + createNoteForReferenceLink: async (title: string) => { + let result; + if (notePath) { + result = await note_create.createNoteWithTypePrompt(notePath, { + activate: false, + title + }); + } - return result?.note?.getBestNotePathString(); - }, - }), - [notePath], - ), - ); + return result?.note?.getBestNotePathString(); + } + }), [ notePath ])); // Keyboard shortcuts useTriliumEvent("addNewLabel", ({ ntxId: eventNtxId }) => { @@ -344,217 +276,135 @@ export default function AttributeEditor({ }); // Imperative API - useImperativeHandle( - api, - () => ({ - save, - refresh, - renderOwnedAttributes: (attributes) => - renderOwnedAttributes(attributes as FAttribute[], false), - focus: () => editorRef.current?.focus(), - }), - [save, refresh, renderOwnedAttributes], - ); + useImperativeHandle(api, () => ({ + save, + refresh, + renderOwnedAttributes: (attributes) => renderOwnedAttributes(attributes as FAttribute[], false), + focus: () => editorRef.current?.focus() + }), [ save, refresh, renderOwnedAttributes ]); return ( <> - {!hidden && ( -
{ - // Skip processing during IME composition - if (isIMEComposing(e)) { - return; - } + {!hidden &&
{ + // Skip processing during IME composition + if (isIMEComposing(e)) { + return; + } - if (e.key === "Enter") { - // allow autocomplete to fill the result textarea - setTimeout(() => save(), 100); - } - }} - > - {" "} -
- { - currentValueRef.current = currentValue ?? ""; + if (e.key === "Enter") { + // allow autocomplete to fill the result textarea + setTimeout(() => save(), 100); + } + }} + >
+ { + currentValueRef.current = currentValue ?? ""; - const oldValue = getPreprocessedData( - lastSavedContent.current ?? "", - ).trimEnd(); - const newValue = getPreprocessedData( - currentValue ?? "", - ).trimEnd(); - setNeedsSaving(oldValue !== newValue); - setError(undefined); - }} - onClick={(e, pos) => { - if (pos && pos.textNode && pos.textNode.data) { - const clickIndex = getClickIndex(pos); + const oldValue = getPreprocessedData(lastSavedContent.current ?? "").trimEnd(); + const newValue = getPreprocessedData(currentValue ?? "").trimEnd(); + setNeedsSaving(oldValue !== newValue); + setError(undefined); + }} + // onClick={(e, pos) => { + // if (pos && pos.textNode && pos.textNode.data) { + // const clickIndex = getClickIndex(pos); - let parsedAttrs: Attribute[]; + // let parsedAttrs: Attribute[]; - try { - parsedAttrs = - attribute_parser.lexAndParse( - getPreprocessedData( - currentValueRef.current, - ), - true, - ); - } catch (e: unknown) { - // the input is incorrect because the user messed up with it and now needs to fix it manually - console.log(e); - return null; - } - - let matchedAttr: Attribute | null = null; - - for (const attr of parsedAttrs) { - if ( - attr.startIndex !== undefined && - clickIndex > attr.startIndex && - attr.endIndex !== undefined && - clickIndex <= attr.endIndex - ) { - matchedAttr = attr; - break; - } - } - - setTimeout(() => { - if (matchedAttr) { - attributeDetailWidget.showAttributeDetail( - { - allAttributes: parsedAttrs, - attribute: matchedAttr, - isOwned: true, - x: e.pageX, - y: e.pageY, - }, - ); - setState("showAttributeDetail"); - } else { - setState("showHelpTooltip"); - } - }, 100); - } else { - setState("showHelpTooltip"); - } - }} - onKeyDown={() => attributeDetailWidget.hide()} - onBlur={() => save()} - onInitialized={() => editorRef.current?.focus()} - disableNewlines - disableSpellcheck - /> - -
- {needsSaving && ( - - )} - - { - // Prevent automatic hiding of the context menu due to the button being clicked. - e.stopPropagation(); - if (isMenuOpen) { - // If we re-show the menu, ContextMenu.show() will call hide() - // and immediately trigger onHide. Suppress that transient hide. - suppressNextOnHide.current = true; - } - setIsMenuOpen(true); + // try { + // parsedAttrs = attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current), true); + // } catch (e: unknown) { + // // the input is incorrect because the user messed up with it and now needs to fix it manually + // console.log(e); + // return null; + // } + onClick={(e) => { + // Prevent automatic hiding of the context menu due to the button being clicked. + e.stopPropagation(); + if (isMenuOpen) { + // If we re-show the menu, ContextMenu.show() will call hide() + // and immediately trigger onHide. Suppress that transient hide. + suppressNextOnHide.current = true; + } + setIsMenuOpen(true); - contextMenu.show({ - x: e.pageX, - y: e.pageY, - orientation: "left", + contextMenu.show({ + x: e.pageX, + y: e.pageY, + orientation: "left", - items: [ - { - title: t( - "attribute_editor.add_new_label", - ), - command: "addNewLabel", - uiIcon: "bx bx-hash", - }, - { - title: t( - "attribute_editor.add_new_relation", - ), - command: "addNewRelation", - uiIcon: "bx bx-transfer", - }, - { kind: "separator" }, - { - title: t( - "attribute_editor.add_new_label_definition", - ), - command: + items: [ + { + title: t( + "attribute_editor.add_new_label", + ), + command: "addNewLabel", + uiIcon: "bx bx-hash", + }, + { + title: t( + "attribute_editor.add_new_relation", + ), + command: "addNewRelation", + uiIcon: "bx bx-transfer", + }, + { kind: "separator" }, + { + title: t( + "attribute_editor.add_new_label_definition", + ), + command: "addNewLabelDefinition", - uiIcon: "bx bx-empty", - }, - { - title: t( - "attribute_editor.add_new_relation_definition", - ), - command: + uiIcon: "bx bx-empty", + }, + { + title: t( + "attribute_editor.add_new_relation_definition", + ), + command: "addNewRelationDefinition", - uiIcon: "bx bx-empty", - }, - ], - selectMenuItemHandler: (item) => - handleAddNewAttributeCommand( - item.command, - ), - onHide: () => { - if (suppressNextOnHide.current) { - suppressNextOnHide.current = false; - return; - } - setIsMenuOpen(false); - }, - }); - }} - /> -
-
- {error && ( -
- {getErrorMessage(error)} -
- )} + uiIcon: "bx bx-empty", + }, + ], + selectMenuItemHandler: (item) => + handleAddNewAttributeCommand( + item.command, + ), + onHide: () => { + if (suppressNextOnHide.current) { + suppressNextOnHide.current = false; + return; + } + setIsMenuOpen(false); + }, + }); + }} + />
- )} + + { error && ( +
+ {getErrorMessage(error)} +
+ )} +
} {createPortal(attributeDetailWidgetEl, document.body)} @@ -585,4 +435,4 @@ function getClickIndex(pos: ModelPosition) { } return clickIndex; -} +} \ No newline at end of file