Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions lang/en/entities.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@
'pages_md_insert_drawing' => 'Insert Drawing',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_drawing_unsaved' => 'Unsaved Drawing Found',
'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?',
'pages_not_in_chapter' => 'Page is not in a chapter',
'pages_move' => 'Move Page',
'pages_copy' => 'Copy Page',
Expand Down
7 changes: 6 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
"codemirror": "^6.0.1",
"idb-keyval": "^6.2.1",
"markdown-it": "^13.0.1",
"markdown-it-task-lists": "^2.1.1",
"snabbdom": "^3.5.1",
Expand Down
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,12 @@ Note: This is not an exhaustive list of all libraries and projects that would be
* [OneLogin's SAML PHP Toolkit](https://github.com/onelogin/php-saml) - _[MIT](https://github.com/onelogin/php-saml/blob/master/LICENSE)_
* [League/CommonMark](https://commonmark.thephpleague.com/) - _[BSD-3-Clause](https://github.com/thephpleague/commonmark/blob/2.2/LICENSE)_
* [League/Flysystem](https://flysystem.thephpleague.com) - _[MIT](https://github.com/thephpleague/flysystem/blob/3.x/LICENSE)_
* [League/html-to-markdown](https://github.com/thephpleague/html-to-markdown) - _[MIT](https://github.com/thephpleague/html-to-markdown/blob/master/LICENSE)_
* [League/oauth2-client](https://oauth2-client.thephpleague.com/) - _[MIT](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE)_
* [pragmarx/google2fa](https://github.com/antonioribeiro/google2fa) - _[MIT](https://github.com/antonioribeiro/google2fa/blob/8.x/LICENSE.md)_
* [Bacon/BaconQrCode](https://github.com/Bacon/BaconQrCode) - _[BSD-2-Clause](https://github.com/Bacon/BaconQrCode/blob/master/LICENSE)_
* [phpseclib](https://github.com/phpseclib/phpseclib) - _[MIT](https://github.com/phpseclib/phpseclib/blob/master/LICENSE)_
* [Clockwork](https://github.com/itsgoingd/clockwork) - _[MIT](https://github.com/itsgoingd/clockwork/blob/master/LICENSE)_
* [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan) - _[MIT](https://github.com/phpstan/phpstan/blob/master/LICENSE) and [MIT](https://github.com/nunomaduro/larastan/blob/master/LICENSE.md)_
* [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) - _[BSD 3-Clause](https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt)_
* [JakeArchibald/IDB-Keyval](https://github.com/jakearchibald/idb-keyval) - _[Apache-2.0](https://github.com/jakearchibald/idb-keyval/blob/main/LICENCE)_
20 changes: 12 additions & 8 deletions resources/js/markdown/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,20 @@ export class Actions {

const selectionRange = this.#getSelectionRange();

DrawIO.show(url, () => Promise.resolve(''), pngData => {
DrawIO.show(url, () => Promise.resolve(''), async pngData => {
const data = {
image: pngData,
uploaded_to: Number(this.editor.config.pageId),
};

window.$http.post('/images/drawio', data).then(resp => {
try {
const resp = await window.$http.post('/images/drawio', data);
this.#insertDrawing(resp.data, selectionRange);
DrawIO.close();
}).catch(err => {
} catch (err) {
this.handleDrawingUploadError(err);
});
throw new Error(`Failed to save image with error: ${err}`);
}
});
}

Expand All @@ -112,13 +114,14 @@ export class Actions {
const selectionRange = this.#getSelectionRange();
const drawingId = imgContainer.getAttribute('drawio-diagram');

DrawIO.show(drawioUrl, () => DrawIO.load(drawingId), pngData => {
DrawIO.show(drawioUrl, () => DrawIO.load(drawingId), async pngData => {
const data = {
image: pngData,
uploaded_to: Number(this.editor.config.pageId),
};

window.$http.post('/images/drawio', data).then(resp => {
try {
const resp = await window.$http.post('/images/drawio', data);
const newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url}"></div>`;
const newContent = this.#getText().split('\n').map(line => {
if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) {
Expand All @@ -128,9 +131,10 @@ export class Actions {
}).join('\n');
this.#setText(newContent, selectionRange);
DrawIO.close();
}).catch(err => {
} catch (err) {
this.handleDrawingUploadError(err);
});
throw new Error(`Failed to save image with error: ${err}`);
}
});
}

Expand Down
44 changes: 38 additions & 6 deletions resources/js/services/drawio.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
// Docs: https://www.diagrams.net/doc/faq/embed-mode
import * as store from './store';

let iFrame = null;
let lastApprovedOrigin;
let onInit; let
onSave;
let onInit;
let onSave;
const saveBackupKey = 'last-drawing-save';

function drawPostMessage(data) {
iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin);
}

function drawEventExport(message) {
store.set(saveBackupKey, message.data);
if (onSave) {
onSave(message.data);
onSave(message.data).then(() => {
store.del(saveBackupKey);
});
}
}

Expand Down Expand Up @@ -62,16 +67,43 @@ function drawReceive(event) {
}
}

/**
* Attempt to prompt and restore unsaved drawing content if existing.
* @returns {Promise<void>}
*/
async function attemptRestoreIfExists() {
const backupVal = await store.get(saveBackupKey);
const dialogEl = document.getElementById('unsaved-drawing-dialog');

if (!dialogEl) {
console.error('Missing expected unsaved-drawing dialog');
}

if (backupVal) {
/** @var {ConfirmDialog} */
const dialog = window.$components.firstOnElement(dialogEl, 'confirm-dialog');
const restore = await dialog.show();
if (restore) {
onInit = async () => backupVal;
}
}
}

/**
* Show the draw.io editor.
* onSaveCallback must return a promise that resolves on successful save and errors on failure.
* onInitCallback must return a promise with the xml to load for the editor.
* Will attempt to provide an option to restore unsaved changes if found to exist.
* @param {String} drawioUrl
* @param {Function} onInitCallback - Must return a promise with the xml to load for the editor.
* @param {Function} onSaveCallback - Is called with the drawing data on save.
* @param {Function<Promise<String>>} onInitCallback
* @param {Function<Promise>} onSaveCallback - Is called with the drawing data on save.
*/
export function show(drawioUrl, onInitCallback, onSaveCallback) {
export async function show(drawioUrl, onInitCallback, onSaveCallback) {
onInit = onInitCallback;
onSave = onSaveCallback;

await attemptRestoreIfExists();

iFrame = document.createElement('iframe');
iFrame.setAttribute('frameborder', '0');
window.addEventListener('message', drawReceive);
Expand Down
1 change: 1 addition & 0 deletions resources/js/services/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {get, set, del} from 'idb-keyval';
17 changes: 14 additions & 3 deletions resources/js/services/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
* leading edge, instead of the trailing.
* @attribution https://davidwalsh.name/javascript-debounce-function
* @param {Function} func
* @param {Number} wait
* @param {Number} waitMs
* @param {Boolean} immediate
* @returns {Function}
*/
export function debounce(func, wait, immediate) {
export function debounce(func, waitMs, immediate) {
let timeout;
return function debouncedWrapper(...args) {
const context = this;
Expand All @@ -19,7 +19,7 @@ export function debounce(func, wait, immediate) {
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
timeout = setTimeout(later, waitMs);
if (callNow) func.apply(context, args);
};
}
Expand Down Expand Up @@ -70,3 +70,14 @@ export function uniqueId() {
const S4 = () => (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
return (`${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`);
}

/**
* Create a promise that resolves after the given time.
* @param {int} timeMs
* @returns {Promise}
*/
export function wait(timeMs) {
return new Promise(res => {
setTimeout(res, timeMs);
});
}
35 changes: 20 additions & 15 deletions resources/js/wysiwyg/plugin-drawio.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as DrawIO from '../services/drawio';
import {wait} from '../services/util';

let pageEditor = null;
let currentNode = null;
Expand Down Expand Up @@ -33,7 +34,6 @@ function showDrawingManager(mceEditor, selectedNode = null) {
}

async function updateContent(pngData) {
const id = `image-${Math.random().toString(16).slice(2)}`;
const loadingImage = window.baseUrl('/loading.gif');

const handleUploadError = error => {
Expand All @@ -57,24 +57,29 @@ async function updateContent(pngData) {
});
} catch (err) {
handleUploadError(err);
throw new Error(`Failed to save image with error: ${err}`);
}
return;
}

setTimeout(async () => {
pageEditor.insertContent(`<div drawio-diagram contenteditable="false"><img src="${loadingImage}" id="${id}"></div>`);
DrawIO.close();
try {
const img = await DrawIO.upload(pngData, options.pageId);
pageEditor.undoManager.transact(() => {
pageEditor.dom.setAttrib(id, 'src', img.url);
pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', img.id);
});
} catch (err) {
pageEditor.dom.remove(id);
handleUploadError(err);
}
}, 5);
await wait(5);

const id = `drawing-${Math.random().toString(16).slice(2)}`;
const wrapId = `drawing-wrap-${Math.random().toString(16).slice(2)}`;
pageEditor.insertContent(`<div drawio-diagram contenteditable="false" id="${wrapId}"><img src="${loadingImage}" id="${id}"></div>`);
DrawIO.close();

try {
const img = await DrawIO.upload(pngData, options.pageId);
pageEditor.undoManager.transact(() => {
pageEditor.dom.setAttrib(id, 'src', img.url);
pageEditor.dom.setAttrib(wrapId, 'drawio-diagram', img.id);
});
} catch (err) {
pageEditor.dom.remove(wrapId);
handleUploadError(err);
throw new Error(`Failed to save image with error: ${err}`);
}
}

function drawingInit() {
Expand Down
3 changes: 2 additions & 1 deletion resources/views/common/confirm-dialog.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<div components="popup confirm-dialog"
refs="confirm-dialog@popup {{ $ref }}"
@if($id ?? false) id="{{ $id }}" @endif
refs="confirm-dialog@popup {{ $ref ?? false }}"
class="popup-background">
<div class="popup-body very-small" tabindex="-1">

Expand Down
7 changes: 7 additions & 0 deletions resources/views/pages/parts/form.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,11 @@ class="text-link text-button hide-over-m page-save-mobile-button">@icon('save')<
{{ trans('entities.pages_edit_delete_draft_confirm') }}
</p>
@endcomponent

{{--Saved Drawing--}}
@component('common.confirm-dialog', ['title' => trans('entities.pages_drawing_unsaved'), 'id' => 'unsaved-drawing-dialog'])
<p>
{{ trans('entities.pages_drawing_unsaved_confirm') }}
</p>
@endcomponent
</div>