]> BookStack Code Mirror - bookstack/blob - resources/js/components/markdown-editor.js
JS Build: Split markdown to own file, updated packages
[bookstack] / resources / js / components / markdown-editor.js
1 import {Component} from './component';
2
3 export class MarkdownEditor extends Component {
4
5     setup() {
6         this.elem = this.$el;
7
8         this.pageId = this.$opts.pageId;
9         this.textDirection = this.$opts.textDirection;
10         this.imageUploadErrorText = this.$opts.imageUploadErrorText;
11         this.serverUploadLimitText = this.$opts.serverUploadLimitText;
12
13         this.display = this.$refs.display;
14         this.input = this.$refs.input;
15         this.divider = this.$refs.divider;
16         this.displayWrap = this.$refs.displayWrap;
17
18         const {settingContainer} = this.$refs;
19         const settingInputs = settingContainer.querySelectorAll('input[type="checkbox"]');
20
21         this.editor = null;
22         window.importVersioned('markdown').then(markdown => {
23             return markdown.init({
24                 pageId: this.pageId,
25                 container: this.elem,
26                 displayEl: this.display,
27                 inputEl: this.input,
28                 drawioUrl: this.getDrawioUrl(),
29                 settingInputs: Array.from(settingInputs),
30                 text: {
31                     serverUploadLimit: this.serverUploadLimitText,
32                     imageUploadError: this.imageUploadErrorText,
33                 },
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             cmEditorView: this.editor.cm,
49         });
50     }
51
52     setupListeners() {
53         // Button actions
54         this.elem.addEventListener('click', event => {
55             const button = event.target.closest('button[data-action]');
56             if (button === null) return;
57
58             const action = button.getAttribute('data-action');
59             if (action === 'insertImage') this.editor.actions.showImageInsert();
60             if (action === 'insertLink') this.editor.actions.showLinkSelector();
61             if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) {
62                 this.editor.actions.showImageManager();
63                 return;
64             }
65             if (action === 'insertDrawing') this.editor.actions.startDrawing();
66             if (action === 'fullscreen') this.editor.actions.fullScreen();
67         });
68
69         // Mobile section toggling
70         this.elem.addEventListener('click', event => {
71             const toolbarLabel = event.target.closest('.editor-toolbar-label');
72             if (!toolbarLabel) return;
73
74             const currentActiveSections = this.elem.querySelectorAll('.markdown-editor-wrap');
75             for (const activeElem of currentActiveSections) {
76                 activeElem.classList.remove('active');
77             }
78
79             toolbarLabel.closest('.markdown-editor-wrap').classList.add('active');
80         });
81
82         this.handleDividerDrag();
83     }
84
85     handleDividerDrag() {
86         this.divider.addEventListener('pointerdown', () => {
87             const wrapRect = this.elem.getBoundingClientRect();
88             const moveListener = event => {
89                 const xRel = event.pageX - wrapRect.left;
90                 const xPct = Math.min(Math.max(20, Math.floor((xRel / wrapRect.width) * 100)), 80);
91                 this.displayWrap.style.flexBasis = `${100 - xPct}%`;
92                 this.editor.settings.set('editorWidth', xPct);
93             };
94             const upListener = () => {
95                 window.removeEventListener('pointermove', moveListener);
96                 window.removeEventListener('pointerup', upListener);
97                 this.display.style.pointerEvents = null;
98                 document.body.style.userSelect = null;
99             };
100
101             this.display.style.pointerEvents = 'none';
102             document.body.style.userSelect = 'none';
103             window.addEventListener('pointermove', moveListener);
104             window.addEventListener('pointerup', upListener);
105         });
106         const widthSetting = this.editor.settings.get('editorWidth');
107         if (widthSetting) {
108             this.displayWrap.style.flexBasis = `${100 - widthSetting}%`;
109         }
110     }
111
112     scrollToTextIfNeeded() {
113         const queryParams = (new URL(window.location)).searchParams;
114         const scrollText = queryParams.get('content-text');
115         if (scrollText) {
116             this.editor.actions.scrollToText(scrollText);
117         }
118     }
119
120     /**
121      * Get the URL for the configured drawio instance.
122      * @returns {String}
123      */
124     getDrawioUrl() {
125         const drawioAttrEl = document.querySelector('[drawio-url]');
126         if (!drawioAttrEl) {
127             return '';
128         }
129
130         return drawioAttrEl.getAttribute('drawio-url') || '';
131     }
132
133     /**
134      * Get the content of this editor.
135      * Used by the parent page editor component.
136      * @return {{html: String, markdown: String}}
137      */
138     getContent() {
139         return this.editor.actions.getContent();
140     }
141
142 }