diff --git a/package.json b/package.json index 97a796126..ca0f01f17 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index 266866c62..9553fd4dd 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -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(); diff --git a/resources/js/wysiwyg/nodes/callout.ts b/resources/js/wysiwyg/nodes/callout.ts index 4fba5ee5b..89b9b162e 100644 --- a/resources/js/wysiwyg/nodes/callout.ts +++ b/resources/js/wysiwyg/nodes/callout.ts @@ -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 index 000000000..f13cef56f --- /dev/null +++ b/resources/js/wysiwyg/nodes/custom-paragraph.ts @@ -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 diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index 77f582877..7dda30647 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -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(); + } + } ]; } diff --git a/resources/views/pages/parts/wysiwyg-editor.blade.php b/resources/views/pages/parts/wysiwyg-editor.blade.php index 30be1a214..bbe76090c 100644 --- a/resources/views/pages/parts/wysiwyg-editor.blade.php +++ b/resources/views/pages/parts/wysiwyg-editor.blade.php @@ -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>