Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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.
  • Loading branch information
ssddanbrown committed May 23, 2022
commit 5fd8e7e0e98d97337daadc18ebe88c6f28b0c18c
16 changes: 15 additions & 1 deletion resources/js/services/drawio.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,18 @@ async function load(drawingId) {
return resp.data.content;
}

export default {show, close, upload, load};

function buildDrawingContentHtml(drawing) {
const isSvg = drawing.url.split('.').pop().toLowerCase() === 'svg';
const image = `<img src="${drawing.url}">`;
const embed = `<embed src="${drawing.url}" type="image/svg+xml">`;
return `<div drawio-diagram="${drawing.id}">${isSvg ? embed : image}</div>`
}

function buildDrawingContentNode(drawing) {
const div = document.createElement('div');
div.innerHTML = buildDrawingContentHtml(drawing);
return div.children[0];
}

export default {show, close, upload, load, buildDrawingContentHtml, buildDrawingContentNode};
55 changes: 36 additions & 19 deletions resources/js/wysiwyg/plugin-drawio.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import DrawIO from "../services/drawio";
import {build} from "./config";

let pageEditor = null;
let currentNode = null;
Expand All @@ -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 = `<div drawio-diagram="${image.id}" contenteditable="false"><img src="${image.url}"></div>`;
pageEditor.insertContent(imgHTML);
const drawingHtml = DrawIO.buildDrawingContentHtml(image);
pageEditor.insertContent(drawingHtml);
}
}, 'drawio');
}
Expand All @@ -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');
Expand All @@ -50,24 +57,21 @@ 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);
}
return;
}

setTimeout(async () => {
pageEditor.insertContent(`<div drawio-diagram contenteditable="false"><img src="${loadingImage}" id="${id}"></div>`);
pageEditor.insertContent(`<div drawio-diagram contenteditable="false"><img src="${loadingImage}" alt="Loading" id="${id}"></div>`);
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);
Expand All @@ -86,7 +90,6 @@ function drawingInit() {
}

/**
*
* @param {WysiwygConfigOptions} providedOptions
* @return {function(Editor, string)}
*/
Expand Down Expand Up @@ -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);
}
}
});
});

Expand Down
5 changes: 5 additions & 0 deletions resources/sass/_tinymce.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down