]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/decorators/code-block.ts
37d3df588c3e49f0538a041802156816aff965e0
[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 {$isDecoratorNode, 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             requestAnimationFrame(() => {
38                 context.editor.update(() => {
39                     $selectSingleNode(this.getNode());
40                 });
41             });
42         });
43
44         element.addEventListener('dblclick', event => {
45             context.editor.getEditorState().read(() => {
46                 $openCodeEditorForNode(context.editor, (this.getNode() as CodeBlockNode));
47             });
48         });
49
50         const selectionChange = (selection: BaseSelection|null): void => {
51             element.classList.toggle('selected', $selectionContainsNode(selection, codeNode));
52         };
53         context.manager.onSelectionChange(selectionChange);
54         this.onDestroy(() => {
55             context.manager.offSelectionChange(selectionChange);
56         });
57
58         // @ts-ignore
59         const renderEditor = (Code) => {
60             this.editor = Code.wysiwygView(element, document, this.latestCode, this.latestLanguage);
61             setTimeout(() => {
62                 element.style.height = '';
63             }, 12);
64         };
65
66         // @ts-ignore
67         window.importVersioned('code').then((Code) => {
68             const timeout = (Date.now() - startTime < 20) ? 20 : 0;
69             setTimeout(() => renderEditor(Code), timeout);
70         });
71
72         this.completedSetup = true;
73     }
74
75     update() {
76         const codeNode = this.getNode() as CodeBlockNode;
77         const code = codeNode.getCode();
78         const language = codeNode.getLanguage();
79
80         if (this.latestCode === code && this.latestLanguage === language) {
81             return;
82         }
83         this.latestLanguage = language;
84         this.latestCode = code;
85
86         if (this.editor) {
87             this.editor.setContent(code);
88             this.editor.setMode(language, code);
89         }
90     }
91
92     render(context: EditorUiContext, element: HTMLElement): void {
93         if (this.completedSetup) {
94             this.update();
95         } else {
96             this.setup(context, element);
97         }
98     }
99 }