]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg-tinymce/plugin-drawio.js
Merge branch 'development' of github.com:BookStackApp/BookStack into development
[bookstack] / resources / js / wysiwyg-tinymce / plugin-drawio.js
1 import * as DrawIO from '../services/drawio.ts';
2 import {wait} from '../services/util.ts';
3
4 let pageEditor = null;
5 let currentNode = null;
6
7 /**
8  * @type {WysiwygConfigOptions}
9  */
10 let options = {};
11
12 function isDrawing(node) {
13     return node.hasAttribute('drawio-diagram');
14 }
15
16 function showDrawingManager(mceEditor, selectedNode = null) {
17     pageEditor = mceEditor;
18     currentNode = selectedNode;
19
20     /** @type {ImageManager} * */
21     const imageManager = window.$components.first('image-manager');
22     imageManager.show(image => {
23         if (selectedNode) {
24             const imgElem = selectedNode.querySelector('img');
25             pageEditor.undoManager.transact(() => {
26                 pageEditor.dom.setAttrib(imgElem, 'src', image.url);
27                 pageEditor.dom.setAttrib(selectedNode, 'drawio-diagram', image.id);
28             });
29         } else {
30             const imgHTML = `<div drawio-diagram="${image.id}" contenteditable="false"><img src="${image.url}"></div>`;
31             pageEditor.insertContent(imgHTML);
32         }
33     }, 'drawio');
34 }
35
36 async function updateContent(pngData) {
37     const loadingImage = window.baseUrl('/loading.gif');
38
39     const handleUploadError = error => {
40         if (error.status === 413) {
41             window.$events.emit('error', options.translations.serverUploadLimitText);
42         } else {
43             window.$events.emit('error', options.translations.imageUploadErrorText);
44         }
45         console.error(error);
46     };
47
48     // Handle updating an existing image
49     if (currentNode) {
50         DrawIO.close();
51         const imgElem = currentNode.querySelector('img');
52         try {
53             const img = await DrawIO.upload(pngData, options.pageId);
54             pageEditor.undoManager.transact(() => {
55                 pageEditor.dom.setAttrib(imgElem, 'src', img.url);
56                 pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', img.id);
57             });
58         } catch (err) {
59             handleUploadError(err);
60             throw new Error(`Failed to save image with error: ${err}`);
61         }
62         return;
63     }
64
65     await wait(5);
66
67     const id = `drawing-${Math.random().toString(16).slice(2)}`;
68     const wrapId = `drawing-wrap-${Math.random().toString(16).slice(2)}`;
69     pageEditor.insertContent(`<div drawio-diagram contenteditable="false" id="${wrapId}"><img src="${loadingImage}" id="${id}"></div>`);
70     DrawIO.close();
71
72     try {
73         const img = await DrawIO.upload(pngData, options.pageId);
74         pageEditor.undoManager.transact(() => {
75             pageEditor.dom.setAttrib(id, 'src', img.url);
76             pageEditor.dom.setAttrib(wrapId, 'drawio-diagram', img.id);
77         });
78     } catch (err) {
79         pageEditor.dom.remove(wrapId);
80         handleUploadError(err);
81         throw new Error(`Failed to save image with error: ${err}`);
82     }
83 }
84
85 function drawingInit() {
86     if (!currentNode) {
87         return Promise.resolve('');
88     }
89
90     const drawingId = currentNode.getAttribute('drawio-diagram');
91     return DrawIO.load(drawingId);
92 }
93
94 function showDrawingEditor(mceEditor, selectedNode = null) {
95     pageEditor = mceEditor;
96     currentNode = selectedNode;
97     DrawIO.show(options.drawioUrl, drawingInit, updateContent);
98 }
99
100 /**
101  * @param {Editor} editor
102  */
103 function register(editor) {
104     editor.addCommand('drawio', () => {
105         const selectedNode = editor.selection.getNode();
106         showDrawingEditor(editor, isDrawing(selectedNode) ? selectedNode : null);
107     });
108
109     editor.ui.registry.addIcon('diagram', `<svg width="24" height="24" fill="${options.darkMode ? '#BBB' : '#000000'}" xmlns="http://www.w3.org/2000/svg"><path d="M20.716 7.639V2.845h-4.794v1.598h-7.99V2.845H3.138v4.794h1.598v7.99H3.138v4.794h4.794v-1.598h7.99v1.598h4.794v-4.794h-1.598v-7.99zM4.736 4.443h1.598V6.04H4.736zm1.598 14.382H4.736v-1.598h1.598zm9.588-1.598h-7.99v-1.598H6.334v-7.99h1.598V6.04h7.99v1.598h1.598v7.99h-1.598zm3.196 1.598H17.52v-1.598h1.598zM17.52 6.04V4.443h1.598V6.04zm-4.21 7.19h-2.79l-.582 1.599H8.643l2.717-7.191h1.119l2.724 7.19h-1.302zm-2.43-1.006h2.086l-1.039-3.06z"/></svg>`);
110
111     editor.ui.registry.addSplitButton('drawio', {
112         tooltip: 'Insert/edit drawing',
113         icon: 'diagram',
114         onAction() {
115             editor.execCommand('drawio');
116             // Hack to de-focus the tinymce editor toolbar
117             window.document.body.dispatchEvent(new Event('mousedown', {bubbles: true}));
118         },
119         fetch(callback) {
120             callback([
121                 {
122                     type: 'choiceitem',
123                     text: 'Drawing manager',
124                     value: 'drawing-manager',
125                 },
126             ]);
127         },
128         onItemAction(api, value) {
129             if (value === 'drawing-manager') {
130                 const selectedNode = editor.selection.getNode();
131                 showDrawingManager(editor, isDrawing(selectedNode) ? selectedNode : null);
132             }
133         },
134     });
135
136     editor.on('dblclick', () => {
137         const selectedNode = editor.selection.getNode();
138         if (!isDrawing(selectedNode)) return;
139         showDrawingEditor(editor, selectedNode);
140     });
141
142     editor.on('SetContent', () => {
143         const drawings = editor.dom.select('body > div[drawio-diagram]');
144         if (!drawings.length) return;
145
146         editor.undoManager.transact(() => {
147             for (const drawing of drawings) {
148                 drawing.setAttribute('contenteditable', 'false');
149             }
150         });
151     });
152 }
153
154 /**
155  *
156  * @param {WysiwygConfigOptions} providedOptions
157  * @return {function(Editor, string)}
158  */
159 export function getPlugin(providedOptions) {
160     options = providedOptions;
161     return register;
162 }
Morty Proxy This is a proxified and sanitized view of the page, visit original site.