"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",
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();
category: CalloutCategory;
}, SerializedElementNode>
-export class Callout extends ElementNode {
+export class CalloutNode extends ElementNode {
__category: CalloutCategory = 'info';
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) {
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);
}
return {
- node: new Callout(category),
+ node: new CalloutNode(category),
};
},
priority: 3,
};
}
- 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;
}
--- /dev/null
+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
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();
+ }
+ }
];
}
</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>