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