]> BookStack Code Mirror - bookstack/blob - resources/js/components/markdown-editor.js
Got md shortcuts working, marked actions for update
[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         this.divider = this.$refs.divider;
18         this.displayWrap = this.$refs.displayWrap;
19
20         const settingContainer = this.$refs.settingContainer;
21         const settingInputs = settingContainer.querySelectorAll('input[type="checkbox"]');
22
23         this.editor = null;
24         initEditor({
25             pageId: this.pageId,
26             container: this.elem,
27             displayEl: this.display,
28             inputEl: this.input,
29             drawioUrl: this.getDrawioUrl(),
30             settingInputs: Array.from(settingInputs),
31             text: {
32                 serverUploadLimit: this.serverUploadLimitText,
33                 imageUploadError: this.imageUploadErrorText,
34             },
35         }).then(editor => {
36             this.editor = editor;
37             this.setupListeners();
38             this.emitEditorEvents();
39             this.scrollToTextIfNeeded();
40             this.editor.actions.updateAndRender();
41         });
42     }
43
44     emitEditorEvents() {
45         window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
46             markdownIt: this.editor.markdown.getRenderer(),
47             displayEl: this.display,
48             // TODO - change to codeMirrorView?
49             // codeMirrorInstance: this.editor.cm,
50         });
51     }
52
53     setupListeners() {
54
55         // Button actions
56         this.elem.addEventListener('click', event => {
57             let button = event.target.closest('button[data-action]');
58             if (button === null) return;
59
60             const action = button.getAttribute('data-action');
61             if (action === 'insertImage') this.editor.actions.showImageInsert();
62             if (action === 'insertLink') this.editor.actions.showLinkSelector();
63             if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) {
64                 this.editor.actions.showImageManager();
65                 return;
66             }
67             if (action === 'insertDrawing') this.editor.actions.startDrawing();
68             if (action === 'fullscreen') this.editor.actions.fullScreen();
69         });
70
71         // Mobile section toggling
72         this.elem.addEventListener('click', event => {
73             const toolbarLabel = event.target.closest('.editor-toolbar-label');
74             if (!toolbarLabel) return;
75
76             const currentActiveSections = this.elem.querySelectorAll('.markdown-editor-wrap');
77             for (const activeElem of currentActiveSections) {
78                 activeElem.classList.remove('active');
79             }
80
81             toolbarLabel.closest('.markdown-editor-wrap').classList.add('active');
82         });
83
84         // Refresh CodeMirror on container resize
85         // TODO
86         // const resizeDebounced = debounce(() => this.editor.cm.refresh(), 100, false);
87         // const observer = new ResizeObserver(resizeDebounced);
88         // observer.observe(this.elem);
89
90         this.handleDividerDrag();
91     }
92
93     handleDividerDrag() {
94         this.divider.addEventListener('pointerdown', event => {
95             const wrapRect = this.elem.getBoundingClientRect();
96             const moveListener = (event) => {
97                 const xRel = event.pageX - wrapRect.left;
98                 const xPct = Math.min(Math.max(20, Math.floor((xRel / wrapRect.width) * 100)), 80);
99                 this.displayWrap.style.flexBasis = `${100-xPct}%`;
100                 this.editor.settings.set('editorWidth', xPct);
101             };
102             const upListener = (event) => {
103                 window.removeEventListener('pointermove', moveListener);
104                 window.removeEventListener('pointerup', upListener);
105                 this.display.style.pointerEvents = null;
106                 document.body.style.userSelect = null;
107                 // TODO
108                 // this.editor.cm.refresh();
109             };
110
111             this.display.style.pointerEvents = 'none';
112             document.body.style.userSelect = 'none';
113             window.addEventListener('pointermove', moveListener);
114             window.addEventListener('pointerup', upListener);
115         });
116         const widthSetting = this.editor.settings.get('editorWidth');
117         if (widthSetting) {
118             this.displayWrap.style.flexBasis = `${100-widthSetting}%`;
119         }
120     }
121
122     scrollToTextIfNeeded() {
123         const queryParams = (new URL(window.location)).searchParams;
124         const scrollText = queryParams.get('content-text');
125         if (scrollText) {
126             this.editor.actions.scrollToText(scrollText);
127         }
128     }
129
130     /**
131      * Get the URL for the configured drawio instance.
132      * @returns {String}
133      */
134     getDrawioUrl() {
135         const drawioAttrEl = document.querySelector('[drawio-url]');
136         if (!drawioAttrEl) {
137             return '';
138         }
139
140         return drawioAttrEl.getAttribute('drawio-url') || '';
141     }
142
143     /**
144      * Get the content of this editor.
145      * Used by the parent page editor component.
146      * @return {{html: String, markdown: String}}
147      */
148     getContent() {
149         return this.editor.actions.getContent();
150     }
151
152 }