1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-08-07 23:03:00 +03:00

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.
This commit is contained in:
Dan Brown
2022-05-23 12:24:40 +01:00
parent d926ca5f71
commit 5fd8e7e0e9
3 changed files with 56 additions and 20 deletions

View File

@@ -99,4 +99,18 @@ async function load(drawingId) {
return resp.data.content; 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};

View File

@@ -1,4 +1,5 @@
import DrawIO from "../services/drawio"; import DrawIO from "../services/drawio";
import {build} from "./config";
let pageEditor = null; let pageEditor = null;
let currentNode = null; let currentNode = null;
@@ -15,15 +16,14 @@ function isDrawing(node) {
function showDrawingManager(mceEditor, selectedNode = null) { function showDrawingManager(mceEditor, selectedNode = null) {
pageEditor = mceEditor; pageEditor = mceEditor;
currentNode = selectedNode; currentNode = selectedNode;
// Show image manager // Show image manager
window.ImageManager.show(function (image) { window.ImageManager.show(function (image) {
if (selectedNode) { if (selectedNode) {
let imgElem = selectedNode.querySelector('img'); pageEditor.dom.replace(buildDrawingNode(image), selectedNode);
pageEditor.dom.setAttrib(imgElem, 'src', image.url);
pageEditor.dom.setAttrib(selectedNode, 'drawio-diagram', image.id);
} else { } else {
let imgHTML = `<div drawio-diagram="${image.id}" contenteditable="false"><img src="${image.url}"></div>`; const drawingHtml = DrawIO.buildDrawingContentHtml(image);
pageEditor.insertContent(imgHTML); pageEditor.insertContent(drawingHtml);
} }
}, 'drawio'); }, 'drawio');
} }
@@ -34,6 +34,13 @@ function showDrawingEditor(mceEditor, selectedNode = null) {
DrawIO.show(options.drawioUrl, drawingInit, updateContent); 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) { async function updateContent(drawingData) {
const id = "image-" + Math.random().toString(16).slice(2); const id = "image-" + Math.random().toString(16).slice(2);
const loadingImage = window.baseUrl('/loading.gif'); const loadingImage = window.baseUrl('/loading.gif');
@@ -50,11 +57,9 @@ async function updateContent(drawingData) {
// Handle updating an existing image // Handle updating an existing image
if (currentNode) { if (currentNode) {
DrawIO.close(); DrawIO.close();
let imgElem = currentNode.querySelector('img');
try { try {
const img = await DrawIO.upload(drawingData, options.pageId); const img = await DrawIO.upload(drawingData, options.pageId);
pageEditor.dom.setAttrib(imgElem, 'src', img.url); pageEditor.dom.replace(buildDrawingNode(img), currentNode);
pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', img.id);
} catch (err) { } catch (err) {
handleUploadError(err); handleUploadError(err);
} }
@@ -62,12 +67,11 @@ async function updateContent(drawingData) {
} }
setTimeout(async () => { 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(); DrawIO.close();
try { try {
const img = await DrawIO.upload(drawingData, options.pageId); const img = await DrawIO.upload(drawingData, options.pageId);
pageEditor.dom.setAttrib(id, 'src', img.url); pageEditor.dom.replace(buildDrawingNode(img), pageEditor.dom.get(id).parentNode);
pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', img.id);
} catch (err) { } catch (err) {
pageEditor.dom.remove(id); pageEditor.dom.remove(id);
handleUploadError(err); handleUploadError(err);
@@ -86,7 +90,6 @@ function drawingInit() {
} }
/** /**
*
* @param {WysiwygConfigOptions} providedOptions * @param {WysiwygConfigOptions} providedOptions
* @return {function(Editor, string)} * @return {function(Editor, string)}
*/ */
@@ -130,14 +133,28 @@ export function getPlugin(providedOptions) {
showDrawingEditor(editor, selectedNode); showDrawingEditor(editor, selectedNode);
}); });
editor.on('SetContent', function () { editor.on('PreInit', () => {
const drawings = editor.$('body > div[drawio-diagram]'); editor.parser.addNodeFilter('div', function(nodes) {
if (!drawings.length) return; 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 () { editor.serializer.addNodeFilter('div', function(nodes) {
drawings.each((index, elem) => { for (const node of nodes) {
elem.setAttribute('contenteditable', 'false'); // Clean up content attributes
}); if (node.attr('drawio-diagram')) {
node.attr('contenteditable', null);
node.attr('data-ephox-embed-iri', null);
}
}
}); });
}); });

View File

@@ -48,6 +48,11 @@ body.page-content.mce-content-body {
display: none; display: none;
} }
// Prevent interaction with embed contents
.page-content.mce-content-body embed {
pointer-events: none;
}
// Details/summary editor usability // Details/summary editor usability
.page-content.mce-content-body details summary { .page-content.mce-content-body details summary {
pointer-events: none; pointer-events: none;