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