]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/nodes/custom-table.ts
Lexical: Merged list nodes
[bookstack] / resources / js / wysiwyg / nodes / custom-table.ts
1 import {SerializedTableNode, TableNode} from "@lexical/table";
2 import {DOMConversion, DOMConversionMap, DOMConversionOutput, LexicalNode, Spread} from "lexical";
3 import {EditorConfig} from "lexical/LexicalEditor";
4
5 import {el, extractStyleMapFromElement, StyleMap} from "../utils/dom";
6 import {getTableColumnWidths} from "../utils/tables";
7 import {
8     CommonBlockAlignment, deserializeCommonBlockNode,
9     SerializedCommonBlockNode,
10     setCommonBlockPropsFromElement,
11     updateElementWithCommonBlockProps
12 } from "./_common";
13
14 export type SerializedCustomTableNode = Spread<Spread<{
15     colWidths: string[];
16     styles: Record<string, string>,
17 }, SerializedTableNode>, SerializedCommonBlockNode>
18
19 export class CustomTableNode extends TableNode {
20     __id: string = '';
21     __colWidths: string[] = [];
22     __styles: StyleMap = new Map;
23     __alignment: CommonBlockAlignment = '';
24     __inset: number = 0;
25
26     static getType() {
27         return 'custom-table';
28     }
29
30     setId(id: string) {
31         const self = this.getWritable();
32         self.__id = id;
33     }
34
35     getId(): string {
36         const self = this.getLatest();
37         return self.__id;
38     }
39
40     setAlignment(alignment: CommonBlockAlignment) {
41         const self = this.getWritable();
42         self.__alignment = alignment;
43     }
44
45     getAlignment(): CommonBlockAlignment {
46         const self = this.getLatest();
47         return self.__alignment;
48     }
49
50     setInset(size: number) {
51         const self = this.getWritable();
52         self.__inset = size;
53     }
54
55     getInset(): number {
56         const self = this.getLatest();
57         return self.__inset;
58     }
59
60     setColWidths(widths: string[]) {
61         const self = this.getWritable();
62         self.__colWidths = widths;
63     }
64
65     getColWidths(): string[] {
66         const self = this.getLatest();
67         return self.__colWidths;
68     }
69
70     getStyles(): StyleMap {
71         const self = this.getLatest();
72         return new Map(self.__styles);
73     }
74
75     setStyles(styles: StyleMap): void {
76         const self = this.getWritable();
77         self.__styles = new Map(styles);
78     }
79
80     static clone(node: CustomTableNode) {
81         const newNode = new CustomTableNode(node.__key);
82         newNode.__id = node.__id;
83         newNode.__colWidths = node.__colWidths;
84         newNode.__styles = new Map(node.__styles);
85         newNode.__alignment = node.__alignment;
86         newNode.__inset = node.__inset;
87         return newNode;
88     }
89
90     createDOM(config: EditorConfig): HTMLElement {
91         const dom = super.createDOM(config);
92         updateElementWithCommonBlockProps(dom, this);
93
94         const colWidths = this.getColWidths();
95         if (colWidths.length > 0) {
96             const colgroup = el('colgroup');
97             for (const width of colWidths) {
98                 const col = el('col');
99                 if (width) {
100                     col.style.width = width;
101                 }
102                 colgroup.append(col);
103             }
104             dom.append(colgroup);
105         }
106
107         for (const [name, value] of this.__styles.entries()) {
108             dom.style.setProperty(name, value);
109         }
110
111         return dom;
112     }
113
114     updateDOM(): boolean {
115         return true;
116     }
117
118     exportJSON(): SerializedCustomTableNode {
119         return {
120             ...super.exportJSON(),
121             type: 'custom-table',
122             version: 1,
123             id: this.__id,
124             colWidths: this.__colWidths,
125             styles: Object.fromEntries(this.__styles),
126             alignment: this.__alignment,
127             inset: this.__inset,
128         };
129     }
130
131     static importJSON(serializedNode: SerializedCustomTableNode): CustomTableNode {
132         const node = $createCustomTableNode();
133         deserializeCommonBlockNode(serializedNode, node);
134         node.setColWidths(serializedNode.colWidths);
135         node.setStyles(new Map(Object.entries(serializedNode.styles)));
136         return node;
137     }
138
139     static importDOM(): DOMConversionMap|null {
140         return {
141             table(node: HTMLElement): DOMConversion|null {
142                 return {
143                     conversion: (element: HTMLElement): DOMConversionOutput|null => {
144                         const node = $createCustomTableNode();
145                         setCommonBlockPropsFromElement(element, node);
146
147                         const colWidths = getTableColumnWidths(element as HTMLTableElement);
148                         node.setColWidths(colWidths);
149                         node.setStyles(extractStyleMapFromElement(element));
150
151                         return {node};
152                     },
153                     priority: 1,
154                 };
155             },
156         };
157     }
158 }
159
160 export function $createCustomTableNode(): CustomTableNode {
161     return new CustomTableNode();
162 }
163
164 export function $isCustomTableNode(node: LexicalNode | null | undefined): node is CustomTableNode {
165     return node instanceof CustomTableNode;
166 }