]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/index.mjs
Editors: Added lexical editor for testing
[bookstack] / resources / js / wysiwyg / index.mjs
1 import {$getRoot, createEditor, ElementNode} from 'lexical';
2 import {createEmptyHistoryState, registerHistory} from '@lexical/history';
3 import {HeadingNode, QuoteNode, registerRichText} from '@lexical/rich-text';
4 import {mergeRegister} from '@lexical/utils';
5 import {$generateNodesFromDOM} from '@lexical/html';
6
7 class CalloutParagraph extends ElementNode {
8     __category = 'info';
9
10     static getType() {
11         return 'callout';
12     }
13
14     static clone(node) {
15         return new CalloutParagraph(node.__category, node.__key);
16     }
17
18     constructor(category, key) {
19         super(key);
20         this.__category = category;
21     }
22
23     createDOM(_config, _editor) {
24         const dom = document.createElement('p');
25         dom.classList.add('callout', this.__category || '');
26         return dom;
27     }
28
29     updateDOM(prevNode, dom) {
30         // Returning false tells Lexical that this node does not need its
31         // DOM element replacing with a new copy from createDOM.
32         return false;
33     }
34
35     static importDOM() {
36         return {
37             p: node => {
38                 if (node.classList.contains('callout')) {
39                     return {
40                         conversion: element => {
41                             let category = 'info';
42                             const categories = ['info', 'success', 'warning', 'danger'];
43
44                             for (const c of categories) {
45                                 if (element.classList.contains(c)) {
46                                     category = c;
47                                     break;
48                                 }
49                             }
50
51                             return {
52                                 node: new CalloutParagraph(category),
53                             };
54                         },
55                         priority: 3,
56                     }
57                 }
58                 return null;
59             }
60         }
61     }
62
63     exportJSON() {
64         return {
65             ...super.exportJSON(),
66             type: 'callout',
67             version: 1,
68             category: this.__category,
69         };
70     }
71 }
72
73 // TODO - Extract callout to own file
74 // TODO - Add helper functions
75 //   https://lexical.dev/docs/concepts/nodes#creating-custom-nodes
76
77 export function createPageEditorInstance(editArea) {
78     console.log('creating editor', editArea);
79
80     const config = {
81         namespace: 'BookStackPageEditor',
82         nodes: [HeadingNode, QuoteNode, CalloutParagraph],
83         onError: console.error,
84     };
85
86     const startingHtml = editArea.innerHTML;
87     const parser = new DOMParser();
88     const dom = parser.parseFromString(startingHtml, 'text/html');
89
90     const editor = createEditor(config);
91     editor.setRootElement(editArea);
92
93     mergeRegister(
94         registerRichText(editor),
95         registerHistory(editor, createEmptyHistoryState(), 300),
96     );
97
98     editor.update(() => {
99         const startingNodes = $generateNodesFromDOM(editor, dom);
100         const root = $getRoot();
101         root.append(...startingNodes);
102     });
103
104     const debugView = document.getElementById('lexical-debug');
105     editor.registerUpdateListener(({editorState}) => {
106         console.log('editorState', editorState.toJSON());
107         debugView.textContent = JSON.stringify(editorState.toJSON(), null, 2);
108     });
109 }