From: Dan Brown Date: Mon, 23 May 2022 11:24:40 +0000 (+0100) Subject: Updated drawio tinymce plugin to use embeds X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/5fd8e7e0e98d97337daadc18ebe88c6f28b0c18c Updated drawio tinymce plugin to use embeds - Adds support for handling drawings as embeds, based on image extension. - Adds additional attribute to drawio elements within editor to prevent tinymce replacing embeds with a placeholder. - Updates how contenteditable is applied to drawio blocks within editor, to use proper filters instead of using the SetContent event. --- diff --git a/resources/js/services/drawio.js b/resources/js/services/drawio.js index 141b61c72..78fb968cf 100644 --- a/resources/js/services/drawio.js +++ b/resources/js/services/drawio.js @@ -99,4 +99,18 @@ async function load(drawingId) { return resp.data.content; } -export default {show, close, upload, load}; \ No newline at end of file + +function buildDrawingContentHtml(drawing) { + const isSvg = drawing.url.split('.').pop().toLowerCase() === 'svg'; + const image = ``; + const embed = ``; + return `
${isSvg ? embed : image}
` +} + +function buildDrawingContentNode(drawing) { + const div = document.createElement('div'); + div.innerHTML = buildDrawingContentHtml(drawing); + return div.children[0]; +} + +export default {show, close, upload, load, buildDrawingContentHtml, buildDrawingContentNode}; \ No newline at end of file diff --git a/resources/js/wysiwyg/plugin-drawio.js b/resources/js/wysiwyg/plugin-drawio.js index c270c5d20..58dff661f 100644 --- a/resources/js/wysiwyg/plugin-drawio.js +++ b/resources/js/wysiwyg/plugin-drawio.js @@ -1,4 +1,5 @@ import DrawIO from "../services/drawio"; +import {build} from "./config"; let pageEditor = null; let currentNode = null; @@ -15,15 +16,14 @@ function isDrawing(node) { function showDrawingManager(mceEditor, selectedNode = null) { pageEditor = mceEditor; currentNode = selectedNode; + // Show image manager window.ImageManager.show(function (image) { if (selectedNode) { - let imgElem = selectedNode.querySelector('img'); - pageEditor.dom.setAttrib(imgElem, 'src', image.url); - pageEditor.dom.setAttrib(selectedNode, 'drawio-diagram', image.id); + pageEditor.dom.replace(buildDrawingNode(image), selectedNode); } else { - let imgHTML = `
`; - pageEditor.insertContent(imgHTML); + const drawingHtml = DrawIO.buildDrawingContentHtml(image); + pageEditor.insertContent(drawingHtml); } }, 'drawio'); } @@ -34,6 +34,13 @@ function showDrawingEditor(mceEditor, selectedNode = null) { DrawIO.show(options.drawioUrl, drawingInit, updateContent); } +function buildDrawingNode(drawing) { + const drawingEl = DrawIO.buildDrawingContentNode(drawing); + drawingEl.setAttribute('contenteditable', 'false'); + drawingEl.setAttribute('data-ephox-embed-iri', 'true'); + return drawingEl; +} + async function updateContent(drawingData) { const id = "image-" + Math.random().toString(16).slice(2); const loadingImage = window.baseUrl('/loading.gif'); @@ -50,11 +57,9 @@ async function updateContent(drawingData) { // Handle updating an existing image if (currentNode) { DrawIO.close(); - let imgElem = currentNode.querySelector('img'); try { const img = await DrawIO.upload(drawingData, options.pageId); - pageEditor.dom.setAttrib(imgElem, 'src', img.url); - pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', img.id); + pageEditor.dom.replace(buildDrawingNode(img), currentNode); } catch (err) { handleUploadError(err); } @@ -62,12 +67,11 @@ async function updateContent(drawingData) { } setTimeout(async () => { - pageEditor.insertContent(`
`); + pageEditor.insertContent(`
Loading
`); DrawIO.close(); try { const img = await DrawIO.upload(drawingData, options.pageId); - pageEditor.dom.setAttrib(id, 'src', img.url); - pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', img.id); + pageEditor.dom.replace(buildDrawingNode(img), pageEditor.dom.get(id).parentNode); } catch (err) { pageEditor.dom.remove(id); handleUploadError(err); @@ -86,7 +90,6 @@ function drawingInit() { } /** - * * @param {WysiwygConfigOptions} providedOptions * @return {function(Editor, string)} */ @@ -130,14 +133,28 @@ export function getPlugin(providedOptions) { showDrawingEditor(editor, selectedNode); }); - editor.on('SetContent', function () { - const drawings = editor.$('body > div[drawio-diagram]'); - if (!drawings.length) return; + editor.on('PreInit', () => { + editor.parser.addNodeFilter('div', function(nodes) { + for (const node of nodes) { + if (node.attr('drawio-diagram')) { + // Set content editable to be false to prevent direct editing of child content. + node.attr('contenteditable', 'false'); + // Set this attribute to prevent drawing contents being parsed as media embeds + // to avoid contents being replaced with placeholder images. + // TinyMCE embed plugin sources looks for this attribute in its logic. + node.attr('data-ephox-embed-iri', 'true'); + } + } + }); - editor.undoManager.transact(function () { - drawings.each((index, elem) => { - elem.setAttribute('contenteditable', 'false'); - }); + editor.serializer.addNodeFilter('div', function(nodes) { + for (const node of nodes) { + // Clean up content attributes + if (node.attr('drawio-diagram')) { + node.attr('contenteditable', null); + node.attr('data-ephox-embed-iri', null); + } + } }); }); diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index 0ee3fa40b..aa6b05fa6 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -48,6 +48,11 @@ body.page-content.mce-content-body { display: none; } +// Prevent interaction with embed contents +.page-content.mce-content-body embed { + pointer-events: none; +} + // Details/summary editor usability .page-content.mce-content-body details summary { pointer-events: none;