]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/nodes/custom-heading.ts
Lexical: Added custom alignment handling for blocks
[bookstack] / resources / js / wysiwyg / nodes / custom-heading.ts
1 import {
2     DOMConversionMap,
3     DOMConversionOutput,
4     LexicalNode,
5     Spread
6 } from "lexical";
7 import {EditorConfig} from "lexical/LexicalEditor";
8 import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text";
9 import {
10     CommonBlockAlignment, commonPropertiesDifferent,
11     SerializedCommonBlockNode,
12     setCommonBlockPropsFromElement,
13     updateElementWithCommonBlockProps
14 } from "./_common";
15
16
17 export type SerializedCustomHeadingNode = Spread<SerializedCommonBlockNode, SerializedHeadingNode>
18
19 export class CustomHeadingNode extends HeadingNode {
20     __id: string = '';
21     __alignment: CommonBlockAlignment = '';
22
23     static getType() {
24         return 'custom-heading';
25     }
26
27     setId(id: string) {
28         const self = this.getWritable();
29         self.__id = id;
30     }
31
32     getId(): string {
33         const self = this.getLatest();
34         return self.__id;
35     }
36
37     setAlignment(alignment: CommonBlockAlignment) {
38         const self = this.getWritable();
39         self.__alignment = alignment;
40     }
41
42     getAlignment(): CommonBlockAlignment {
43         const self = this.getLatest();
44         return self.__alignment;
45     }
46
47     static clone(node: CustomHeadingNode) {
48         const newNode = new CustomHeadingNode(node.__tag, node.__key);
49         newNode.__alignment = node.__alignment;
50         return newNode;
51     }
52
53     createDOM(config: EditorConfig): HTMLElement {
54         const dom = super.createDOM(config);
55         updateElementWithCommonBlockProps(dom, this);
56         return dom;
57     }
58
59     updateDOM(prevNode: CustomHeadingNode, dom: HTMLElement): boolean {
60         return super.updateDOM(prevNode, dom)
61             || commonPropertiesDifferent(prevNode, this);
62     }
63
64     exportJSON(): SerializedCustomHeadingNode {
65         return {
66             ...super.exportJSON(),
67             type: 'custom-heading',
68             version: 1,
69             id: this.__id,
70             alignment: this.__alignment,
71         };
72     }
73
74     static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode {
75         const node = $createCustomHeadingNode(serializedNode.tag);
76         node.setId(serializedNode.id);
77         node.setAlignment(serializedNode.alignment);
78         return node;
79     }
80
81     static importDOM(): DOMConversionMap | null {
82         return {
83             h1: (node: Node) => ({
84                 conversion: $convertHeadingElement,
85                 priority: 0,
86             }),
87             h2: (node: Node) => ({
88                 conversion: $convertHeadingElement,
89                 priority: 0,
90             }),
91             h3: (node: Node) => ({
92                 conversion: $convertHeadingElement,
93                 priority: 0,
94             }),
95             h4: (node: Node) => ({
96                 conversion: $convertHeadingElement,
97                 priority: 0,
98             }),
99             h5: (node: Node) => ({
100                 conversion: $convertHeadingElement,
101                 priority: 0,
102             }),
103             h6: (node: Node) => ({
104                 conversion: $convertHeadingElement,
105                 priority: 0,
106             }),
107         };
108     }
109 }
110
111 function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
112     const nodeName = element.nodeName.toLowerCase();
113     let node = null;
114     if (
115         nodeName === 'h1' ||
116         nodeName === 'h2' ||
117         nodeName === 'h3' ||
118         nodeName === 'h4' ||
119         nodeName === 'h5' ||
120         nodeName === 'h6'
121     ) {
122         node = $createCustomHeadingNode(nodeName);
123         setCommonBlockPropsFromElement(element, node);
124     }
125     return {node};
126 }
127
128 export function $createCustomHeadingNode(tag: HeadingTagType) {
129     return new CustomHeadingNode(tag);
130 }
131
132 export function $isCustomHeadingNode(node: LexicalNode | null | undefined): node is CustomHeadingNode {
133     return node instanceof CustomHeadingNode;
134 }