]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/nodes/custom-heading.ts
f069ff16048bd84ff08cf4e0ca74b57593848aba
[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         return new CustomHeadingNode(node.__tag, node.__key);
34     }
35
36     createDOM(config: EditorConfig): HTMLElement {
37         const dom = super.createDOM(config);
38         if (this.__id) {
39             dom.setAttribute('id', this.__id);
40         }
41
42         return dom;
43     }
44
45     exportJSON(): SerializedCustomHeadingNode {
46         return {
47             ...super.exportJSON(),
48             type: 'custom-heading',
49             version: 1,
50             id: this.__id,
51         };
52     }
53
54     static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode {
55         const node = $createCustomHeadingNode(serializedNode.tag);
56         node.setId(serializedNode.id);
57         return node;
58     }
59
60     static importDOM(): DOMConversionMap | null {
61         return {
62             h1: (node: Node) => ({
63                 conversion: $convertHeadingElement,
64                 priority: 0,
65             }),
66             h2: (node: Node) => ({
67                 conversion: $convertHeadingElement,
68                 priority: 0,
69             }),
70             h3: (node: Node) => ({
71                 conversion: $convertHeadingElement,
72                 priority: 0,
73             }),
74             h4: (node: Node) => ({
75                 conversion: $convertHeadingElement,
76                 priority: 0,
77             }),
78             h5: (node: Node) => ({
79                 conversion: $convertHeadingElement,
80                 priority: 0,
81             }),
82             h6: (node: Node) => ({
83                 conversion: $convertHeadingElement,
84                 priority: 0,
85             }),
86         };
87     }
88 }
89
90 function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
91     const nodeName = element.nodeName.toLowerCase();
92     let node = null;
93     if (
94         nodeName === 'h1' ||
95         nodeName === 'h2' ||
96         nodeName === 'h3' ||
97         nodeName === 'h4' ||
98         nodeName === 'h5' ||
99         nodeName === 'h6'
100     ) {
101         node = $createCustomHeadingNode(nodeName);
102         if (element.style !== null) {
103             node.setFormat(element.style.textAlign as ElementFormatType);
104         }
105         if (element.id) {
106             node.setId(element.id);
107         }
108     }
109     return {node};
110 }
111
112 export function $createCustomHeadingNode(tag: HeadingTagType) {
113     return new CustomHeadingNode(tag);
114 }
115
116 export function $isCustomHeadingNode(node: LexicalNode | null | undefined): node is CustomHeadingNode {
117     return node instanceof CustomHeadingNode;
118 }