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