]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/plugin-drawio.js
Replaced embeds with images in exports
[bookstack] / resources / js / wysiwyg / plugin-drawio.js
1 import DrawIO from "../services/drawio";
2 import {build} from "./config";
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     // Show image manager
21     window.ImageManager.show(function (image) {
22         if (selectedNode) {
23             pageEditor.dom.replace(buildDrawingNode(image), selectedNode);
24         } else {
25             const drawingHtml = DrawIO.buildDrawingContentHtml(image);
26             pageEditor.insertContent(drawingHtml);
27         }
28     }, 'drawio');
29 }
30
31 function showDrawingEditor(mceEditor, selectedNode = null) {
32     pageEditor = mceEditor;
33     currentNode = selectedNode;
34     DrawIO.show(options.drawioUrl, drawingInit, updateContent);
35 }
36
37 function buildDrawingNode(drawing) {
38     const drawingEl = DrawIO.buildDrawingContentNode(drawing);
39     drawingEl.setAttribute('contenteditable', 'false');
40     drawingEl.setAttribute('data-ephox-embed-iri', 'true');
41     return drawingEl;
42 }
43
44 async function updateContent(drawingData) {
45     const id = "image-" + Math.random().toString(16).slice(2);
46     const loadingImage = window.baseUrl('/loading.gif');
47
48     const handleUploadError = (error) => {
49         if (error.status === 413) {
50             window.$events.emit('error', options.translations.serverUploadLimitText);
51         } else {
52             window.$events.emit('error', options.translations.imageUploadErrorText);
53         }
54         console.log(error);
55     };
56
57     // Handle updating an existing image
58     if (currentNode) {
59         DrawIO.close();
60         try {
61             const img = await DrawIO.upload(drawingData, options.pageId);
62             pageEditor.dom.replace(buildDrawingNode(img), currentNode);
63         } catch (err) {
64             handleUploadError(err);
65         }
66         return;
67     }
68
69     setTimeout(async () => {
70         pageEditor.insertContent(`<div drawio-diagram contenteditable="false"><img src="${loadingImage}" alt="Loading" id="${id}"></div>`);
71         DrawIO.close();
72         try {
73             const img = await DrawIO.upload(drawingData, options.pageId);
74             pageEditor.dom.replace(buildDrawingNode(img), pageEditor.dom.get(id).parentNode);
75         } catch (err) {
76             pageEditor.dom.remove(id);
77             handleUploadError(err);
78         }
79     }, 5);
80 }
81
82
83 function drawingInit() {
84     if (!currentNode) {
85         return Promise.resolve('');
86     }
87
88     let drawingId = currentNode.getAttribute('drawio-diagram');
89     return DrawIO.load(drawingId);
90 }
91
92 /**
93  * @param {WysiwygConfigOptions} providedOptions
94  * @return {function(Editor, string)}
95  */
96 export function getPlugin(providedOptions) {
97     options = providedOptions;
98     return function(editor, url) {
99
100         editor.addCommand('drawio', () => {
101             const selectedNode = editor.selection.getNode();
102             showDrawingEditor(editor, isDrawing(selectedNode) ? selectedNode : null);
103         });
104
105         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>`)
106
107         editor.ui.registry.addSplitButton('drawio', {
108             tooltip: 'Insert/edit drawing',
109             icon: 'diagram',
110             onAction() {
111                 editor.execCommand('drawio');
112             },
113             fetch(callback) {
114                 callback([
115                     {
116                         type: 'choiceitem',
117                         text: 'Drawing manager',
118                         value: 'drawing-manager',
119                     }
120                 ]);
121             },
122             onItemAction(api, value) {
123                 if (value === 'drawing-manager') {
124                     const selectedNode = editor.selection.getNode();
125                     showDrawingManager(editor, isDrawing(selectedNode) ? selectedNode : null);
126                 }
127             }
128         });
129
130         editor.on('dblclick', event => {
131             let selectedNode = editor.selection.getNode();
132             if (!isDrawing(selectedNode)) return;
133             showDrawingEditor(editor, selectedNode);
134         });
135
136         editor.on('PreInit', () => {
137             editor.parser.addNodeFilter('div', function(nodes) {
138                 for (const node of nodes) {
139                     if (node.attr('drawio-diagram')) {
140                         // Set content editable to be false to prevent direct editing of child content.
141                         node.attr('contenteditable', 'false');
142                         // Set this attribute to prevent drawing contents being parsed as media embeds
143                         // to avoid contents being replaced with placeholder images.
144                         // TinyMCE embed plugin sources looks for this attribute in its logic.
145                         node.attr('data-ephox-embed-iri', 'true');
146                     }
147                 }
148             });
149
150             editor.serializer.addNodeFilter('div', function(nodes) {
151                 for (const node of nodes) {
152                     // Clean up content attributes
153                     if (node.attr('drawio-diagram')) {
154                         node.attr('contenteditable', null);
155                         node.attr('data-ephox-embed-iri', null);
156                     }
157                 }
158             });
159         });
160
161     };
162 }