]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Added custom id-supporting paragraph blocks
authorDan Brown <redacted>
Tue, 28 May 2024 14:09:50 +0000 (15:09 +0100)
committerDan Brown <redacted>
Tue, 28 May 2024 14:09:50 +0000 (15:09 +0100)
package.json
resources/js/wysiwyg/index.ts
resources/js/wysiwyg/nodes/callout.ts
resources/js/wysiwyg/nodes/custom-paragraph.ts [new file with mode: 0644]
resources/js/wysiwyg/nodes/index.ts
resources/views/pages/parts/wysiwyg-editor.blade.php

index 97a79612623862f802a5bc9a3d0fe817cef41e29..ca0f01f17a709cee9d37eac58eef34537ccdf713 100644 (file)
@@ -5,7 +5,7 @@
     "build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources",
     "build:css:production": "sass ./resources/sass:./public/dist -s compressed",
     "build:js:dev": "node dev/build/esbuild.js",
-    "build:js:watch": "chokidar --initial \"./resources/**/*.js\" \"./resources/**/*.mjs\" -c \"npm run build:js:dev\"",
+    "build:js:watch": "chokidar --initial \"./resources/**/*.js\" \"./resources/**/*.mjs\" \"./resources/**/*.ts\" -c \"npm run build:js:dev\"",
     "build:js:production": "node dev/build/esbuild.js production",
     "build": "npm-run-all --parallel build:*:dev",
     "production": "npm-run-all --parallel build:*:production",
index 266866c62519c62cbd27d6d33abca490e9141c1c..9553fd4dd3df7ce580f5123b3a28f83f44ac9460 100644 (file)
@@ -45,10 +45,6 @@ export function createPageEditorInstance(editArea: HTMLElement) {
         debugView.textContent = JSON.stringify(editorState.toJSON(), null, 2);
     });
 
-    // Todo - How can we store things like IDs and alignment?
-    //   Node overrides?
-    //   https://lexical.dev/docs/concepts/node-replacement
-
     // Example of creating, registering and using a custom command
 
     const SET_BLOCK_CALLOUT_COMMAND = createCommand();
index 4fba5ee5b639d9958d2caa2a2527a727533b01b0..89b9b162ef04ab75c6f7012bf36bf75c74f10793 100644 (file)
@@ -16,7 +16,7 @@ export type SerializedCalloutNode = Spread<{
     category: CalloutCategory;
 }, SerializedElementNode>
 
