]> BookStack Code Mirror - bookstack/blob - resources/js/components/markdown-editor.js
Added test to preference boolean endpoint
[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.settingContainer = this.$refs.settingContainer;
18
19         this.editor = null;
20         initEditor({
21             pageId: this.pageId,
22             container: this.elem,
23             displayEl: this.display,
24             inputEl: this.input,
25             drawioUrl: this.getDrawioUrl(),
26             text: {
27                 serverUploadLimit: this.serverUploadLimitText,
28                 imageUploadError: this.imageUploadErrorText,
29             },
30             settings: this.loadSettings(),
31         }).then(editor => {
32             this.editor = editor;
33             this.setupListeners();
34             this.emitEditorEvents();
35             this.scrollToTextIfNeeded();
36             this.editor.actions.updateAndRender();
37         });
38     }
39
40     emitEditorEvents() {
41         window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
42             markdownIt: this.editor.markdown.getRenderer(),
43             displayEl: this.display,
44             codeMirrorInstance: this.editor.cm,
45         });
46     }
47
48     setupListeners() {
49
50         // Button actions
51         this.elem.addEventListener('click', event => {
52             let button = event.target.closest('button[data-action]');
53             if (button === null) return;
54
55             const action = button.getAttribute('data-action');
56             if (action === 'insertImage') this.editor.actions.insertImage();
57             if (action === 'insertLink') this.editor.actions.showLinkSelector();
58             if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) {
59                 this.editor.actions.showImageManager();
60                 return;
61             }
62             if (action === 'insertDrawing') this.editor.actions.startDrawing();
63             if (action === 'fullscreen') this.editor.actions.fullScreen();
64         });
65
66         // Mobile section toggling
67         this.elem.addEventListener('click', event => {
68             const toolbarLabel = event.target.closest('.editor-toolbar-label');
69             if (!toolbarLabel) return;
70
71             const currentActiveSections = this.elem.querySelectorAll('.markdown-editor-wrap');
72             for (const activeElem of currentActiveSections) {
73                 activeElem.classList.remove('active');
74             }
75
76             toolbarLabel.closest('.markdown-editor-wrap').classList.add('active');
77         });
78
79         // Setting changes
80         this.settingContainer.addEventListener('change', e => {
81             const actualInput = e.target.parentNode.querySelector('input[type="hidden"]');
82             const name = actualInput.getAttribute('name');
83             const value = actualInput.getAttribute('value');
84             window.$http.patch('/preferences/update-boolean', {name, value});
85             this.editor.settings.set(name, value === 'true');
86         });
87
88         // Refresh CodeMirror on container resize
89         const resizeDebounced = debounce(() => this.editor.cm.refresh(), 100, false);
90         const observer = new ResizeObserver(resizeDebounced);
91         observer.observe(this.elem);
92     }
93
94     loadSettings() {
95         const settings = {};
96         const inputs = this.settingContainer.querySelectorAll('input[type="hidden"]');
97
98         for (const input of inputs) {
99             settings[input.getAttribute('name')] = input.value === 'true';
100         }
101
102         return settings;
103     }
104
105     scrollToTextIfNeeded() {
106         const queryParams = (new URL(window.location)).searchParams;
107         const scrollText = queryParams.get('content-text');
108         if (scrollText) {
109             this.editor.actions.scrollToText(scrollText);
110         }
111     }
112
113     /**
114      * Get the URL for the configured drawio instance.
115      * @returns {String}
116      */
117     getDrawioUrl() {
118         const drawioAttrEl = document.querySelector('[drawio-url]');
119         if (!drawioAttrEl) {
120             return '';
121         }
122
123         return drawioAttrEl.getAttribute('drawio-url') || '';
124     }
125
126 }