]> BookStack Code Mirror - bookstack/blob - resources/js/components/markdown-editor.js
671aa4e65fc6e78351d8c51934b7b224d6227837
[bookstack] / resources / js / components / markdown-editor.js
1 import {debounce} from "../services/util";
2 import {Component} from "./component";
3 import {init as initEditor} from "../markdown/editor";
4
5 export class MarkdownEditor extends Component {
6
7     setup() {
8         this.elem = this.$el;
9
10         this.pageId = this.$opts.pageId;
11         this.textDirection = this.$opts.textDirection;
12         this.imageUploadErrorText = this.$opts.imageUploadErrorText;
13         this.serverUploadLimitText = this.$opts.serverUploadLimitText;
14
15         this.display = this.$refs.display;
16         this.input = this.$refs.input;
17
18         this.editor = null;
19         initEditor({
20             pageId: this.pageId,
21             container: this.elem,
22             displayEl: this.display,
23             inputEl: this.input,
24             drawioUrl: this.getDrawioUrl(),
25             text: {
26                 serverUploadLimit: this.serverUploadLimitText,
27                 imageUploadError: this.imageUploadErrorText,
28             }
29         }).then(editor => {
30             this.editor = editor;
31             this.setupListeners();
32             this.emitEditorEvents();
33             this.scrollToTextIfNeeded();
34             this.editor.actions.updateAndRender();
35         });
36     }
37
38     emitEditorEvents() {
39         window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
40             markdownIt: this.editor.markdown.getRenderer(),
41             displayEl: this.display,
42             codeMirrorInstance: this.editor.cm,
43         });
44     }
45
46     setupListeners() {
47
48         // Button actions
49         this.elem.addEventListener('click', event => {
50             let button = event.target.closest('button[data-action]');
51             if (button === null) return;
52
53             const action = button.getAttribute('data-action');
54             if (action === 'insertImage') this.editor.actions.insertImage();
55             if (action === 'insertLink') this.editor.actions.showLinkSelector();
56             if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) {
57                 this.editor.actions.showImageManager();
58                 return;
59             }
60             if (action === 'insertDrawing') this.editor.actions.startDrawing();
61             if (action === 'fullscreen') this.editor.actions.fullScreen();
62         });
63
64         // Mobile section toggling
65         this.elem.addEventListener('click', event => {
66             const toolbarLabel = event.target.closest('.editor-toolbar-label');
67             if (!toolbarLabel) return;
68
69             const currentActiveSections = this.elem.querySelectorAll('.markdown-editor-wrap');
70             for (const activeElem of currentActiveSections) {
71                 activeElem.classList.remove('active');
72             }
73
74             toolbarLabel.closest('.markdown-editor-wrap').classList.add('active');
75         });
76
77         // Refresh CodeMirror on container resize
78         const resizeDebounced = debounce(() => this.editor.cm.refresh(), 100, false);
79         const observer = new ResizeObserver(resizeDebounced);
80         observer.observe(this.elem);
81     }
82
83     scrollToTextIfNeeded() {
84         const queryParams = (new URL(window.location)).searchParams;
85         const scrollText = queryParams.get('content-text');
86         if (scrollText) {
87             this.editor.actions.scrollToText(scrollText);
88         }
89     }
90
91     /**
92      * Get the URL for the configured drawio instance.
93      * @returns {String}
94      */
95     getDrawioUrl() {
96         const drawioAttrEl = document.querySelector('[drawio-url]');
97         if (!drawioAttrEl) {
98             return '';
99         }
100
101         return drawioAttrEl.getAttribute('drawio-url') || '';
102     }
103
104 }