From 9e0b8a9fb6b0c96f9fed911544e22d9d56cb74a1 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 8 Feb 2022 23:08:00 +0000 Subject: [PATCH] Started support for WYSIWYG details/summary blocks --- resources/js/wysiwyg/config.js | 8 +- resources/js/wysiwyg/plugins-details.js | 207 ++++++++++++++++++++++++ resources/sass/_pages.scss | 21 +++ resources/sass/_tinymce.scss | 5 + 4 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 resources/js/wysiwyg/plugins-details.js diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 8e7669acc..13d15e1c5 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -8,6 +8,7 @@ import {getPlugin as getDrawioPlugin} from "./plugin-drawio"; import {getPlugin as getCustomhrPlugin} from "./plugins-customhr"; import {getPlugin as getImagemanagerPlugin} from "./plugins-imagemanager"; import {getPlugin as getAboutPlugin} from "./plugins-about"; +import {getPlugin as getDetailsPlugin} from "./plugins-details"; const style_formats = [ {title: "Large Header", format: "h2", preview: 'color: blue;'}, @@ -27,7 +28,6 @@ const style_formats = [ ]; const formats = { - codeeditor: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div'}, alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'}, aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'}, alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'}, @@ -79,7 +79,7 @@ function buildToolbar(options) { insertoverflow: { icon: 'more-drawer', tooltip: 'More', - items: 'hr codeeditor drawio media' + items: 'hr codeeditor drawio media details' } }; @@ -121,6 +121,7 @@ function gatherPlugins(options) { "media", "imagemanager", "about", + "details", options.textDirection === 'rtl' ? 'directionality' : '', ]; @@ -128,6 +129,7 @@ function gatherPlugins(options) { window.tinymce.PluginManager.add('customhr', getCustomhrPlugin(options)); window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin(options)); window.tinymce.PluginManager.add('about', getAboutPlugin(options)); + window.tinymce.PluginManager.add('details', getDetailsPlugin(options)); if (options.drawioUrl) { window.tinymce.PluginManager.add('drawio', getDrawioPlugin(options)); @@ -240,7 +242,7 @@ export function build(options) { statusbar: false, menubar: false, paste_data_images: false, - extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram]', + extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*]', automatic_uploads: false, valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre],+div[img]", plugins: gatherPlugins(options), diff --git a/resources/js/wysiwyg/plugins-details.js b/resources/js/wysiwyg/plugins-details.js new file mode 100644 index 000000000..90fdf84ec --- /dev/null +++ b/resources/js/wysiwyg/plugins-details.js @@ -0,0 +1,207 @@ +/** + * @param {Editor} editor + * @param {String} url + */ + +function register(editor, url) { + + editor.ui.registry.addIcon('details', ''); + + editor.ui.registry.addButton('details', { + icon: 'details', + tooltip: 'Insert collapsible block', + onAction() { + editor.execCommand('InsertDetailsBlock'); + } + }); + + editor.ui.registry.addButton('removedetails', { + icon: 'table-delete-table', + tooltip: 'Unwrap collapsible block', + onAction() { + unwrapDetailsInSelection(editor) + } + }); + + editor.ui.registry.addButton('editdetials', { + icon: 'tag', + tooltip: 'Edit label', + onAction() { + const details = getSelectedDetailsBlock(editor); + const dialog = editor.windowManager.open(detailsDialog(editor)); + dialog.setData({summary: getSummaryTextFromDetails(details)}); + } + }); + + editor.ui.registry.addButton('collapsedetails', { + icon: 'action-prev', + tooltip: 'Collapse', + onAction() { + const details = getSelectedDetailsBlock(editor); + details.removeAttribute('open'); + editor.focus(); + } + }); + + editor.ui.registry.addButton('expanddetails', { + icon: 'action-next', + tooltip: 'Expand', + onAction() { + const details = getSelectedDetailsBlock(editor); + details.setAttribute('open', 'open'); + editor.focus(); + } + }); + + editor.addCommand('InsertDetailsBlock', function () { + const content = editor.selection.getContent({format: 'html'}); + const details = document.createElement('details'); + const summary = document.createElement('summary'); + details.appendChild(summary); + details.innerHTML += content; + + editor.insertContent(details.outerHTML); + }); + + editor.ui.registry.addContextToolbar('details', { + predicate: function (node) { + return node.nodeName.toLowerCase() === 'details'; + }, + items: 'removedetails editdetials collapsedetails expanddetails', + position: 'node', + scope: 'node' + }); + + editor.on('PreInit', () => { + setupElementFilters(editor); + }); +} + +/** + * @param {Editor} editor + */ +function getSelectedDetailsBlock(editor) { + return editor.selection.getNode().closest('details'); +} + +/** + * @param {Element} element + */ +function getSummaryTextFromDetails(element) { + const summary = element.querySelector('summary'); + if (!summary) { + return ''; + } + return summary.textContent; +} + +/** + * @param {Editor} editor + */ +function detailsDialog(editor) { + return { + title: 'Edit collapsible block', + body: { + type: 'panel', + items: [ + { + type: 'input', + name: 'summary', + label: 'Toggle label text', + }, + ], + }, + buttons: [ + { + type: 'cancel', + text: 'Cancel' + }, + { + type: 'submit', + text: 'Save', + primary: true, + } + ], + onSubmit(api) { + const {summary} = api.getData(); + setSummary(editor, summary); + api.close(); + } + } +} + +function setSummary(editor, summaryContent) { + const details = getSelectedDetailsBlock(editor); + if (!details) return; + + editor.undoManager.transact(() => { + let summary = details.querySelector('summary'); + if (!summary) { + summary = document.createElement('summary'); + details.appendChild(summary); + } + summary.textContent = summaryContent; + }); +} + +/** + * @param {Editor} editor + */ +function unwrapDetailsInSelection(editor) { + const details = editor.selection.getNode().closest('details'); + if (details) { + const summary = details.querySelector('summary'); + editor.undoManager.transact(() => { + if (summary) { + summary.remove(); + } + while (details.firstChild) { + details.parentNode.insertBefore(details.firstChild, details); + } + details.remove(); + }); + } + editor.focus(); +} + +/** + * @param {Editor} editor + */ +function setupElementFilters(editor) { + editor.parser.addNodeFilter('details', function(elms) { + for (const el of elms) { + // el.attr('contenteditable', 'false'); + // console.log(el); + // let wrap = el.find('div[detailswrap]'); + // if (!wrap) { + // wrap = document.createElement('div'); + // wrap.setAttribute('detailswrap', 'true'); + // } + // + // for (const child of el.children) { + // if (child.nodeName.toLowerCase() === 'summary' || child.hasAttribute('detailswrap')) { + // continue; + // } + // wrap.appendChild(child); + // } + // + // el.appendChild(wrap); + // wrap.setAttribute('contenteditable', 'true'); + } + }); + + editor.serializer.addNodeFilter('details', function(elms) { + for (const summaryEl of elms) { + summaryEl.attr('contenteditable', null); + } + }); +} + + +/** + * @param {WysiwygConfigOptions} options + * @return {register} + */ +export function getPlugin(options) { + return register; +} \ No newline at end of file diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss index 23f5150a7..494937299 100755 --- a/resources/sass/_pages.scss +++ b/resources/sass/_pages.scss @@ -135,6 +135,27 @@ body.tox-fullscreen, body.markdown-fullscreen { background: #FFECEC; } + details { + border: 1px solid #DDD; + margin-bottom: 1em; + padding: $-s; + } + details > summary { + margin-top: -$-s; + margin-left: -$-s; + margin-right: -$-s; + margin-bottom: -$-s; + font-weight: bold; + background-color: #EEEEEE; + padding: $-xs $-s; + } + details[open] > summary { + margin-bottom: 0; + } + details > summary + * { + margin-top: .2em; + } + &.page-revision { pre code { white-space: pre-wrap; diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index e846b138f..c5cc179d0 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -37,6 +37,11 @@ body.page-content.mce-content-body { pointer-events: none; } +// Prevent details summary clicks { +.page-content.mce-content-body details summary { + pointer-events: none; +} + /** * Dark Mode Overrides */