]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/decorators/code-block.ts
650bd64c550435fb5ea6374e9a17d2dbea36c6de
[bookstack] / resources / js / wysiwyg / ui / decorators / code-block.ts
1 import {EditorDecorator} from "../framework/decorator";
2 import {EditorUiContext} from "../framework/core";
3 import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
4 import {BaseSelection} from "lexical";
5 import {$selectionContainsNode, $selectSingleNode} from "../../utils/selection";
6
7
8 export class CodeBlockDecorator extends EditorDecorator {
9
10     protected completedSetup: boolean = false;
11     protected latestCode: string = '';
12     protected latestLanguage: string = '';
13
14     // @ts-ignore
15     protected editor: any = null;
16
17     setup(context: EditorUiContext, element: HTMLElement) {
18         const codeNode = this.getNode() as CodeBlockNode;
19         const preEl = element.querySelector('pre');
20         if (!preEl) {
21             return;
22         }
23
24         if (preEl) {
25             preEl.hidden = true;
26         }
27
28         this.latestCode = codeNode.__code;
29         this.latestLanguage = codeNode.__language;
30         const lines = this.latestCode.split('\n').length;
31         const height = (lines * 19.2) + 18 + 24;
32         element.style.height = `${height}px`;
33
34         const startTime = Date.now();
35
36         element.addEventListener('click', event => {
37             context.editor.update(() => {
38                 $selectSingleNode(this.getNode());
39             })
40         });
41
42         element.addEventListener('dblclick', event => {
43             context.editor.getEditorState().read(() => {
44                 $openCodeEditorForNode(context.editor, (this.getNode() as CodeBlockNode));
45             });
46         });
47
48         const selectionChange = (selection: BaseSelection|null): void => {
49             element.classList.toggle('selected', $selectionContainsNode(selection, codeNode));
50         };
51         context.manager.onSelectionChange(selectionChange);
52         this.onDestroy(() => {
53             context.manager.offSelectionChange(selectionChange);
54         });
55
56         // @ts-ignore
57         const renderEditor = (Code) => {
58             this.editor = Code.wysiwygView(element, document, this.latestCode, this.latestLanguage);
59             setTimeout(() => {
60                 element.style.height = '';
61             }, 12);
62         };
63
64         // @ts-ignore
65         window.importVersioned('code').then((Code) => {
66             const timeout = (Date.now() - startTime < 20) ? 20 : 0;
67             setTimeout(() => renderEditor(Code), timeout);
68         });
69
70         this.completedSetup = true;
71     }
72
73     update() {
74         const codeNode = this.getNode() as CodeBlockNode;
75         const code = codeNode.getCode();
76         const language = codeNode.getLanguage();
77
78         if (this.latestCode === code && this.latestLanguage === language) {
79             return;
80         }
81         this.latestLanguage = language;
82         this.latestCode = code;
83
84         if (this.editor) {
85             this.editor.setContent(code);
86             this.editor.setMode(language, code);
87         }
88     }
89
90     render(context: EditorUiContext, element: HTMLElement): void {
91         if (this.completedSetup) {
92             this.update();
93         } else {
94             this.setup(context, element);
95         }
96     }
97 }