From 6d37e19b40ac2db8d3acf49c0e89bf0d48e2804f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 11:44:51 +0300 Subject: [PATCH] chore(react/ribbon): start implementing attribute editor --- .../attribute_widgets/attribute_editor.ts | 108 ------------------ .../src/widgets/ribbon/OwnedAttributesTab.tsx | 10 ++ apps/client/src/widgets/ribbon/Ribbon.tsx | 7 +- .../ribbon/components/AttributeEditor.tsx | 78 +++++++++++++ apps/client/src/widgets/ribbon/style.css | 64 +++++++++++ .../ribbon_widgets/owned_attribute_list.ts | 45 -------- 6 files changed, 157 insertions(+), 155 deletions(-) create mode 100644 apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx create mode 100644 apps/client/src/widgets/ribbon/components/AttributeEditor.tsx diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 3e97723a5..24a4e09d2 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -24,58 +24,6 @@ const HELP_TEXT = `

${t("attribute_editor.help_text_body3")}

`; const TPL = /*html*/` -
- - -
@@ -84,61 +32,6 @@ const TPL = /*html*/`
`; -const mentionSetup: MentionFeed[] = [ - { - marker: "@", - feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText), - itemRenderer: (_item) => { - const item = _item as Suggestion; - const itemElement = document.createElement("button"); - - itemElement.innerHTML = `${item.highlightedNotePathTitle} `; - - return itemElement; - }, - minimumCharacters: 0 - }, - { - marker: "#", - feed: async (queryText) => { - const names = await server.get(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`); - - return names.map((name) => { - return { - id: `#${name}`, - name: name - }; - }); - }, - minimumCharacters: 0 - }, - { - marker: "~", - feed: async (queryText) => { - const names = await server.get(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`); - - return names.map((name) => { - return { - id: `~${name}`, - name: name - }; - }); - }, - minimumCharacters: 0 - } -]; - -const editorConfig: EditorConfig = { - toolbar: { - items: [] - }, - placeholder: t("attribute_editor.placeholder"), - mention: { - feeds: mentionSetup - }, - licenseKey: "GPL" -}; - type AttributeCommandNames = FilteredCommandNames; export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> { @@ -324,7 +217,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.$editor.on("click", (e) => this.handleEditorClick(e)); - this.textEditor = await AttributeEditor.create(this.$editor[0], editorConfig); this.textEditor.model.document.on("change:data", () => this.dataChanged()); this.textEditor.editing.view.document.on( "enter", diff --git a/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx new file mode 100644 index 000000000..33554989c --- /dev/null +++ b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx @@ -0,0 +1,10 @@ +import AttributeEditor from "./components/AttributeEditor"; +import { TabContext } from "./ribbon-interface"; + +export default function OwnedAttributesTab({ note }: TabContext) { + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index 4702dcf07..032a905a6 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -19,6 +19,7 @@ import FilePropertiesTab from "./FilePropertiesTab"; import ImagePropertiesTab from "./ImagePropertiesTab"; import NotePathsTab from "./NotePathsTab"; import NoteMapTab from "./NoteMapTab"; +import OwnedAttributesTab from "./OwnedAttributesTab"; interface TitleContext { note: FNote | null | undefined; @@ -105,9 +106,11 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ toggleCommand: "toggleRibbonTabBasicProperties" }, { - // OwnedAttributeListWidget title: t("owned_attribute_list.owned_attributes"), - icon: "bx bx-list-check" + icon: "bx bx-list-check", + content: OwnedAttributesTab, + show: ({note}) => !note?.isLaunchBarConfig(), + toggleCommand: "toggleRibbonTabOwnedAttributes" }, { // InheritedAttributesWidget diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx new file mode 100644 index 000000000..88bc85139 --- /dev/null +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -0,0 +1,78 @@ +import { useEffect, useRef } from "preact/hooks" +import { AttributeEditor as CKEditor, EditorConfig, MentionFeed } from "@triliumnext/ckeditor5"; +import { t } from "../../../services/i18n"; +import server from "../../../services/server"; +import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; + +const mentionSetup: MentionFeed[] = [ + { + marker: "@", + feed: (queryText) => note_autocomplete.autocompleteSourceForCKEditor(queryText), + itemRenderer: (_item) => { + const item = _item as Suggestion; + const itemElement = document.createElement("button"); + + itemElement.innerHTML = `${item.highlightedNotePathTitle} `; + + return itemElement; + }, + minimumCharacters: 0 + }, + { + marker: "#", + feed: async (queryText) => { + const names = await server.get(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`); + + return names.map((name) => { + return { + id: `#${name}`, + name: name + }; + }); + }, + minimumCharacters: 0 + }, + { + marker: "~", + feed: async (queryText) => { + const names = await server.get(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`); + + return names.map((name) => { + return { + id: `~${name}`, + name: name + }; + }); + }, + minimumCharacters: 0 + } +]; + +const editorConfig: EditorConfig = { + toolbar: { + items: [] + }, + placeholder: t("attribute_editor.placeholder"), + mention: { + feeds: mentionSetup + }, + licenseKey: "GPL" +}; + +export default function AttributeEditor() { + const editorContainerRef = useRef(null); + + useEffect(() => { + if (!editorContainerRef.current) return; + + CKEditor.create(editorContainerRef.current, editorConfig).then((textEditor) => { + + }); + }, []); + + return ( +
+
+
+ ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index c09fa989f..22c9d7b43 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -267,4 +267,68 @@ color: var(--muted-text-color); display: none; } +/* #endregion */ + +/* #region Attribute editor */ +.attribute-list-editor { + border: 0 !important; + outline: 0 !important; + box-shadow: none !important; + padding: 0 0 0 5px !important; + margin: 0 !important; + max-height: 100px; + overflow: auto; + transition: opacity .1s linear; +} + +.attribute-list-editor.ck-content .mention { + color: var(--muted-text-color) !important; + background: transparent !important; +} + +.save-attributes-button { + color: var(--muted-text-color); + position: absolute; + bottom: 14px; + right: 25px; + cursor: pointer; + border: 1px solid transparent; + font-size: 130%; +} + +.add-new-attribute-button { + color: var(--muted-text-color); + position: absolute; + bottom: 13px; + right: 0; + cursor: pointer; + border: 1px solid transparent; + font-size: 130%; +} + +.add-new-attribute-button:hover, .save-attributes-button:hover { + border: 1px solid var(--button-border-color); + border-radius: var(--button-border-radius); + background: var(--button-background-color); + color: var(--button-text-color); +} + +.attribute-errors { + color: red; + padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */ +} +/* #endregion */ + +/* #region Owned Attributes */ +.attribute-list { + margin-left: 7px; + margin-right: 7px; + margin-top: 5px; + margin-bottom: 2px; + position: relative; +} + +.attribute-list-editor p { + margin: 0 !important; +} /* #endregion */ \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts b/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts index 113e03e0d..6b036bd0e 100644 --- a/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts +++ b/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts @@ -5,40 +5,12 @@ import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js"; import type { CommandListenerData } from "../../components/app_context.js"; import type FAttribute from "../../entities/fattribute.js"; -const TPL = /*html*/` -
- - -
-
-`; - export default class OwnedAttributeListWidget extends NoteContextAwareWidget { private attributeDetailWidget: AttributeDetailWidget; private attributeEditorWidget: AttributeEditorWidget; private $title!: JQuery; - get name() { - return "ownedAttributes"; - } - - get toggleCommand() { - return "toggleRibbonTabOwnedAttributes"; - } - constructor() { super(); @@ -49,23 +21,6 @@ export default class OwnedAttributeListWidget extends NoteContextAwareWidget { this.child(this.attributeEditorWidget, this.attributeDetailWidget); } - getTitle() { - return { - show: !this.note?.isLaunchBarConfig(), - - }; - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - - this.$widget.find(".attr-editor-placeholder").replaceWith(this.attributeEditorWidget.render()); - this.$widget.append(this.attributeDetailWidget.render()); - - this.$title = $("
"); - } - async saveAttributesCommand() { await this.attributeEditorWidget.save(); }