-export class Callout extends ElementNode {
+export class CalloutNode extends ElementNode {
 
     __category: CalloutCategory = 'info';
 
@@ -24,8 +24,8 @@ export class Callout extends ElementNode {
         return 'callout';
     }
 
-    static clone(node: Callout) {
-        return new Callout(node.__category, node.__key);
+    static clone(node: CalloutNode) {
+        return new CalloutNode(node.__category, node.__key);
     }
 
     constructor(category: CalloutCategory, key?: string) {
@@ -45,7 +45,7 @@ export class Callout extends ElementNode {
         return false;
     }
 
-    insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): Callout|ParagraphNode {
+    insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode {
         const anchorOffset = selection ? selection.anchor.offset : 0;
         const newElement = anchorOffset === this.getTextContentSize() || !selection
             ? $createParagraphNode() : $createCalloutNode(this.__category);
@@ -79,7 +79,7 @@ export class Callout extends ElementNode {
                             }
 
                             return {
-                                node: new Callout(category),
+                                node: new CalloutNode(category),
                             };
                         },
                         priority: 3,
@@ -99,16 +99,16 @@ export class Callout extends ElementNode {
         };
     }
 
-    static importJSON(serializedNode: SerializedCalloutNode): Callout {
+    static importJSON(serializedNode: SerializedCalloutNode): CalloutNode {
         return $createCalloutNode(serializedNode.category);
     }
 
 }
 
 export function $createCalloutNode(category: CalloutCategory = 'info') {
-    return new Callout(category);
+    return new CalloutNode(category);
 }
 
 export function $isCalloutNode(node: LexicalNode | null | undefined) {
-    return node instanceof Callout;
+    return node instanceof CalloutNode;
 }
diff --git a/resources/js/wysiwyg/nodes/custom-paragraph.ts b/resources/js/wysiwyg/nodes/custom-paragraph.ts
new file mode 100644 (file)
index 0000000..f13cef5
--- /dev/null
@@ -0,0 +1,98 @@
+import {
+    DOMConversion,
+    DOMConversionMap,
+    DOMConversionOutput, ElementFormatType,
+    LexicalNode,
+    ParagraphNode,
+    SerializedParagraphNode,
+    Spread
+} from "lexical";
+import {EditorConfig} from "lexical/LexicalEditor";
+
+
+export type SerializedCustomParagraphNode = Spread<{
+    id: string;
+}, SerializedParagraphNode>
+
+export class CustomParagraphNode extends ParagraphNode {
+    __id: string = '';
+
+    static getType() {
+        return 'custom-paragraph';
+    }
+
+    setId(id: string) {
+        const self = this.getWritable();
+        self.__id = id;
+    }
+
+    getId(): string {
+        const self = this.getLatest();
+        return self.__id;
+    }
+
+    static clone(node: CustomParagraphNode) {
+        const newNode = new CustomParagraphNode(node.__key);
+        newNode.__id = node.__id;
+        return newNode;
+    }
+
+    createDOM(config: EditorConfig): HTMLElement {
+        const dom = super.createDOM(config);
+        const id = this.getId();
+        if (id) {
+            dom.setAttribute('id', id);
+        }
+
+        return dom;
+    }
+
+    exportJSON(): SerializedCustomParagraphNode {
+        return {
+            ...super.exportJSON(),
+            type: 'custom-paragraph',
+            version: 1,
+            id: this.__id,
+        };
+    }
+
+    static importJSON(serializedNode: SerializedCustomParagraphNode): CustomParagraphNode {
+        const node = $createCustomParagraphNode();
+        node.setId(serializedNode.id);
+        return node;
+    }
+
+    static importDOM(): DOMConversionMap|null {
+        return {
+            p(node: HTMLElement): DOMConversion|null {
+                return {
+                    conversion: (element: HTMLElement): DOMConversionOutput|null => {
+                        const node = $createCustomParagraphNode();
+                        if (element.style) {
+                            node.setFormat(element.style.textAlign as ElementFormatType);
+                            const indent = parseInt(element.style.textIndent, 10) / 20;
+                            if (indent > 0) {
+                                node.setIndent(indent);
+                            }
+                        }
+
+                        if (element.id) {
+                            node.setId(element.id);
+                        }
+
+                        return {node};
+                    },
+                    priority: 1,
+                };
+            },
+        };
+    }
+}
+
+export function $createCustomParagraphNode() {
+    return new CustomParagraphNode();
+}
+
+export function $isCustomParagraphNode(node: LexicalNode | null | undefined) {
+    return node instanceof CustomParagraphNode;
+}
\ No newline at end of file
index 77f582877f4c63cf67a52dda3d88b505b0d3da76..7dda306479b175fc6d7d9e38bdd664cfc522d8c3 100644 (file)
@@ -1,14 +1,22 @@
 import {HeadingNode, QuoteNode} from '@lexical/rich-text';
-import {Callout} from './callout';
-import {KlassConstructor, LexicalNode} from "lexical";
+import {CalloutNode} from './callout';
+import {KlassConstructor, LexicalNode, LexicalNodeReplacement, ParagraphNode} from "lexical";
+import {CustomParagraphNode} from "./custom-paragraph";
 
 /**
  * Load the nodes for lexical.
  */
-export function getNodesForPageEditor(): KlassConstructor<typeof LexicalNode>[] {
+export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> | LexicalNodeReplacement)[] {
     return [
-        Callout,
-        HeadingNode,
-        QuoteNode,
+        CalloutNode, // Todo - Create custom
+        HeadingNode, // Todo - Create custom
+        QuoteNode, // Todo - Create custom
+        CustomParagraphNode,
+        {
+            replace: ParagraphNode,
+            with: (node: ParagraphNode) => {
+                return new CustomParagraphNode();
+            }
+        }
     ];
 }
index 30be1a21422ed0d7d38fac4ef4d9496ffba905dc..bbe76090c40616f6781f9e90b661b7099ede171a 100644 (file)
@@ -11,7 +11,7 @@
     </div>
 
     <div refs="wysiwyg-editor@edit-area" contenteditable="true">
-        <p>Some content here</p>
+        <p id="Content!">Some <strong>content</strong> here</p>
         <h2>List below this h2 header</h2>
         <ul>
             <li>Hello</li>