From 20ecaa5c5abcf86bd3411a6e2c440a208631f203 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 28 Feb 2022 13:29:34 +0000 Subject: [PATCH 01/30] Added ctrl+shift+k shortcut to WYSIWYG Shows entity select dialog for more direct entity link insertion. Aligns with shortcut from markdown editor. For #3244 --- resources/js/wysiwyg/shortcuts.js | 15 +++++++++++++++ resources/lang/en/editor.php | 1 + resources/views/help/wysiwyg.blade.php | 9 +++++++++ 3 files changed, 25 insertions(+) diff --git a/resources/js/wysiwyg/shortcuts.js b/resources/js/wysiwyg/shortcuts.js index 7b7af41ec..8b51437f1 100644 --- a/resources/js/wysiwyg/shortcuts.js +++ b/resources/js/wysiwyg/shortcuts.js @@ -39,4 +39,19 @@ export function register(editor) { editor.formatter.apply('callout' + newFormat); }); + + // Link selector shortcut + editor.shortcuts.add('meta+shift+K', '', function() { + window.EntitySelectorPopup.show(function(entity) { + + if (editor.selection.isCollapsed()) { + editor.insertContent(editor.dom.createHTML('a', {href: entity.link}, editor.dom.encode(entity.name))); + } else { + editor.formatter.apply('link', {href: entity.link}); + } + + editor.selection.collapse(false); + editor.focus(); + }) + }); } \ No newline at end of file diff --git a/resources/lang/en/editor.php b/resources/lang/en/editor.php index 76a9f7fca..4fb1b8f2e 100644 --- a/resources/lang/en/editor.php +++ b/resources/lang/en/editor.php @@ -145,6 +145,7 @@ return [ 'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.', 'save_continue' => 'Save Page & Continue', 'callouts_cycle' => '(Keep pressing to toggle through types)', + 'link_selector' => 'Link to content', 'shortcuts' => 'Shortcuts', 'shortcut' => 'Shortcut', 'shortcuts_intro' => 'The following shortcuts are available in the editor:', diff --git a/resources/views/help/wysiwyg.blade.php b/resources/views/help/wysiwyg.blade.php index 932e37d2e..30fdc18de 100644 --- a/resources/views/help/wysiwyg.blade.php +++ b/resources/views/help/wysiwyg.blade.php @@ -115,6 +115,15 @@ {{ trans('editor.callouts_cycle') }} + + + Ctrl+Shift+K + + + Cmd+Shift+K + + {{ trans('editor.link_selector') }} + From 6252b46395b35c559e6cbe8e50f170bb0485c997 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 28 Feb 2022 13:54:33 +0000 Subject: [PATCH 02/30] Added a custom link context toolbar - Allows for easy unlinking, link preview or link editing. - Created custom one to limit actions available. - Performed refactoring of non-plugin toolbar editor code to extact into its own file. Related to #3276 --- resources/js/wysiwyg/config.js | 51 ++----------------------- resources/js/wysiwyg/toolbars.js | 64 ++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 48 deletions(-) create mode 100644 resources/js/wysiwyg/toolbars.js diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 1b3b6e7b5..2da1e2c98 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -2,6 +2,7 @@ import {register as registerShortcuts} from "./shortcuts"; import {listen as listenForCommonEvents} from "./common-events"; import {scrollToQueryString} from "./scrolling"; import {listenForDragAndPaste} from "./drop-paste-handling"; +import {getPrimaryToolbar, registerAdditionalToolbars} from "./toolbars"; import {getPlugin as getCodeeditorPlugin} from "./plugin-codeeditor"; import {getPlugin as getDrawioPlugin} from "./plugin-drawio"; @@ -58,48 +59,6 @@ function file_picker_callback(callback, value, meta) { } -/** - * @param {WysiwygConfigOptions} options - * @return {{toolbar: string, groupButtons: Object}} - */ -function buildToolbar(options) { - const textDirPlugins = options.textDirection === 'rtl' ? 'ltr rtl' : ''; - - const groupButtons = { - formatoverflow: { - icon: 'more-drawer', - tooltip: 'More', - items: 'strikethrough superscript subscript inlinecode removeformat' - }, - listoverflow: { - icon: 'more-drawer', - tooltip: 'More', - items: 'outdent indent' - }, - insertoverflow: { - icon: 'more-drawer', - tooltip: 'More', - items: 'hr codeeditor drawio media details' - } - }; - - const toolbar = [ - 'undo redo', - 'styleselect', - 'bold italic underline forecolor backcolor formatoverflow', - 'alignleft aligncenter alignright alignjustify', - 'bullist numlist listoverflow', - textDirPlugins, - 'link table imagemanager-insert insertoverflow', - 'code about fullscreen' - ]; - - return { - toolbar: toolbar.filter(row => Boolean(row)).join(' | '), - groupButtons, - }; -} - /** * @param {WysiwygConfigOptions} options * @return {string} @@ -218,8 +177,6 @@ export function build(options) { // Set language window.tinymce.addI18n(options.language, options.translationMap); - // Build toolbar content - const {toolbar, groupButtons: toolBarGroupButtons} = buildToolbar(options); // Return config object return { @@ -256,7 +213,7 @@ export function build(options) { plugins: gatherPlugins(options), imagetools_toolbar: 'imageoptions', contextmenu: false, - toolbar: toolbar, + toolbar: getPrimaryToolbar(options), content_style: getContentStyle(options), style_formats, style_formats_merge: false, @@ -276,9 +233,7 @@ export function build(options) { head.innerHTML += fetchCustomHeadContent(); }, setup(editor) { - for (const [key, config] of Object.entries(toolBarGroupButtons)) { - editor.ui.registry.addGroupToolbarButton(key, config); - } + registerAdditionalToolbars(editor, options); getSetupCallback(options)(editor); }, }; diff --git a/resources/js/wysiwyg/toolbars.js b/resources/js/wysiwyg/toolbars.js new file mode 100644 index 000000000..40cf09dc3 --- /dev/null +++ b/resources/js/wysiwyg/toolbars.js @@ -0,0 +1,64 @@ +/** + * @param {WysiwygConfigOptions} options + * @return {String} + */ +export function getPrimaryToolbar(options) { + const textDirPlugins = options.textDirection === 'rtl' ? 'ltr rtl' : ''; + + const toolbar = [ + 'undo redo', + 'styleselect', + 'bold italic underline forecolor backcolor formatoverflow', + 'alignleft aligncenter alignright alignjustify', + 'bullist numlist listoverflow', + textDirPlugins, + 'link table imagemanager-insert insertoverflow', + 'code about fullscreen' + ]; + + return toolbar.filter(row => Boolean(row)).join(' | '); +} + +/** + * @param {Editor} editor + */ +function registerPrimaryToolbarGroups(editor) { + editor.ui.registry.addGroupToolbarButton('formatoverflow', { + icon: 'more-drawer', + tooltip: 'More', + items: 'strikethrough superscript subscript inlinecode removeformat' + }); + editor.ui.registry.addGroupToolbarButton('listoverflow', { + icon: 'more-drawer', + tooltip: 'More', + items: 'outdent indent' + }); + editor.ui.registry.addGroupToolbarButton('insertoverflow', { + icon: 'more-drawer', + tooltip: 'More', + items: 'hr codeeditor drawio media details' + }); +} + +/** + * @param {Editor} editor + */ +function registerLinkContextToolbar(editor) { + editor.ui.registry.addContextToolbar('linkcontexttoolbar', { + predicate(node) { + return node.closest('a') !== null; + }, + position: 'node', + scope: 'node', + items: 'link unlink openlink' + }); +} + +/** + * @param {Editor} editor + * @param {WysiwygConfigOptions} options + */ +export function registerAdditionalToolbars(editor, options) { + registerPrimaryToolbarGroups(editor); + registerLinkContextToolbar(editor); +} \ No newline at end of file From ee6a2339b654d1a4537319fb1548fd311549dfc7 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 9 Mar 2022 14:30:36 +0000 Subject: [PATCH 03/30] Applied latest styleCI changes --- app/Util/CspService.php | 4 +++- tests/SecurityHeaderTest.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Util/CspService.php b/app/Util/CspService.php index ba927c93b..f9ab666ac 100644 --- a/app/Util/CspService.php +++ b/app/Util/CspService.php @@ -22,7 +22,7 @@ class CspService } /** - * Get the CSP headers for the application + * Get the CSP headers for the application. */ public function getCspHeader(): string { @@ -86,6 +86,7 @@ class CspService { $iframeHosts = $this->getAllowedIframeHosts(); array_unshift($iframeHosts, "'self'"); + return 'frame-ancestors ' . implode(' ', $iframeHosts); } @@ -97,6 +98,7 @@ class CspService { $iframeHosts = $this->getAllowedIframeSources(); array_unshift($iframeHosts, "'self'"); + return 'frame-src ' . implode(' ', $iframeHosts); } diff --git a/tests/SecurityHeaderTest.php b/tests/SecurityHeaderTest.php index 1a0a6c9b3..d8ba5873f 100644 --- a/tests/SecurityHeaderTest.php +++ b/tests/SecurityHeaderTest.php @@ -130,7 +130,7 @@ class SecurityHeaderTest extends TestCase { config()->set([ 'app.iframe_sources' => 'https://example.com', - 'services.drawio' => 'https://diagrams.example.com/testing?cat=dog', + 'services.drawio' => 'https://diagrams.example.com/testing?cat=dog', ]); $resp = $this->get('/'); From f991948c4931ecf2d4e32dadc8b099207f76fa82 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 19 Mar 2022 16:04:33 +0000 Subject: [PATCH 04/30] Started initial tasklist attempt, failed implementation --- resources/js/wysiwyg/config.js | 5 +- resources/js/wysiwyg/plugins-tasklist.js | 123 +++++++++++++++++++++++ resources/sass/_text.scss | 1 + 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 resources/js/wysiwyg/plugins-tasklist.js diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 965b14d08..fab6a3886 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -10,6 +10,7 @@ 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"; +import {getPlugin as getTasklistPlugin} from "./plugins-tasklist"; const style_formats = [ {title: "Large Header", format: "h2", preview: 'color: blue;'}, @@ -81,6 +82,7 @@ function gatherPlugins(options) { "imagemanager", "about", "details", + "tasklist", options.textDirection === 'rtl' ? 'directionality' : '', ]; @@ -89,6 +91,7 @@ function gatherPlugins(options) { window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin(options)); window.tinymce.PluginManager.add('about', getAboutPlugin(options)); window.tinymce.PluginManager.add('details', getDetailsPlugin(options)); + window.tinymce.PluginManager.add('tasklist', getTasklistPlugin(options)); if (options.drawioUrl) { window.tinymce.PluginManager.add('drawio', getDrawioPlugin(options)); @@ -204,7 +207,7 @@ export function build(options) { statusbar: false, menubar: false, paste_data_images: false, - extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*]', + extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class]', automatic_uploads: false, custom_elements: 'doc-root,code-block', valid_children: [ diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js new file mode 100644 index 000000000..07f934463 --- /dev/null +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -0,0 +1,123 @@ +/** + * @param {Editor} editor + */ +function defineTaskListCustomElement(editor) { + const doc = editor.getDoc(); + const win = doc.defaultView; + + class TaskListElement extends win.HTMLElement { + constructor() { + super(); + // this.attachShadow({mode: 'open'}); + // + // const input = doc.createElement('input'); + // input.setAttribute('type', 'checkbox'); + // input.setAttribute('disabled', 'disabled'); + // + // if (this.hasAttribute('selected')) { + // input.setAttribute('selected', 'selected'); + // } + // + // this.shadowRoot.append(input); + // this.shadowRoot.close(); + } + } + + win.customElements.define('task-list-item', TaskListElement); +} + +/** + * @param {Editor} editor + * @param {String} url + */ +function register(editor, url) { + + // editor.on('NewBlock', ({ newBlock}) => { + // ensureElementHasCheckbox(newBlock); + // }); + + editor.on('PreInit', () => { + + defineTaskListCustomElement(editor); + + editor.parser.addNodeFilter('li', function(elms) { + for (const elem of elms) { + if (elem.attributes.map.class === 'task-list-item') { + replaceTaskListNode(elem); + } + } + }); + + // editor.serializer.addNodeFilter('li', function(elms) { + // for (const elem of elms) { + // if (elem.attributes.map.class === 'task-list-item') { + // ensureNodeHasCheckbox(elem); + // } + // } + // }); + + }); + +} + +/** + * @param {AstNode} node + */ +function replaceTaskListNode(node) { + + const taskListItem = new tinymce.html.Node.create('task-list-item', { + }); + + for (const child of node.children()) { + if (node.name !== 'input') { + taskListItem.append(child); + } + } + + node.replace(taskListItem); +} + +// /** +// * @param {Element} elem +// */ +// function ensureElementHasCheckbox(elem) { +// const hasCheckbox = elem.querySelector(':scope > input[type="checkbox"]') !== null; +// if (hasCheckbox) { +// return; +// } +// +// const input = elem.ownerDocument.createElement('input'); +// input.setAttribute('type', 'checkbox'); +// input.setAttribute('disabled', 'disabled'); +// elem.prepend(input); +// } + +/** + * @param {AstNode} elem + */ +function ensureNodeHasCheckbox(elem) { + // Stop if there's already an input + if (elem.firstChild && elem.firstChild.name === 'input') { + return; + } + + const input = new tinymce.html.Node.create('input', { + type: 'checkbox', + disabled: 'disabled', + }); + + if (elem.firstChild) { + elem.insert(input, elem.firstChild, true); + } else { + elem.append(input); + } +} + + +/** + * @param {WysiwygConfigOptions} options + * @return {register} + */ +export function getPlugin(options) { + return register; +} \ No newline at end of file diff --git a/resources/sass/_text.scss b/resources/sass/_text.scss index cbe3cd4be..884808bb4 100644 --- a/resources/sass/_text.scss +++ b/resources/sass/_text.scss @@ -310,6 +310,7 @@ li > ol, li > ul { } li.checkbox-item, li.task-list-item { + display: list-item; list-style: none; margin-left: -($-m * 1.2); input[type="checkbox"] { From 65dd7ad1e91f01f3ee1250fcb18fafb4c0429463 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 19 Mar 2022 17:12:56 +0000 Subject: [PATCH 05/30] Changed to a psuedo-style approach for tasklist in wysiwyg --- resources/js/wysiwyg/config.js | 2 +- resources/js/wysiwyg/plugins-tasklist.js | 116 +++++++---------------- resources/sass/_tinymce.scss | 25 +++++ 3 files changed, 58 insertions(+), 85 deletions(-) diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index fab6a3886..e75e4f712 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -207,7 +207,7 @@ export function build(options) { statusbar: false, menubar: false, paste_data_images: false, - extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class]', + extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class|checked]', automatic_uploads: false, custom_elements: 'doc-root,code-block', valid_children: [ diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js index 07f934463..4070575d9 100644 --- a/resources/js/wysiwyg/plugins-tasklist.js +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -1,60 +1,27 @@ -/** - * @param {Editor} editor - */ -function defineTaskListCustomElement(editor) { - const doc = editor.getDoc(); - const win = doc.defaultView; - - class TaskListElement extends win.HTMLElement { - constructor() { - super(); - // this.attachShadow({mode: 'open'}); - // - // const input = doc.createElement('input'); - // input.setAttribute('type', 'checkbox'); - // input.setAttribute('disabled', 'disabled'); - // - // if (this.hasAttribute('selected')) { - // input.setAttribute('selected', 'selected'); - // } - // - // this.shadowRoot.append(input); - // this.shadowRoot.close(); - } - } - - win.customElements.define('task-list-item', TaskListElement); -} - /** * @param {Editor} editor * @param {String} url */ -function register(editor, url) { - // editor.on('NewBlock', ({ newBlock}) => { - // ensureElementHasCheckbox(newBlock); - // }); +function register(editor, url) { editor.on('PreInit', () => { - defineTaskListCustomElement(editor); - - editor.parser.addNodeFilter('li', function(elms) { - for (const elem of elms) { - if (elem.attributes.map.class === 'task-list-item') { - replaceTaskListNode(elem); + editor.parser.addNodeFilter('li', function(nodes) { + for (const node of nodes) { + if (node.attributes.map.class === 'task-list-item') { + parseTaskListNode(node); } } }); - // editor.serializer.addNodeFilter('li', function(elms) { - // for (const elem of elms) { - // if (elem.attributes.map.class === 'task-list-item') { - // ensureNodeHasCheckbox(elem); - // } - // } - // }); + editor.serializer.addNodeFilter('li', function(nodes) { + for (const node of nodes) { + if (node.attributes.map.class === 'task-list-item') { + serializeTaskListNode(node); + } + } + }); }); @@ -63,57 +30,38 @@ function register(editor, url) { /** * @param {AstNode} node */ -function replaceTaskListNode(node) { - - const taskListItem = new tinymce.html.Node.create('task-list-item', { - }); +function parseTaskListNode(node) { + // Force task list item class + node.attr('class', 'task-list-item'); + // Copy checkbox status and remove checkbox within editor for (const child of node.children()) { - if (node.name !== 'input') { - taskListItem.append(child); + if (child.name === 'input') { + if (child.attr('checked') === 'checked') { + node.attr('checked', 'checked'); + } + child.remove(); } } - - node.replace(taskListItem); } -// /** -// * @param {Element} elem -// */ -// function ensureElementHasCheckbox(elem) { -// const hasCheckbox = elem.querySelector(':scope > input[type="checkbox"]') !== null; -// if (hasCheckbox) { -// return; -// } -// -// const input = elem.ownerDocument.createElement('input'); -// input.setAttribute('type', 'checkbox'); -// input.setAttribute('disabled', 'disabled'); -// elem.prepend(input); -// } - /** - * @param {AstNode} elem + * @param {AstNode} node */ -function ensureNodeHasCheckbox(elem) { - // Stop if there's already an input - if (elem.firstChild && elem.firstChild.name === 'input') { - return; +function serializeTaskListNode(node) { + const isChecked = node.attr('checked') === 'checked'; + node.attr('checked', null); + + const inputAttrs = {type: 'checkbox', disabled: 'disabled'}; + if (isChecked) { + inputAttrs.checked = 'checked'; } - const input = new tinymce.html.Node.create('input', { - type: 'checkbox', - disabled: 'disabled', - }); - - if (elem.firstChild) { - elem.insert(input, elem.firstChild, true); - } else { - elem.append(input); - } + const checkbox = new tinymce.html.Node.create('input', inputAttrs); + checkbox.shortEnded = true; + node.firstChild ? node.insert(checkbox, node.firstChild, true) : node.append(checkbox); } - /** * @param {WysiwygConfigOptions} options * @return {register} diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index 6add27f45..c4848561a 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -112,4 +112,29 @@ body.page-content.mce-content-body { } .tox-menu .tox-collection__item-label { line-height: normal !important; +} + +/** + * Fake task list checkboxes + */ +.page-content.mce-content-body .task-list-item > input[type="checkbox"] { + display: none; +} +.page-content.mce-content-body .task-list-item:before { + content: ''; + display: inline-block; + border: 2px solid #CCC; + width: 12px; + height: 12px; + border-radius: 2px; + margin-right: 8px; + vertical-align: text-top; + cursor: pointer; +} + +.page-content.mce-content-body .task-list-item[checked]:before { + background-color: #CCC; + background-image: url('data:image/svg+xml;utf8,'); + background-position: 50% 50%; + background-size: 100% 100%; } \ No newline at end of file From b6be8a2bb9a640eef14e67376cbb57f999084ca9 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 Mar 2022 11:59:46 +0000 Subject: [PATCH 06/30] Added WYSIWYG tasklist clicking ability --- resources/js/wysiwyg/plugins-tasklist.js | 31 ++++++++++++++++++++++++ resources/sass/_tinymce.scss | 7 ++++++ 2 files changed, 38 insertions(+) diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js index 4070575d9..3fbc2c1e8 100644 --- a/resources/js/wysiwyg/plugins-tasklist.js +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -25,6 +25,37 @@ function register(editor, url) { }); + editor.on('click', function(event) { + const clickedEl = event.originalTarget; + if (clickedEl.nodeName === 'LI' && clickedEl.classList.contains('task-list-item')) { + handleTaskListItemClick(event, clickedEl, editor); + } + }); + +} + +/** + * @param {MouseEvent} event + * @param {Element} clickedEl + * @param {Editor} editor + */ +function handleTaskListItemClick(event, clickedEl, editor) { + const bounds = clickedEl.getBoundingClientRect(); + const withinBounds = event.clientX <= bounds.right + && event.clientX >= bounds.left + && event.clientY >= bounds.top + && event.clientY <= bounds.bottom; + + // Outside of the task list item bounds mean we're probably clicking the pseudo-element. + if (!withinBounds) { + editor.undoManager.transact(() => { + if (clickedEl.hasAttribute('checked')) { + clickedEl.removeAttribute('checked'); + } else { + clickedEl.setAttribute('checked', 'checked'); + } + }); + } } /** diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index c4848561a..0ee3fa40b 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -117,6 +117,10 @@ body.page-content.mce-content-body { /** * Fake task list checkboxes */ +.page-content.mce-content-body .task-list-item { + margin-left: 0; + position: relative; +} .page-content.mce-content-body .task-list-item > input[type="checkbox"] { display: none; } @@ -130,6 +134,9 @@ body.page-content.mce-content-body { margin-right: 8px; vertical-align: text-top; cursor: pointer; + position: absolute; + left: -24px; + top: 4px; } .page-content.mce-content-body .task-list-item[checked]:before { From 5ae9ed1e226638096a85d79836962397b6bbb263 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 Mar 2022 13:30:48 +0000 Subject: [PATCH 07/30] Added functioning wysiwyg tasklist toolbar button - Includes new icon. - Includes menu button overrides of existing list styles to prevent incompatible mixing. --- resources/js/wysiwyg/plugins-tasklist.js | 56 +++++++++++++++++++++--- resources/js/wysiwyg/toolbars.js | 2 +- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js index 3fbc2c1e8..cb1bb4a7a 100644 --- a/resources/js/wysiwyg/plugins-tasklist.js +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -2,11 +2,59 @@ * @param {Editor} editor * @param {String} url */ - function register(editor, url) { - editor.on('PreInit', () => { + // Tasklist UI buttons + editor.ui.registry.addIcon('tasklist', ''); + editor.ui.registry.addToggleButton('tasklist', { + tooltip: 'Task list', + icon: 'tasklist', + active: false, + onAction(api) { + if (api.isActive()) { + editor.execCommand('RemoveList'); + } else { + editor.execCommand('InsertUnorderedList', null, { + 'list-item-attributes': { + class: 'task-list-item', + }, + 'list-style-type': 'tasklist', + }); + } + }, + onSetup(api) { + editor.on('NodeChange', event => { + const inList = event.parents.find(el => el.nodeName === 'LI' && el.classList.contains('task-list-item')) !== undefined; + api.setActive(inList); + }); + } + }); + // Tweak existing bullet list button active state to not be active + // when we're in a task list. + const existingBullListButton = editor.ui.registry.getAll().buttons.bullist; + existingBullListButton.onSetup = function(api) { + editor.on('NodeChange', event => { + const notInTaskList = event.parents.find(el => el.nodeName === 'LI' && el.classList.contains('task-list-item')) === undefined; + const inList = event.parents.find(el => el.nodeName === 'UL') !== undefined; + api.setActive(inList && notInTaskList); + }); + }; + existingBullListButton.onAction = function() { + editor.execCommand('InsertUnorderedList', null, { + 'list-item-attributes': {class: null} + }); + }; + // Tweak existing number list to not allow classes on child items + const existingNumListButton = editor.ui.registry.getAll().buttons.numlist; + existingNumListButton.onAction = function() { + editor.execCommand('InsertOrderedList', null, { + 'list-item-attributes': {class: null} + }); + }; + + // Setup filters on pre-init + editor.on('PreInit', () => { editor.parser.addNodeFilter('li', function(nodes) { for (const node of nodes) { if (node.attributes.map.class === 'task-list-item') { @@ -14,7 +62,6 @@ function register(editor, url) { } } }); - editor.serializer.addNodeFilter('li', function(nodes) { for (const node of nodes) { if (node.attributes.map.class === 'task-list-item') { @@ -22,16 +69,15 @@ function register(editor, url) { } } }); - }); + // Handle checkbox click in editor editor.on('click', function(event) { const clickedEl = event.originalTarget; if (clickedEl.nodeName === 'LI' && clickedEl.classList.contains('task-list-item')) { handleTaskListItemClick(event, clickedEl, editor); } }); - } /** diff --git a/resources/js/wysiwyg/toolbars.js b/resources/js/wysiwyg/toolbars.js index 40cf09dc3..4f8897f84 100644 --- a/resources/js/wysiwyg/toolbars.js +++ b/resources/js/wysiwyg/toolbars.js @@ -10,7 +10,7 @@ export function getPrimaryToolbar(options) { 'styleselect', 'bold italic underline forecolor backcolor formatoverflow', 'alignleft aligncenter alignright alignjustify', - 'bullist numlist listoverflow', + 'bullist numlist tasklist listoverflow', textDirPlugins, 'link table imagemanager-insert insertoverflow', 'code about fullscreen' From ea62fe6004b2403c245d02a0b957f29d9c232ccd Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 22 Mar 2022 14:03:20 +0000 Subject: [PATCH 08/30] Improved tasklist wysiwyg behaviour - Updated buttons/actions to better handle nesting. - Added hack for better usage with normal bullets --- resources/js/wysiwyg/plugins-tasklist.js | 30 ++++++++++++++++++++---- resources/js/wysiwyg/toolbars.js | 4 ++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js index cb1bb4a7a..2dd6528e1 100644 --- a/resources/js/wysiwyg/plugins-tasklist.js +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -24,7 +24,8 @@ function register(editor, url) { }, onSetup(api) { editor.on('NodeChange', event => { - const inList = event.parents.find(el => el.nodeName === 'LI' && el.classList.contains('task-list-item')) !== undefined; + const parentListEl = event.parents.find(el => el.nodeName === 'LI'); + const inList = parentListEl && parentListEl.classList.contains('task-list-item'); api.setActive(inList); }); } @@ -35,12 +36,22 @@ function register(editor, url) { const existingBullListButton = editor.ui.registry.getAll().buttons.bullist; existingBullListButton.onSetup = function(api) { editor.on('NodeChange', event => { - const notInTaskList = event.parents.find(el => el.nodeName === 'LI' && el.classList.contains('task-list-item')) === undefined; - const inList = event.parents.find(el => el.nodeName === 'UL') !== undefined; - api.setActive(inList && notInTaskList); + const parentList = event.parents.find(el => el.nodeName === 'LI'); + const inTaskList = parentList && parentList.classList.contains('task-list-item'); + const inUlList = parentList && parentList.parentNode.nodeName === 'UL'; + api.setActive(inUlList && !inTaskList); }); }; existingBullListButton.onAction = function() { + // Cheeky hack to prevent list toggle action treating tasklists as normal + // unordered lists which would unwrap the list on toggle from tasklist to bullet list. + // Instead we quickly jump through an ordered list first if we're within a tasklist. + if (elementWithinTaskList(editor.selection.getNode())) { + editor.execCommand('InsertOrderedList', null, { + 'list-item-attributes': {class: null} + }); + } + editor.execCommand('InsertUnorderedList', null, { 'list-item-attributes': {class: null} }); @@ -80,6 +91,15 @@ function register(editor, url) { }); } +/** + * @param {Element} element + * @return {boolean} + */ +function elementWithinTaskList(element) { + const listEl = element.closest('li'); + return listEl && listEl.parentNode.nodeName === 'UL' && listEl.classList.contains('task-list-item'); +} + /** * @param {MouseEvent} event * @param {Element} clickedEl @@ -126,6 +146,7 @@ function parseTaskListNode(node) { * @param {AstNode} node */ function serializeTaskListNode(node) { + // Get checked status and clean it from list node const isChecked = node.attr('checked') === 'checked'; node.attr('checked', null); @@ -134,6 +155,7 @@ function serializeTaskListNode(node) { inputAttrs.checked = 'checked'; } + // Create & insert checkbox input element const checkbox = new tinymce.html.Node.create('input', inputAttrs); checkbox.shortEnded = true; node.firstChild ? node.insert(checkbox, node.firstChild, true) : node.append(checkbox); diff --git a/resources/js/wysiwyg/toolbars.js b/resources/js/wysiwyg/toolbars.js index 4f8897f84..740220d84 100644 --- a/resources/js/wysiwyg/toolbars.js +++ b/resources/js/wysiwyg/toolbars.js @@ -10,7 +10,7 @@ export function getPrimaryToolbar(options) { 'styleselect', 'bold italic underline forecolor backcolor formatoverflow', 'alignleft aligncenter alignright alignjustify', - 'bullist numlist tasklist listoverflow', + 'bullist numlist listoverflow', textDirPlugins, 'link table imagemanager-insert insertoverflow', 'code about fullscreen' @@ -31,7 +31,7 @@ function registerPrimaryToolbarGroups(editor) { editor.ui.registry.addGroupToolbarButton('listoverflow', { icon: 'more-drawer', tooltip: 'More', - items: 'outdent indent' + items: 'tasklist outdent indent' }); editor.ui.registry.addGroupToolbarButton('insertoverflow', { icon: 'more-drawer', From c5aad29c72e204605e7f0c5fa03d631bcb147cdf Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 22 Mar 2022 14:56:51 +0000 Subject: [PATCH 09/30] Added tasklist support to markdown exporter --- .../Tools/Markdown/CheckboxConverter.php | 28 +++++++++++++++++++ .../Tools/Markdown/HtmlToMarkdown.php | 1 + tests/Entity/ExportTest.php | 12 ++++++++ 3 files changed, 41 insertions(+) create mode 100644 app/Entities/Tools/Markdown/CheckboxConverter.php diff --git a/app/Entities/Tools/Markdown/CheckboxConverter.php b/app/Entities/Tools/Markdown/CheckboxConverter.php new file mode 100644 index 000000000..e4666d666 --- /dev/null +++ b/app/Entities/Tools/Markdown/CheckboxConverter.php @@ -0,0 +1,28 @@ +getAttribute('type')) === 'checkbox') { + $isChecked = $element->getAttribute('checked') === 'checked'; + return $isChecked ? ' [x] ' : ' [ ] '; + } + + return $element->getValue(); + } + + /** + * @return string[] + */ + public function getSupportedTags(): array + { + return ['input']; + } +} \ No newline at end of file diff --git a/app/Entities/Tools/Markdown/HtmlToMarkdown.php b/app/Entities/Tools/Markdown/HtmlToMarkdown.php index e8804690c..51366705c 100644 --- a/app/Entities/Tools/Markdown/HtmlToMarkdown.php +++ b/app/Entities/Tools/Markdown/HtmlToMarkdown.php @@ -87,6 +87,7 @@ class HtmlToMarkdown $environment->addConverter(new CustomParagraphConverter()); $environment->addConverter(new PreformattedConverter()); $environment->addConverter(new TextConverter()); + $environment->addConverter(new CheckboxConverter()); return $environment; } diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index fc15bb8f3..2841175ad 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -386,6 +386,18 @@ class ExportTest extends TestCase $resp->assertSee("# Dogcat\n\n```JavaScript\nvar a = 'cat';\n```\n\nAnother line", false); } + public function test_page_markdown_export_handles_tasklist_checkboxes() + { + $page = Page::query()->first()->forceFill([ + 'markdown' => '', + 'html' => '
  • Item A
  • Item B
', + ]); + $page->save(); + + $resp = $this->asEditor()->get($page->getUrl('/export/markdown')); + $resp->assertSee("- [x] Item A\n- [ ] Item B", false); + } + public function test_chapter_markdown_export() { $chapter = Chapter::query()->first(); From 883e18f7c4fe474d688b08bbc7d95643f1680a57 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 23 Mar 2022 11:51:19 +0000 Subject: [PATCH 10/30] Updated tasklist style and functionality for cross-browser use - Updated styles to better align checkboxes within page content. - Updated functionality to use a cross-compatible property on checkbox click within the editor. --- resources/js/wysiwyg/plugins-tasklist.js | 3 ++- resources/sass/_pages.scss | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js index 2dd6528e1..5b0e1c1f0 100644 --- a/resources/js/wysiwyg/plugins-tasklist.js +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -84,9 +84,10 @@ function register(editor, url) { // Handle checkbox click in editor editor.on('click', function(event) { - const clickedEl = event.originalTarget; + const clickedEl = event.target; if (clickedEl.nodeName === 'LI' && clickedEl.classList.contains('task-list-item')) { handleTaskListItemClick(event, clickedEl, editor); + event.preventDefault(); } }); } diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss index 8103ca20d..73819975f 100755 --- a/resources/sass/_pages.scss +++ b/resources/sass/_pages.scss @@ -164,6 +164,11 @@ body.tox-fullscreen, body.markdown-fullscreen { clear: both; } + li > input[type="checkbox"] { + vertical-align: top; + margin-top: 0.3em; + } + p:empty { min-height: 1.6em; } From 95e496d16fbbb6164603f53edc1f0104febe7a79 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 23 Mar 2022 11:54:27 +0000 Subject: [PATCH 11/30] Added translation string for tasklist WYSIWYG action --- resources/lang/en/editor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/en/editor.php b/resources/lang/en/editor.php index 4fb1b8f2e..3daa03e7e 100644 --- a/resources/lang/en/editor.php +++ b/resources/lang/en/editor.php @@ -55,6 +55,7 @@ return [ 'align_justify' => 'Align justify', 'list_bullet' => 'Bullet list', 'list_numbered' => 'Numbered list', + 'list_task' => 'Task list', 'indent_increase' => 'Increase indent', 'indent_decrease' => 'Decrease indent', 'table' => 'Table', From 622adc5450d77ecf6edf4752a573c94d28779897 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 23 Mar 2022 11:57:20 +0000 Subject: [PATCH 12/30] Updated justify translation for editor Fixes #3342 --- resources/lang/en/editor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/en/editor.php b/resources/lang/en/editor.php index 4fb1b8f2e..a70a403f6 100644 --- a/resources/lang/en/editor.php +++ b/resources/lang/en/editor.php @@ -52,7 +52,7 @@ return [ 'align_left' => 'Align left', 'align_center' => 'Align center', 'align_right' => 'Align right', - 'align_justify' => 'Align justify', + 'align_justify' => 'Justify', 'list_bullet' => 'Bullet list', 'list_numbered' => 'Numbered list', 'indent_increase' => 'Increase indent', From 981807220c2165a3479c2e7734292da7f389e4b4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 23 Mar 2022 12:02:01 +0000 Subject: [PATCH 13/30] Applied StyleCI changes and updated dependancies --- .../Tools/Markdown/CheckboxConverter.php | 4 +- composer.lock | 519 +++++++++--------- package-lock.json | 372 +++++++------ package.json | 6 +- 4 files changed, 464 insertions(+), 437 deletions(-) diff --git a/app/Entities/Tools/Markdown/CheckboxConverter.php b/app/Entities/Tools/Markdown/CheckboxConverter.php index e4666d666..6d872330a 100644 --- a/app/Entities/Tools/Markdown/CheckboxConverter.php +++ b/app/Entities/Tools/Markdown/CheckboxConverter.php @@ -7,11 +7,11 @@ use League\HTMLToMarkdown\ElementInterface; class CheckboxConverter implements ConverterInterface { - public function convert(ElementInterface $element): string { if (strtolower($element->getAttribute('type')) === 'checkbox') { $isChecked = $element->getAttribute('checked') === 'checked'; + return $isChecked ? ' [x] ' : ' [ ] '; } @@ -25,4 +25,4 @@ class CheckboxConverter implements ConverterInterface { return ['input']; } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 2060278ee..4e5e3adab 100644 --- a/composer.lock +++ b/composer.lock @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.209.30", + "version": "3.215.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "54c1e491b8de74360bbe94d727706db384c8d9a8" + "reference": "8ddbc5242ec59a22b2c4704867e028343c6d8ade" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/54c1e491b8de74360bbe94d727706db384c8d9a8", - "reference": "54c1e491b8de74360bbe94d727706db384c8d9a8", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8ddbc5242ec59a22b2c4704867e028343c6d8ade", + "reference": "8ddbc5242ec59a22b2c4704867e028343c6d8ade", "shasum": "" }, "require": { @@ -143,22 +143,22 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.209.30" + "source": "https://github.com/aws/aws-sdk-php/tree/3.215.2" }, - "time": "2022-02-23T19:14:31+00:00" + "time": "2022-03-22T19:02:23+00:00" }, { "name": "bacon/bacon-qr-code", - "version": "2.0.6", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/Bacon/BaconQrCode.git", - "reference": "0069435e2a01a57193b25790f105a5d3168653c1" + "reference": "d70c840f68657ce49094b8d91f9ee0cc07fbf66c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/0069435e2a01a57193b25790f105a5d3168653c1", - "reference": "0069435e2a01a57193b25790f105a5d3168653c1", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/d70c840f68657ce49094b8d91f9ee0cc07fbf66c", + "reference": "d70c840f68657ce49094b8d91f9ee0cc07fbf66c", "shasum": "" }, "require": { @@ -197,9 +197,9 @@ "homepage": "https://github.com/Bacon/BaconQrCode", "support": { "issues": "https://github.com/Bacon/BaconQrCode/issues", - "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.6" + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.7" }, - "time": "2022-02-04T20:16:05+00:00" + "time": "2022-03-14T02:02:36+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -563,16 +563,16 @@ }, { "name": "doctrine/dbal", - "version": "3.3.2", + "version": "3.3.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "35eae239ef515d55ebb24e9d4715cad09a4f58ed" + "reference": "82331b861727c15b1f457ef05a8729e508e7ead5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/35eae239ef515d55ebb24e9d4715cad09a4f58ed", - "reference": "35eae239ef515d55ebb24e9d4715cad09a4f58ed", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/82331b861727c15b1f457ef05a8729e508e7ead5", + "reference": "82331b861727c15b1f457ef05a8729e508e7ead5", "shasum": "" }, "require": { @@ -587,14 +587,14 @@ "require-dev": { "doctrine/coding-standard": "9.0.0", "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.0", + "phpstan/phpstan": "1.4.6", "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "9.5.11", + "phpunit/phpunit": "9.5.16", "psalm/plugin-phpunit": "0.16.1", "squizlabs/php_codesniffer": "3.6.2", "symfony/cache": "^5.2|^6.0", "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", - "vimeo/psalm": "4.16.1" + "vimeo/psalm": "4.22.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -654,7 +654,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.3.2" + "source": "https://github.com/doctrine/dbal/tree/3.3.3" }, "funding": [ { @@ -670,7 +670,7 @@ "type": "tidelift" } ], - "time": "2022-02-05T16:33:45+00:00" + "time": "2022-03-09T15:39:50+00:00" }, { "name": "doctrine/deprecations", @@ -902,16 +902,16 @@ }, { "name": "doctrine/lexer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c" + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c", - "reference": "9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", "shasum": "" }, "require": { @@ -919,7 +919,7 @@ }, "require-dev": { "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "1.3", + "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "vimeo/psalm": "^4.11" }, @@ -958,7 +958,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.2" + "source": "https://github.com/doctrine/lexer/tree/1.2.3" }, "funding": [ { @@ -974,7 +974,7 @@ "type": "tidelift" } ], - "time": "2022-01-12T08:27:12+00:00" + "time": "2022-02-28T11:07:21+00:00" }, { "name": "dompdf/dompdf", @@ -1307,16 +1307,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.4.1", + "version": "7.4.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79" + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4", + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4", "shasum": "" }, "require": { @@ -1411,7 +1411,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.1" + "source": "https://github.com/guzzle/guzzle/tree/7.4.2" }, "funding": [ { @@ -1427,7 +1427,7 @@ "type": "tidelift" } ], - "time": "2021-12-06T18:43:05+00:00" + "time": "2022-03-20T14:16:28+00:00" }, { "name": "guzzlehttp/promises", @@ -1515,16 +1515,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.1.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72" + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72", - "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", "shasum": "" }, "require": { @@ -1548,7 +1548,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } }, "autoload": { @@ -1610,7 +1610,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.1.0" + "source": "https://github.com/guzzle/psr7/tree/2.2.1" }, "funding": [ { @@ -1626,7 +1626,7 @@ "type": "tidelift" } ], - "time": "2021-10-06T17:43:30+00:00" + "time": "2022-03-20T21:55:58+00:00" }, { "name": "intervention/image", @@ -1788,16 +1788,16 @@ }, { "name": "laravel/framework", - "version": "v8.83.2", + "version": "v8.83.5", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b91b3b5b39fbbdc763746f5714e08d50a4dd7857" + "reference": "33b1b981266e3a19fbc826b60c4a6847e311ac95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b91b3b5b39fbbdc763746f5714e08d50a4dd7857", - "reference": "b91b3b5b39fbbdc763746f5714e08d50a4dd7857", + "url": "https://api.github.com/repos/laravel/framework/zipball/33b1b981266e3a19fbc826b60c4a6847e311ac95", + "reference": "33b1b981266e3a19fbc826b60c4a6847e311ac95", "shasum": "" }, "require": { @@ -1957,7 +1957,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-02-22T15:10:17+00:00" + "time": "2022-03-15T13:37:44+00:00" }, { "name": "laravel/serializable-closure", @@ -2020,16 +2020,16 @@ }, { "name": "laravel/socialite", - "version": "v5.5.1", + "version": "v5.5.2", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "9b96dfd69e9c1de69c23205cb390550bc71c357e" + "reference": "68afb03259b82d898c68196cbcacd48596a9dd72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/9b96dfd69e9c1de69c23205cb390550bc71c357e", - "reference": "9b96dfd69e9c1de69c23205cb390550bc71c357e", + "url": "https://api.github.com/repos/laravel/socialite/zipball/68afb03259b82d898c68196cbcacd48596a9dd72", + "reference": "68afb03259b82d898c68196cbcacd48596a9dd72", "shasum": "" }, "require": { @@ -2085,20 +2085,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2022-02-07T16:08:19+00:00" + "time": "2022-03-10T15:26:19+00:00" }, { "name": "laravel/tinker", - "version": "v2.7.0", + "version": "v2.7.1", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "5f2f9815b7631b9f586a3de7933c25f9327d4073" + "reference": "1e2d500585a4e546346fadd3adc6f9c1a97e15f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/5f2f9815b7631b9f586a3de7933c25f9327d4073", - "reference": "5f2f9815b7631b9f586a3de7933c25f9327d4073", + "url": "https://api.github.com/repos/laravel/tinker/zipball/1e2d500585a4e546346fadd3adc6f9c1a97e15f4", + "reference": "1e2d500585a4e546346fadd3adc6f9c1a97e15f4", "shasum": "" }, "require": { @@ -2151,9 +2151,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.7.0" + "source": "https://github.com/laravel/tinker/tree/v2.7.1" }, - "time": "2022-01-10T08:52:49+00:00" + "time": "2022-03-15T15:25:01+00:00" }, { "name": "laravel/ui", @@ -2456,16 +2456,16 @@ }, { "name": "league/html-to-markdown", - "version": "5.0.2", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/thephpleague/html-to-markdown.git", - "reference": "4d0394e120dc14b0d5c52fd1755fd48656da2ec9" + "reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/4d0394e120dc14b0d5c52fd1755fd48656da2ec9", - "reference": "4d0394e120dc14b0d5c52fd1755fd48656da2ec9", + "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/e0fc8cf07bdabbcd3765341ecb50c34c271d64e1", + "reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1", "shasum": "" }, "require": { @@ -2475,11 +2475,11 @@ }, "require-dev": { "mikehaertl/php-shellcommand": "^1.1.0", - "phpstan/phpstan": "^0.12.82", + "phpstan/phpstan": "^0.12.99", "phpunit/phpunit": "^8.5 || ^9.2", "scrutinizer/ocular": "^1.6", "unleashedtech/php-coding-standard": "^2.7", - "vimeo/psalm": "^4.6" + "vimeo/psalm": "^4.22" }, "bin": [ "bin/html-to-markdown" @@ -2487,7 +2487,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-master": "5.2-dev" } }, "autoload": { @@ -2521,7 +2521,7 @@ ], "support": { "issues": "https://github.com/thephpleague/html-to-markdown/issues", - "source": "https://github.com/thephpleague/html-to-markdown/tree/5.0.2" + "source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.0" }, "funding": [ { @@ -2541,7 +2541,7 @@ "type": "tidelift" } ], - "time": "2021-11-06T05:38:26+00:00" + "time": "2022-03-02T17:24:08+00:00" }, { "name": "league/mime-type-detection", @@ -2747,16 +2747,16 @@ }, { "name": "monolog/monolog", - "version": "2.3.5", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd4380d6fc37626e2f799f29d91195040137eba9" + "reference": "d7fd7450628561ba697b7097d86db72662f54aef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd4380d6fc37626e2f799f29d91195040137eba9", - "reference": "fd4380d6fc37626e2f799f29d91195040137eba9", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d7fd7450628561ba697b7097d86db72662f54aef", + "reference": "d7fd7450628561ba697b7097d86db72662f54aef", "shasum": "" }, "require": { @@ -2778,7 +2778,7 @@ "phpstan/phpstan": "^0.12.91", "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", + "rollbar/rollbar": "^1.3 || ^2 || ^3", "ruflin/elastica": ">=0.90@dev", "swiftmailer/swiftmailer": "^5.3|^6.0" }, @@ -2830,7 +2830,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.3.5" + "source": "https://github.com/Seldaek/monolog/tree/2.4.0" }, "funding": [ { @@ -2842,7 +2842,7 @@ "type": "tidelift" } ], - "time": "2021-10-01T21:08:31+00:00" + "time": "2022-03-14T12:44:37+00:00" }, { "name": "mtdowling/jmespath.php", @@ -3141,12 +3141,12 @@ } }, "autoload": { - "psr-4": { - "Opis\\Closure\\": "src/" - }, "files": [ "functions.php" - ] + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3341,25 +3341,25 @@ }, { "name": "phenx/php-svg-lib", - "version": "0.4.0", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "3ffbbb037f0871c3a819e90cff8b36dd7e656189" + "reference": "4498b5df7b08e8469f0f8279651ea5de9626ed02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/3ffbbb037f0871c3a819e90cff8b36dd7e656189", - "reference": "3ffbbb037f0871c3a819e90cff8b36dd7e656189", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/4498b5df7b08e8469f0f8279651ea5de9626ed02", + "reference": "4498b5df7b08e8469f0f8279651ea5de9626ed02", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^7.4 || ^8.0", + "php": "^7.1 || ^7.2 || ^7.3 || ^7.4 || ^8.0", "sabberworm/php-css-parser": "^8.4" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" }, "type": "library", "autoload": { @@ -3381,9 +3381,9 @@ "homepage": "https://github.com/PhenX/php-svg-lib", "support": { "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/0.4.0" + "source": "https://github.com/dompdf/php-svg-lib/tree/0.4.1" }, - "time": "2021-12-17T14:08:35+00:00" + "time": "2022-03-07T12:52:04+00:00" }, { "name": "phpoption/phpoption", @@ -4095,16 +4095,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.1", + "version": "v0.11.2", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "570292577277f06f590635381a7f761a6cf4f026" + "reference": "7f7da640d68b9c9fec819caae7c744a213df6514" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/570292577277f06f590635381a7f761a6cf4f026", - "reference": "570292577277f06f590635381a7f761a6cf4f026", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/7f7da640d68b9c9fec819caae7c744a213df6514", + "reference": "7f7da640d68b9c9fec819caae7c744a213df6514", "shasum": "" }, "require": { @@ -4115,6 +4115,9 @@ "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, "require-dev": { "bamarni/composer-bin-plugin": "^1.2", "hoa/console": "3.17.05.02" @@ -4164,9 +4167,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.1" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.2" }, - "time": "2022-01-03T13:58:38+00:00" + "time": "2022-02-28T15:28:54+00:00" }, { "name": "ralouphie/getallheaders", @@ -4356,12 +4359,12 @@ } }, "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4642,16 +4645,16 @@ }, { "name": "socialiteproviders/microsoft-azure", - "version": "5.0.1", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Microsoft-Azure.git", - "reference": "9b23e02ff711de42e513aa55f768a4f1c67c0e41" + "reference": "7522b27cd8518706b50e03b40a396fb0a6891feb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Microsoft-Azure/zipball/9b23e02ff711de42e513aa55f768a4f1c67c0e41", - "reference": "9b23e02ff711de42e513aa55f768a4f1c67c0e41", + "url": "https://api.github.com/repos/SocialiteProviders/Microsoft-Azure/zipball/7522b27cd8518706b50e03b40a396fb0a6891feb", + "reference": "7522b27cd8518706b50e03b40a396fb0a6891feb", "shasum": "" }, "require": { @@ -4689,20 +4692,20 @@ "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2021-10-07T22:21:59+00:00" + "time": "2022-03-15T21:17:43+00:00" }, { "name": "socialiteproviders/okta", - "version": "4.2.0", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Okta.git", - "reference": "768df935e2da6add4812bb5b14628e9ffcb16be1" + "reference": "7c0b7522423943131f680e74123b71ccd3989541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/768df935e2da6add4812bb5b14628e9ffcb16be1", - "reference": "768df935e2da6add4812bb5b14628e9ffcb16be1", + "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/7c0b7522423943131f680e74123b71ccd3989541", + "reference": "7c0b7522423943131f680e74123b71ccd3989541", "shasum": "" }, "require": { @@ -4739,7 +4742,7 @@ "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2022-02-16T23:55:01+00:00" + "time": "2022-03-14T23:25:14+00:00" }, { "name": "socialiteproviders/slack", @@ -4954,16 +4957,16 @@ }, { "name": "symfony/console", - "version": "v5.4.3", + "version": "v5.4.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a2a86ec353d825c75856c6fd14fac416a7bdb6b8" + "reference": "d8111acc99876953f52fe16d4c50eb60940d49ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a2a86ec353d825c75856c6fd14fac416a7bdb6b8", - "reference": "a2a86ec353d825c75856c6fd14fac416a7bdb6b8", + "url": "https://api.github.com/repos/symfony/console/zipball/d8111acc99876953f52fe16d4c50eb60940d49ad", + "reference": "d8111acc99876953f52fe16d4c50eb60940d49ad", "shasum": "" }, "require": { @@ -5033,7 +5036,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.3" + "source": "https://github.com/symfony/console/tree/v5.4.5" }, "funding": [ { @@ -5049,7 +5052,7 @@ "type": "tidelift" } ], - "time": "2022-01-26T16:28:35+00:00" + "time": "2022-02-24T12:45:35+00:00" }, { "name": "symfony/css-selector", @@ -5484,16 +5487,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.3", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ef409ff341a565a3663157d4324536746d49a0c7" + "reference": "34e89bc147633c0f9dd6caaaf56da3b806a21465" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ef409ff341a565a3663157d4324536746d49a0c7", - "reference": "ef409ff341a565a3663157d4324536746d49a0c7", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/34e89bc147633c0f9dd6caaaf56da3b806a21465", + "reference": "34e89bc147633c0f9dd6caaaf56da3b806a21465", "shasum": "" }, "require": { @@ -5537,7 +5540,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.3" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.6" }, "funding": [ { @@ -5553,20 +5556,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-03-05T21:03:43+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.4", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "49f40347228c773688a0488feea0175aa7f4d268" + "reference": "d41f29ae9af1b5f40c7ebcddf09082953229411d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/49f40347228c773688a0488feea0175aa7f4d268", - "reference": "49f40347228c773688a0488feea0175aa7f4d268", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d41f29ae9af1b5f40c7ebcddf09082953229411d", + "reference": "d41f29ae9af1b5f40c7ebcddf09082953229411d", "shasum": "" }, "require": { @@ -5649,7 +5652,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.4" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.6" }, "funding": [ { @@ -5665,7 +5668,7 @@ "type": "tidelift" } ], - "time": "2022-01-29T18:08:07+00:00" + "time": "2022-03-05T21:14:51+00:00" }, { "name": "symfony/mime", @@ -5752,7 +5755,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -5784,12 +5787,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5814,7 +5817,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" }, "funding": [ { @@ -5834,7 +5837,7 @@ }, { "name": "symfony/polyfill-iconv", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", @@ -5897,7 +5900,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.25.0" }, "funding": [ { @@ -5917,7 +5920,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -5978,7 +5981,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" }, "funding": [ { @@ -5998,7 +6001,7 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -6065,7 +6068,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0" }, "funding": [ { @@ -6085,7 +6088,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -6149,7 +6152,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" }, "funding": [ { @@ -6169,7 +6172,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -6232,7 +6235,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" }, "funding": [ { @@ -6252,7 +6255,7 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", @@ -6308,7 +6311,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" }, "funding": [ { @@ -6328,7 +6331,7 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -6387,7 +6390,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0" }, "funding": [ { @@ -6407,16 +6410,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9" + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/57b712b08eddb97c762a8caa32c84e037892d2e9", - "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", "shasum": "" }, "require": { @@ -6470,7 +6473,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" }, "funding": [ { @@ -6486,11 +6489,11 @@ "type": "tidelift" } ], - "time": "2021-09-13T13:58:33+00:00" + "time": "2022-03-04T08:16:47+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -6549,7 +6552,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" }, "funding": [ { @@ -6569,16 +6572,16 @@ }, { "name": "symfony/process", - "version": "v5.4.3", + "version": "v5.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "553f50487389a977eb31cf6b37faae56da00f753" + "reference": "95440409896f90a5f85db07a32b517ecec17fa4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/553f50487389a977eb31cf6b37faae56da00f753", - "reference": "553f50487389a977eb31cf6b37faae56da00f753", + "url": "https://api.github.com/repos/symfony/process/zipball/95440409896f90a5f85db07a32b517ecec17fa4c", + "reference": "95440409896f90a5f85db07a32b517ecec17fa4c", "shasum": "" }, "require": { @@ -6611,7 +6614,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.3" + "source": "https://github.com/symfony/process/tree/v5.4.5" }, "funding": [ { @@ -6627,7 +6630,7 @@ "type": "tidelift" } ], - "time": "2022-01-26T16:28:35+00:00" + "time": "2022-01-30T18:16:22+00:00" }, { "name": "symfony/routing", @@ -6835,12 +6838,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -6890,16 +6893,16 @@ }, { "name": "symfony/translation", - "version": "v5.4.3", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a9dd7403232c61e87e27fb306bbcd1627f245d70" + "reference": "a7ca9fdfffb0174209440c2ffa1dee228e15d95b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a9dd7403232c61e87e27fb306bbcd1627f245d70", - "reference": "a9dd7403232c61e87e27fb306bbcd1627f245d70", + "url": "https://api.github.com/repos/symfony/translation/zipball/a7ca9fdfffb0174209440c2ffa1dee228e15d95b", + "reference": "a7ca9fdfffb0174209440c2ffa1dee228e15d95b", "shasum": "" }, "require": { @@ -6967,7 +6970,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.3" + "source": "https://github.com/symfony/translation/tree/v5.4.6" }, "funding": [ { @@ -6983,7 +6986,7 @@ "type": "tidelift" } ], - "time": "2022-01-07T00:28:17+00:00" + "time": "2022-03-02T12:56:28+00:00" }, { "name": "symfony/translation-contracts", @@ -7065,16 +7068,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.3", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "970a01f208bf895c5f327ba40b72288da43adec4" + "reference": "294e9da6e2e0dd404e983daa5aa74253d92c05d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/970a01f208bf895c5f327ba40b72288da43adec4", - "reference": "970a01f208bf895c5f327ba40b72288da43adec4", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/294e9da6e2e0dd404e983daa5aa74253d92c05d0", + "reference": "294e9da6e2e0dd404e983daa5aa74253d92c05d0", "shasum": "" }, "require": { @@ -7134,7 +7137,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.3" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.6" }, "funding": [ { @@ -7150,7 +7153,7 @@ "type": "tidelift" } ], - "time": "2022-01-17T16:30:37+00:00" + "time": "2022-03-02T12:42:23+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7497,16 +7500,16 @@ }, { "name": "composer/composer", - "version": "2.2.6", + "version": "2.2.9", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "ce785a18c0fb472421e52d958bab339247cb0e82" + "reference": "07eccf080ad63d55d95a7c9133506db7d9029264" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/ce785a18c0fb472421e52d958bab339247cb0e82", - "reference": "ce785a18c0fb472421e52d958bab339247cb0e82", + "url": "https://api.github.com/repos/composer/composer/zipball/07eccf080ad63d55d95a7c9133506db7d9029264", + "reference": "07eccf080ad63d55d95a7c9133506db7d9029264", "shasum": "" }, "require": { @@ -7515,7 +7518,7 @@ "composer/pcre": "^1.0", "composer/semver": "^3.0", "composer/spdx-licenses": "^1.2", - "composer/xdebug-handler": "^2.0", + "composer/xdebug-handler": "^2.0 || ^3.0", "justinrainbow/json-schema": "^5.2.11", "php": "^5.3.2 || ^7.0 || ^8.0", "psr/log": "^1.0 || ^2.0", @@ -7576,7 +7579,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", - "source": "https://github.com/composer/composer/tree/2.2.6" + "source": "https://github.com/composer/composer/tree/2.2.9" }, "funding": [ { @@ -7592,7 +7595,7 @@ "type": "tidelift" } ], - "time": "2022-02-04T16:00:38+00:00" + "time": "2022-03-15T21:13:37+00:00" }, { "name": "composer/metadata-minifier", @@ -7736,16 +7739,16 @@ }, { "name": "composer/semver", - "version": "3.2.9", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "a951f614bd64dcd26137bc9b7b2637ddcfc57649" + "reference": "5d8e574bb0e69188786b8ef77d43341222a41a71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/a951f614bd64dcd26137bc9b7b2637ddcfc57649", - "reference": "a951f614bd64dcd26137bc9b7b2637ddcfc57649", + "url": "https://api.github.com/repos/composer/semver/zipball/5d8e574bb0e69188786b8ef77d43341222a41a71", + "reference": "5d8e574bb0e69188786b8ef77d43341222a41a71", "shasum": "" }, "require": { @@ -7797,7 +7800,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.9" + "source": "https://github.com/composer/semver/tree/3.3.1" }, "funding": [ { @@ -7813,7 +7816,7 @@ "type": "tidelift" } ], - "time": "2022-02-04T13:58:43+00:00" + "time": "2022-03-16T11:22:07+00:00" }, { "name": "composer/spdx-licenses", @@ -7897,27 +7900,27 @@ }, { "name": "composer/xdebug-handler", - "version": "2.0.4", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "0c1a3925ec58a4ec98e992b9c7d171e9e184be0a" + "reference": "ced299686f41dce890debac69273b47ffe98a40c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/0c1a3925ec58a4ec98e992b9c7d171e9e184be0a", - "reference": "0c1a3925ec58a4ec98e992b9c7d171e9e184be0a", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", "shasum": "" }, "require": { - "composer/pcre": "^1", - "php": "^5.3.2 || ^7.0 || ^8.0", + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" + "symfony/phpunit-bridge": "^6.0" }, "type": "library", "autoload": { @@ -7943,7 +7946,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.4" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" }, "funding": [ { @@ -7959,33 +7962,34 @@ "type": "tidelift" } ], - "time": "2022-01-04T17:06:45+00:00" + "time": "2022-02-25T21:32:43+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { @@ -8012,7 +8016,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" }, "funding": [ { @@ -8028,7 +8032,7 @@ "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2022-03-03T08:28:38+00:00" }, { "name": "facade/ignition-contracts", @@ -8413,25 +8417,29 @@ }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { @@ -8456,7 +8464,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" }, "funding": [ { @@ -8464,7 +8472,7 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { "name": "nunomaduro/collision", @@ -8991,16 +8999,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.4.6", + "version": "1.4.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8a7761f1c520e0dad6e04d862fdc697445457cfe" + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8a7761f1c520e0dad6e04d862fdc697445457cfe", - "reference": "8a7761f1c520e0dad6e04d862fdc697445457cfe", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72", + "reference": "898c479c39caa727bedf4311dd294a8f4e250e72", "shasum": "" }, "require": { @@ -9014,11 +9022,6 @@ "phpstan.phar" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, "autoload": { "files": [ "bootstrap.php" @@ -9031,7 +9034,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.4.6" + "source": "https://github.com/phpstan/phpstan/tree/1.4.10" }, "funding": [ { @@ -9051,20 +9054,20 @@ "type": "tidelift" } ], - "time": "2022-02-06T12:56:13+00:00" + "time": "2022-03-14T10:25:45+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.13", + "version": "9.2.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "deac8540cb7bd40b2b8cfa679b76202834fd04e8" + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/deac8540cb7bd40b2b8cfa679b76202834fd04e8", - "reference": "deac8540cb7bd40b2b8cfa679b76202834fd04e8", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "shasum": "" }, "require": { @@ -9120,7 +9123,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.13" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" }, "funding": [ { @@ -9128,7 +9131,7 @@ "type": "github" } ], - "time": "2022-02-23T17:02:38+00:00" + "time": "2022-03-07T09:28:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9373,16 +9376,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.16", + "version": "9.5.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5ff8c545a50226c569310a35f4fa89d79f1ddfdc" + "reference": "35ea4b7f3acabb26f4bb640f8c30866c401da807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5ff8c545a50226c569310a35f4fa89d79f1ddfdc", - "reference": "5ff8c545a50226c569310a35f4fa89d79f1ddfdc", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35ea4b7f3acabb26f4bb640f8c30866c401da807", + "reference": "35ea4b7f3acabb26f4bb640f8c30866c401da807", "shasum": "" }, "require": { @@ -9412,7 +9415,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.4", + "sebastian/type": "^3.0", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -9460,7 +9463,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.16" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.19" }, "funding": [ { @@ -9472,7 +9475,7 @@ "type": "github" } ], - "time": "2022-02-23T17:10:58+00:00" + "time": "2022-03-15T09:57:31+00:00" }, { "name": "react/promise", @@ -10407,28 +10410,28 @@ }, { "name": "sebastian/type", - "version": "2.3.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -10451,7 +10454,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" }, "funding": [ { @@ -10459,7 +10462,7 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2022-03-15T09:54:48+00:00" }, { "name": "sebastian/version", @@ -10627,16 +10630,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.3", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "2634381fdf27a2a0a8ac8eb404025eb656c65d0c" + "reference": "c0bda97480d96337bd3866026159a8b358665457" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2634381fdf27a2a0a8ac8eb404025eb656c65d0c", - "reference": "2634381fdf27a2a0a8ac8eb404025eb656c65d0c", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c0bda97480d96337bd3866026159a8b358665457", + "reference": "c0bda97480d96337bd3866026159a8b358665457", "shasum": "" }, "require": { @@ -10682,7 +10685,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.3" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.6" }, "funding": [ { @@ -10698,20 +10701,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-03-02T12:42:23+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.3", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "0f0c4bf1840420f4aef3f32044a9dbb24682731b" + "reference": "d53a45039974952af7f7ebc461ccdd4295e29440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/0f0c4bf1840420f4aef3f32044a9dbb24682731b", - "reference": "0f0c4bf1840420f4aef3f32044a9dbb24682731b", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d53a45039974952af7f7ebc461ccdd4295e29440", + "reference": "d53a45039974952af7f7ebc461ccdd4295e29440", "shasum": "" }, "require": { @@ -10746,7 +10749,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.3" + "source": "https://github.com/symfony/filesystem/tree/v5.4.6" }, "funding": [ { @@ -10762,7 +10765,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-03-02T12:42:23+00:00" }, { "name": "theseer/tokenizer", diff --git a/package-lock.json b/package-lock.json index 851ceb20a..dd4f0228d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,22 +4,21 @@ "requires": true, "packages": { "": { - "name": "bookstack", "dependencies": { "clipboard": "^2.0.10", "codemirror": "^5.65.2", "dropzone": "^5.9.3", "markdown-it": "^12.3.2", "markdown-it-task-lists": "^2.1.1", - "sortablejs": "^1.14.0" + "sortablejs": "^1.15.0" }, "devDependencies": { "chokidar-cli": "^3.0", - "esbuild": "0.14.23", + "esbuild": "0.14.27", "livereload": "^0.9.3", "npm-run-all": "^4.1.5", "punycode": "^2.1.1", - "sass": "^1.49.8" + "sass": "^1.49.9" } }, "node_modules/ansi-regex": { @@ -342,9 +341,9 @@ } }, "node_modules/esbuild": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.23.tgz", - "integrity": "sha512-XjnIcZ9KB6lfonCa+jRguXyRYcldmkyZ99ieDksqW/C8bnyEX299yA4QH2XcgijCgaddEZePPTgvx/2imsq7Ig==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.27.tgz", + "integrity": "sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==", "dev": true, "hasInstallScript": true, "bin": { @@ -354,31 +353,48 @@ "node": ">=12" }, "optionalDependencies": { - "esbuild-android-arm64": "0.14.23", - "esbuild-darwin-64": "0.14.23", - "esbuild-darwin-arm64": "0.14.23", - "esbuild-freebsd-64": "0.14.23", - "esbuild-freebsd-arm64": "0.14.23", - "esbuild-linux-32": "0.14.23", - "esbuild-linux-64": "0.14.23", - "esbuild-linux-arm": "0.14.23", - "esbuild-linux-arm64": "0.14.23", - "esbuild-linux-mips64le": "0.14.23", - "esbuild-linux-ppc64le": "0.14.23", - "esbuild-linux-riscv64": "0.14.23", - "esbuild-linux-s390x": "0.14.23", - "esbuild-netbsd-64": "0.14.23", - "esbuild-openbsd-64": "0.14.23", - "esbuild-sunos-64": "0.14.23", - "esbuild-windows-32": "0.14.23", - "esbuild-windows-64": "0.14.23", - "esbuild-windows-arm64": "0.14.23" + "esbuild-android-64": "0.14.27", + "esbuild-android-arm64": "0.14.27", + "esbuild-darwin-64": "0.14.27", + "esbuild-darwin-arm64": "0.14.27", + "esbuild-freebsd-64": "0.14.27", + "esbuild-freebsd-arm64": "0.14.27", + "esbuild-linux-32": "0.14.27", + "esbuild-linux-64": "0.14.27", + "esbuild-linux-arm": "0.14.27", + "esbuild-linux-arm64": "0.14.27", + "esbuild-linux-mips64le": "0.14.27", + "esbuild-linux-ppc64le": "0.14.27", + "esbuild-linux-riscv64": "0.14.27", + "esbuild-linux-s390x": "0.14.27", + "esbuild-netbsd-64": "0.14.27", + "esbuild-openbsd-64": "0.14.27", + "esbuild-sunos-64": "0.14.27", + "esbuild-windows-32": "0.14.27", + "esbuild-windows-64": "0.14.27", + "esbuild-windows-arm64": "0.14.27" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz", + "integrity": "sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, "node_modules/esbuild-android-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.23.tgz", - "integrity": "sha512-k9sXem++mINrZty1v4FVt6nC5BQCFG4K2geCIUUqHNlTdFnuvcqsY7prcKZLFhqVC1rbcJAr9VSUGFL/vD4vsw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz", + "integrity": "sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==", "cpu": [ "arm64" ], @@ -392,9 +408,9 @@ } }, "node_modules/esbuild-darwin-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.23.tgz", - "integrity": "sha512-lB0XRbtOYYL1tLcYw8BoBaYsFYiR48RPrA0KfA/7RFTr4MV7Bwy/J4+7nLsVnv9FGuQummM3uJ93J3ptaTqFug==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz", + "integrity": "sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==", "cpu": [ "x64" ], @@ -408,9 +424,9 @@ } }, "node_modules/esbuild-darwin-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.23.tgz", - "integrity": "sha512-yat73Z/uJ5tRcfRiI4CCTv0FSnwErm3BJQeZAh+1tIP0TUNh6o+mXg338Zl5EKChD+YGp6PN+Dbhs7qa34RxSw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz", + "integrity": "sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==", "cpu": [ "arm64" ], @@ -424,9 +440,9 @@ } }, "node_modules/esbuild-freebsd-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.23.tgz", - "integrity": "sha512-/1xiTjoLuQ+LlbfjJdKkX45qK/M7ARrbLmyf7x3JhyQGMjcxRYVR6Dw81uH3qlMHwT4cfLW4aEVBhP1aNV7VsA==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz", + "integrity": "sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==", "cpu": [ "x64" ], @@ -440,9 +456,9 @@ } }, "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.23.tgz", - "integrity": "sha512-uyPqBU/Zcp6yEAZS4LKj5jEE0q2s4HmlMBIPzbW6cTunZ8cyvjG6YWpIZXb1KK3KTJDe62ltCrk3VzmWHp+iLg==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz", + "integrity": "sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==", "cpu": [ "arm64" ], @@ -456,9 +472,9 @@ } }, "node_modules/esbuild-linux-32": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.23.tgz", - "integrity": "sha512-37R/WMkQyUfNhbH7aJrr1uCjDVdnPeTHGeDhZPUNhfoHV0lQuZNCKuNnDvlH/u/nwIYZNdVvz1Igv5rY/zfrzQ==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz", + "integrity": "sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==", "cpu": [ "ia32" ], @@ -472,9 +488,9 @@ } }, "node_modules/esbuild-linux-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.23.tgz", - "integrity": "sha512-H0gztDP60qqr8zoFhAO64waoN5yBXkmYCElFklpd6LPoobtNGNnDe99xOQm28+fuD75YJ7GKHzp/MLCLhw2+vQ==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz", + "integrity": "sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==", "cpu": [ "x64" ], @@ -488,9 +504,9 @@ } }, "node_modules/esbuild-linux-arm": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.23.tgz", - "integrity": "sha512-x64CEUxi8+EzOAIpCUeuni0bZfzPw/65r8tC5cy5zOq9dY7ysOi5EVQHnzaxS+1NmV+/RVRpmrzGw1QgY2Xpmw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz", + "integrity": "sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==", "cpu": [ "arm" ], @@ -504,9 +520,9 @@ } }, "node_modules/esbuild-linux-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.23.tgz", - "integrity": "sha512-c4MLOIByNHR55n3KoYf9hYDfBRghMjOiHLaoYLhkQkIabb452RWi+HsNgB41sUpSlOAqfpqKPFNg7VrxL3UX9g==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz", + "integrity": "sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==", "cpu": [ "arm64" ], @@ -520,9 +536,9 @@ } }, "node_modules/esbuild-linux-mips64le": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.23.tgz", - "integrity": "sha512-kHKyKRIAedYhKug2EJpyJxOUj3VYuamOVA1pY7EimoFPzaF3NeY7e4cFBAISC/Av0/tiV0xlFCt9q0HJ68IBIw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz", + "integrity": "sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==", "cpu": [ "mips64el" ], @@ -536,9 +552,9 @@ } }, "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.23.tgz", - "integrity": "sha512-7ilAiJEPuJJnJp/LiDO0oJm5ygbBPzhchJJh9HsHZzeqO+3PUzItXi+8PuicY08r0AaaOe25LA7sGJ0MzbfBag==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz", + "integrity": "sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==", "cpu": [ "ppc64" ], @@ -552,9 +568,9 @@ } }, "node_modules/esbuild-linux-riscv64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.23.tgz", - "integrity": "sha512-fbL3ggK2wY0D8I5raPIMPhpCvODFE+Bhb5QGtNP3r5aUsRR6TQV+ZBXIaw84iyvKC8vlXiA4fWLGhghAd/h/Zg==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz", + "integrity": "sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==", "cpu": [ "riscv64" ], @@ -568,9 +584,9 @@ } }, "node_modules/esbuild-linux-s390x": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.23.tgz", - "integrity": "sha512-GHMDCyfy7+FaNSO8RJ8KCFsnax8fLUsOrj9q5Gi2JmZMY0Zhp75keb5abTFCq2/Oy6KVcT0Dcbyo/bFb4rIFJA==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz", + "integrity": "sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==", "cpu": [ "s390x" ], @@ -584,9 +600,9 @@ } }, "node_modules/esbuild-netbsd-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.23.tgz", - "integrity": "sha512-ovk2EX+3rrO1M2lowJfgMb/JPN1VwVYrx0QPUyudxkxLYrWeBxDKQvc6ffO+kB4QlDyTfdtAURrVzu3JeNdA2g==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz", + "integrity": "sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==", "cpu": [ "x64" ], @@ -600,9 +616,9 @@ } }, "node_modules/esbuild-openbsd-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.23.tgz", - "integrity": "sha512-uYYNqbVR+i7k8ojP/oIROAHO9lATLN7H2QeXKt2H310Fc8FJj4y3Wce6hx0VgnJ4k1JDrgbbiXM8rbEgQyg8KA==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz", + "integrity": "sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==", "cpu": [ "x64" ], @@ -616,9 +632,9 @@ } }, "node_modules/esbuild-sunos-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.23.tgz", - "integrity": "sha512-hAzeBeET0+SbScknPzS2LBY6FVDpgE+CsHSpe6CEoR51PApdn2IB0SyJX7vGelXzlyrnorM4CAsRyb9Qev4h9g==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz", + "integrity": "sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==", "cpu": [ "x64" ], @@ -632,9 +648,9 @@ } }, "node_modules/esbuild-windows-32": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.23.tgz", - "integrity": "sha512-Kttmi3JnohdaREbk6o9e25kieJR379TsEWF0l39PQVHXq3FR6sFKtVPgY8wk055o6IB+rllrzLnbqOw/UV60EA==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz", + "integrity": "sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==", "cpu": [ "ia32" ], @@ -648,9 +664,9 @@ } }, "node_modules/esbuild-windows-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.23.tgz", - "integrity": "sha512-JtIT0t8ymkpl6YlmOl6zoSWL5cnCgyLaBdf/SiU/Eg3C13r0NbHZWNT/RDEMKK91Y6t79kTs3vyRcNZbfu5a8g==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz", + "integrity": "sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==", "cpu": [ "x64" ], @@ -664,9 +680,9 @@ } }, "node_modules/esbuild-windows-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.23.tgz", - "integrity": "sha512-cTFaQqT2+ik9e4hePvYtRZQ3pqOvKDVNarzql0VFIzhc0tru/ZgdLoXd6epLiKT+SzoSce6V9YJ+nn6RCn6SHw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz", + "integrity": "sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==", "cpu": [ "arm64" ], @@ -1504,9 +1520,9 @@ } }, "node_modules/sass": { - "version": "1.49.8", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz", - "integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==", + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -1582,9 +1598,9 @@ } }, "node_modules/sortablejs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", - "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==" }, "node_modules/source-map-js": { "version": "1.0.2", @@ -2130,162 +2146,170 @@ } }, "esbuild": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.23.tgz", - "integrity": "sha512-XjnIcZ9KB6lfonCa+jRguXyRYcldmkyZ99ieDksqW/C8bnyEX299yA4QH2XcgijCgaddEZePPTgvx/2imsq7Ig==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.27.tgz", + "integrity": "sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==", "dev": true, "requires": { - "esbuild-android-arm64": "0.14.23", - "esbuild-darwin-64": "0.14.23", - "esbuild-darwin-arm64": "0.14.23", - "esbuild-freebsd-64": "0.14.23", - "esbuild-freebsd-arm64": "0.14.23", - "esbuild-linux-32": "0.14.23", - "esbuild-linux-64": "0.14.23", - "esbuild-linux-arm": "0.14.23", - "esbuild-linux-arm64": "0.14.23", - "esbuild-linux-mips64le": "0.14.23", - "esbuild-linux-ppc64le": "0.14.23", - "esbuild-linux-riscv64": "0.14.23", - "esbuild-linux-s390x": "0.14.23", - "esbuild-netbsd-64": "0.14.23", - "esbuild-openbsd-64": "0.14.23", - "esbuild-sunos-64": "0.14.23", - "esbuild-windows-32": "0.14.23", - "esbuild-windows-64": "0.14.23", - "esbuild-windows-arm64": "0.14.23" + "esbuild-android-64": "0.14.27", + "esbuild-android-arm64": "0.14.27", + "esbuild-darwin-64": "0.14.27", + "esbuild-darwin-arm64": "0.14.27", + "esbuild-freebsd-64": "0.14.27", + "esbuild-freebsd-arm64": "0.14.27", + "esbuild-linux-32": "0.14.27", + "esbuild-linux-64": "0.14.27", + "esbuild-linux-arm": "0.14.27", + "esbuild-linux-arm64": "0.14.27", + "esbuild-linux-mips64le": "0.14.27", + "esbuild-linux-ppc64le": "0.14.27", + "esbuild-linux-riscv64": "0.14.27", + "esbuild-linux-s390x": "0.14.27", + "esbuild-netbsd-64": "0.14.27", + "esbuild-openbsd-64": "0.14.27", + "esbuild-sunos-64": "0.14.27", + "esbuild-windows-32": "0.14.27", + "esbuild-windows-64": "0.14.27", + "esbuild-windows-arm64": "0.14.27" } }, + "esbuild-android-64": { + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz", + "integrity": "sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==", + "dev": true, + "optional": true + }, "esbuild-android-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.23.tgz", - "integrity": "sha512-k9sXem++mINrZty1v4FVt6nC5BQCFG4K2geCIUUqHNlTdFnuvcqsY7prcKZLFhqVC1rbcJAr9VSUGFL/vD4vsw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz", + "integrity": "sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==", "dev": true, "optional": true }, "esbuild-darwin-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.23.tgz", - "integrity": "sha512-lB0XRbtOYYL1tLcYw8BoBaYsFYiR48RPrA0KfA/7RFTr4MV7Bwy/J4+7nLsVnv9FGuQummM3uJ93J3ptaTqFug==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz", + "integrity": "sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==", "dev": true, "optional": true }, "esbuild-darwin-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.23.tgz", - "integrity": "sha512-yat73Z/uJ5tRcfRiI4CCTv0FSnwErm3BJQeZAh+1tIP0TUNh6o+mXg338Zl5EKChD+YGp6PN+Dbhs7qa34RxSw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz", + "integrity": "sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==", "dev": true, "optional": true }, "esbuild-freebsd-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.23.tgz", - "integrity": "sha512-/1xiTjoLuQ+LlbfjJdKkX45qK/M7ARrbLmyf7x3JhyQGMjcxRYVR6Dw81uH3qlMHwT4cfLW4aEVBhP1aNV7VsA==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz", + "integrity": "sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==", "dev": true, "optional": true }, "esbuild-freebsd-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.23.tgz", - "integrity": "sha512-uyPqBU/Zcp6yEAZS4LKj5jEE0q2s4HmlMBIPzbW6cTunZ8cyvjG6YWpIZXb1KK3KTJDe62ltCrk3VzmWHp+iLg==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz", + "integrity": "sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==", "dev": true, "optional": true }, "esbuild-linux-32": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.23.tgz", - "integrity": "sha512-37R/WMkQyUfNhbH7aJrr1uCjDVdnPeTHGeDhZPUNhfoHV0lQuZNCKuNnDvlH/u/nwIYZNdVvz1Igv5rY/zfrzQ==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz", + "integrity": "sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==", "dev": true, "optional": true }, "esbuild-linux-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.23.tgz", - "integrity": "sha512-H0gztDP60qqr8zoFhAO64waoN5yBXkmYCElFklpd6LPoobtNGNnDe99xOQm28+fuD75YJ7GKHzp/MLCLhw2+vQ==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz", + "integrity": "sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==", "dev": true, "optional": true }, "esbuild-linux-arm": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.23.tgz", - "integrity": "sha512-x64CEUxi8+EzOAIpCUeuni0bZfzPw/65r8tC5cy5zOq9dY7ysOi5EVQHnzaxS+1NmV+/RVRpmrzGw1QgY2Xpmw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz", + "integrity": "sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==", "dev": true, "optional": true }, "esbuild-linux-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.23.tgz", - "integrity": "sha512-c4MLOIByNHR55n3KoYf9hYDfBRghMjOiHLaoYLhkQkIabb452RWi+HsNgB41sUpSlOAqfpqKPFNg7VrxL3UX9g==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz", + "integrity": "sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==", "dev": true, "optional": true }, "esbuild-linux-mips64le": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.23.tgz", - "integrity": "sha512-kHKyKRIAedYhKug2EJpyJxOUj3VYuamOVA1pY7EimoFPzaF3NeY7e4cFBAISC/Av0/tiV0xlFCt9q0HJ68IBIw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz", + "integrity": "sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==", "dev": true, "optional": true }, "esbuild-linux-ppc64le": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.23.tgz", - "integrity": "sha512-7ilAiJEPuJJnJp/LiDO0oJm5ygbBPzhchJJh9HsHZzeqO+3PUzItXi+8PuicY08r0AaaOe25LA7sGJ0MzbfBag==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz", + "integrity": "sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==", "dev": true, "optional": true }, "esbuild-linux-riscv64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.23.tgz", - "integrity": "sha512-fbL3ggK2wY0D8I5raPIMPhpCvODFE+Bhb5QGtNP3r5aUsRR6TQV+ZBXIaw84iyvKC8vlXiA4fWLGhghAd/h/Zg==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz", + "integrity": "sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==", "dev": true, "optional": true }, "esbuild-linux-s390x": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.23.tgz", - "integrity": "sha512-GHMDCyfy7+FaNSO8RJ8KCFsnax8fLUsOrj9q5Gi2JmZMY0Zhp75keb5abTFCq2/Oy6KVcT0Dcbyo/bFb4rIFJA==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz", + "integrity": "sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==", "dev": true, "optional": true }, "esbuild-netbsd-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.23.tgz", - "integrity": "sha512-ovk2EX+3rrO1M2lowJfgMb/JPN1VwVYrx0QPUyudxkxLYrWeBxDKQvc6ffO+kB4QlDyTfdtAURrVzu3JeNdA2g==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz", + "integrity": "sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==", "dev": true, "optional": true }, "esbuild-openbsd-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.23.tgz", - "integrity": "sha512-uYYNqbVR+i7k8ojP/oIROAHO9lATLN7H2QeXKt2H310Fc8FJj4y3Wce6hx0VgnJ4k1JDrgbbiXM8rbEgQyg8KA==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz", + "integrity": "sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==", "dev": true, "optional": true }, "esbuild-sunos-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.23.tgz", - "integrity": "sha512-hAzeBeET0+SbScknPzS2LBY6FVDpgE+CsHSpe6CEoR51PApdn2IB0SyJX7vGelXzlyrnorM4CAsRyb9Qev4h9g==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz", + "integrity": "sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==", "dev": true, "optional": true }, "esbuild-windows-32": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.23.tgz", - "integrity": "sha512-Kttmi3JnohdaREbk6o9e25kieJR379TsEWF0l39PQVHXq3FR6sFKtVPgY8wk055o6IB+rllrzLnbqOw/UV60EA==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz", + "integrity": "sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==", "dev": true, "optional": true }, "esbuild-windows-64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.23.tgz", - "integrity": "sha512-JtIT0t8ymkpl6YlmOl6zoSWL5cnCgyLaBdf/SiU/Eg3C13r0NbHZWNT/RDEMKK91Y6t79kTs3vyRcNZbfu5a8g==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz", + "integrity": "sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==", "dev": true, "optional": true }, "esbuild-windows-arm64": { - "version": "0.14.23", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.23.tgz", - "integrity": "sha512-cTFaQqT2+ik9e4hePvYtRZQ3pqOvKDVNarzql0VFIzhc0tru/ZgdLoXd6epLiKT+SzoSce6V9YJ+nn6RCn6SHw==", + "version": "0.14.27", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz", + "integrity": "sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==", "dev": true, "optional": true }, @@ -2886,9 +2910,9 @@ } }, "sass": { - "version": "1.49.8", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz", - "integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==", + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -2946,9 +2970,9 @@ } }, "sortablejs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", - "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==" }, "source-map-js": { "version": "1.0.2", diff --git a/package.json b/package.json index 5ba15e245..054015009 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,11 @@ }, "devDependencies": { "chokidar-cli": "^3.0", - "esbuild": "0.14.23", + "esbuild": "0.14.27", "livereload": "^0.9.3", "npm-run-all": "^4.1.5", "punycode": "^2.1.1", - "sass": "^1.49.8" + "sass": "^1.49.9" }, "dependencies": { "clipboard": "^2.0.10", @@ -28,6 +28,6 @@ "dropzone": "^5.9.3", "markdown-it": "^12.3.2", "markdown-it-task-lists": "^2.1.1", - "sortablejs": "^1.14.0" + "sortablejs": "^1.15.0" } } From 1c859e94e03d0407daf55ade43ced245f208f971 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 23 Mar 2022 14:31:42 +0000 Subject: [PATCH 14/30] Fixed conctenation of direct book pages within markdown export - Updated to ensure seperation with newlines. - Added test to cover. For #3341 --- app/Entities/Tools/ExportFormatter.php | 8 ++++---- tests/Entity/ExportTest.php | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/Entities/Tools/ExportFormatter.php b/app/Entities/Tools/ExportFormatter.php index 9029d7270..99aa4536f 100644 --- a/app/Entities/Tools/ExportFormatter.php +++ b/app/Entities/Tools/ExportFormatter.php @@ -326,7 +326,7 @@ class ExportFormatter $text .= $this->pageToMarkdown($page) . "\n\n"; } - return $text; + return trim($text); } /** @@ -338,12 +338,12 @@ class ExportFormatter $text = '# ' . $book->name . "\n\n"; foreach ($bookTree as $bookChild) { if ($bookChild instanceof Chapter) { - $text .= $this->chapterToMarkdown($bookChild); + $text .= $this->chapterToMarkdown($bookChild) . "\n\n"; } else { - $text .= $this->pageToMarkdown($bookChild); + $text .= $this->pageToMarkdown($bookChild) . "\n\n"; } } - return $text; + return trim($text); } } diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index 2841175ad..f96ff97fa 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -420,6 +420,25 @@ class ExportTest extends TestCase $resp->assertSee('# ' . $page->name); } + public function test_book_markdown_export_concats_immediate_pages_with_newlines() + { + /** @var Book $book */ + $book = Book::query()->whereHas('pages')->first(); + + $this->asEditor()->get($book->getUrl('/create-page')); + $this->get($book->getUrl('/create-page')); + + [$pageA, $pageB] = $book->pages()->where('chapter_id', '=', 0)->get(); + $pageA->html = '

hello tester

'; + $pageA->save(); + $pageB->name = 'The second page in this test'; + $pageB->save(); + + $resp = $this->get($book->getUrl('/export/markdown')); + $resp->assertDontSee("hello tester# The second page in this test"); + $resp->assertSee("hello tester\n\n# The second page in this test"); + } + public function test_export_option_only_visible_and_accessible_with_permission() { $book = Book::query()->whereHas('pages')->whereHas('chapters')->first(); From d23b24b8db2e4268dd5e32b9cfda1dd6a5a3caf3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 23 Mar 2022 14:41:54 +0000 Subject: [PATCH 15/30] Added additional missing editor translations - Also merged StyleCI fixes As per #3342 --- resources/lang/en/editor.php | 15 +++++++++++++++ tests/Entity/ExportTest.php | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/resources/lang/en/editor.php b/resources/lang/en/editor.php index 7160af22d..827120292 100644 --- a/resources/lang/en/editor.php +++ b/resources/lang/en/editor.php @@ -24,6 +24,7 @@ return [ 'width' => 'Width', 'height' => 'Height', 'More' => 'More', + 'select' => 'Select...', // Toolbar 'formats' => 'Formats', @@ -92,7 +93,10 @@ return [ 'cell_properties_title' => 'Cell Properties', 'cell_type' => 'Cell type', 'cell_type_cell' => 'Cell', + 'cell_scope' => 'Scope', 'cell_type_header' => 'Header cell', + 'merge_cells' => 'Merge cells', + 'split_cell' => 'Split cell', 'table_row_group' => 'Row Group', 'table_column_group' => 'Column Group', 'horizontal_align' => 'Horizontal align', @@ -120,6 +124,16 @@ return [ 'caption' => 'Caption', 'show_caption' => 'Show caption', 'constrain' => 'Constrain proportions', + 'cell_border_solid' => 'Solid', + 'cell_border_dotted' => 'Dotted', + 'cell_border_dashed' => 'Dashed', + 'cell_border_double' => 'Double', + 'cell_border_groove' => 'Groove', + 'cell_border_ridge' => 'Ridge', + 'cell_border_inset' => 'Inset', + 'cell_border_outset' => 'Outset', + 'cell_border_none' => 'None', + 'cell_border_hidden' => 'Hidden', // Images, links, details/summary & embed 'source' => 'Source', @@ -140,6 +154,7 @@ return [ 'toggle_label' => 'Toggle label', // About view + 'about' => 'About the editor', 'about_title' => 'About the WYSIWYG Editor', 'editor_license' => 'Editor License & Copyright', 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.', diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index f96ff97fa..141f072f8 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -435,7 +435,7 @@ class ExportTest extends TestCase $pageB->save(); $resp = $this->get($book->getUrl('/export/markdown')); - $resp->assertDontSee("hello tester# The second page in this test"); + $resp->assertDontSee('hello tester# The second page in this test'); $resp->assertSee("hello tester\n\n# The second page in this test"); } From dd7463259ad1f6384807616fe967e373003f3d69 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 23 Mar 2022 15:11:14 +0000 Subject: [PATCH 16/30] Added wysiwyg filter to handle
tags within code blocks This filters out
elements within code blocks and replaces them with newlines. The editor started using
's more harshley after some configuration changes upon upgrading tinymce, in which we standardised on forced br tags to avoid empty elements. For #3327 --- resources/js/wysiwyg/config.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index e75e4f712..c32a5ffc1 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -114,6 +114,23 @@ function fetchCustomHeadContent() { return headContentLines.slice(startLineIndex + 1, endLineIndex).join('\n'); } +/** + * Setup a serializer filter for
tags to ensure they're not rendered + * within code blocks and that we use newlines there instead. + * @param {Editor} editor + */ +function setupBrFilter(editor) { + editor.serializer.addNodeFilter('br', function(nodes) { + for (const node of nodes) { + if (node.parent && node.parent.name === 'code') { + const newline = new tinymce.html.Node.create('#text'); + newline.value = '\n'; + node.replace(newline); + } + } + }); +} + /** * @param {WysiwygConfigOptions} options * @return {function(Editor)} @@ -131,6 +148,10 @@ function getSetupCallback(options) { window.editor = editor; }); + editor.on('PreInit', () => { + setupBrFilter(editor); + }); + function editorChange() { const content = editor.getContent(); if (options.darkMode) { From 8594f4258417e0e7a4a59c17d006d87dc84e9f3a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 23 Mar 2022 16:34:23 +0000 Subject: [PATCH 17/30] Added LDAP group debugging env option Closes #3345 --- app/Auth/Access/Guards/LdapSessionGuard.php | 12 +++-- app/Auth/Access/LdapService.php | 28 ++++++++--- app/Config/services.php | 1 + phpunit.xml | 2 + tests/Auth/LdapTest.php | 56 +++++++++++++++++++++ 5 files changed, 88 insertions(+), 11 deletions(-) diff --git a/app/Auth/Access/Guards/LdapSessionGuard.php b/app/Auth/Access/Guards/LdapSessionGuard.php index 5a902af76..18d4c289d 100644 --- a/app/Auth/Access/Guards/LdapSessionGuard.php +++ b/app/Auth/Access/Guards/LdapSessionGuard.php @@ -5,6 +5,7 @@ namespace BookStack\Auth\Access\Guards; use BookStack\Auth\Access\LdapService; use BookStack\Auth\Access\RegistrationService; use BookStack\Auth\User; +use BookStack\Exceptions\JsonDebugException; use BookStack\Exceptions\LdapException; use BookStack\Exceptions\LoginAttemptEmailNeededException; use BookStack\Exceptions\LoginAttemptException; @@ -15,7 +16,7 @@ use Illuminate\Support\Str; class LdapSessionGuard extends ExternalBaseSessionGuard { - protected $ldapService; + protected LdapService $ldapService; /** * LdapSessionGuard constructor. @@ -57,12 +58,13 @@ class LdapSessionGuard extends ExternalBaseSessionGuard * Attempt to authenticate a user using the given credentials. * * @param array $credentials - * @param bool $remember - * - * @throws LoginAttemptException - * @throws LdapException + * @param bool $remember * * @return bool + * @throws LdapException*@throws \BookStack\Exceptions\JsonDebugException + * + * @throws LoginAttemptException + * @throws JsonDebugException */ public function attempt(array $credentials = [], $remember = false) { diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index e529b80fd..f5d64dab3 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -15,12 +15,17 @@ use Illuminate\Support\Facades\Log; */ class LdapService { - protected $ldap; - protected $groupSyncService; + protected Ldap $ldap; + protected GroupSyncService $groupSyncService; + protected UserAvatars $userAvatars; + + /** + * @var resource + */ protected $ldapConnection; - protected $userAvatars; - protected $config; - protected $enabled; + + protected array $config; + protected bool $enabled; /** * LdapService constructor. @@ -274,6 +279,7 @@ class LdapService * Get the groups a user is a part of on ldap. * * @throws LdapException + * @throws JsonDebugException */ public function getUserGroups(string $userName): array { @@ -285,8 +291,17 @@ class LdapService } $userGroups = $this->groupFilter($user); + $allGroups = $this->getGroupsRecursive($userGroups, []); - return $this->getGroupsRecursive($userGroups, []); + if ($this->config['dump_user_groups']) { + throw new JsonDebugException([ + 'details_from_ldap' => $user, + 'parsed_direct_user_groups' => $userGroups, + 'parsed_recursive_user_groups' => $allGroups, + ]); + } + + return $allGroups; } /** @@ -369,6 +384,7 @@ class LdapService * Sync the LDAP groups to the user roles for the current user. * * @throws LdapException + * @throws JsonDebugException */ public function syncGroups(User $user, string $username) { diff --git a/app/Config/services.php b/app/Config/services.php index 2d7253fb8..a035f1056 100644 --- a/app/Config/services.php +++ b/app/Config/services.php @@ -119,6 +119,7 @@ return [ 'ldap' => [ 'server' => env('LDAP_SERVER', false), 'dump_user_details' => env('LDAP_DUMP_USER_DETAILS', false), + 'dump_user_groups' => env('LDAP_DUMP_USER_GROUPS', false), 'dn' => env('LDAP_DN', false), 'pass' => env('LDAP_PASS', false), 'base_dn' => env('LDAP_BASE_DN', false), diff --git a/phpunit.xml b/phpunit.xml index 960f4c4c3..90320ff41 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -34,6 +34,8 @@ + + diff --git a/tests/Auth/LdapTest.php b/tests/Auth/LdapTest.php index d00e8cf15..c59685ef5 100644 --- a/tests/Auth/LdapTest.php +++ b/tests/Auth/LdapTest.php @@ -348,6 +348,62 @@ class LdapTest extends TestCase ]); } + public function test_dump_user_groups_shows_group_related_details_as_json() + { + app('config')->set([ + 'services.ldap.user_to_groups' => true, + 'services.ldap.group_attribute' => 'memberOf', + 'services.ldap.remove_from_groups' => true, + 'services.ldap.dump_user_groups' => true, + ]); + + $userResp = ['count' => 1, 0 => [ + 'uid' => [$this->mockUser->name], + 'cn' => [$this->mockUser->name], + 'dn' => 'dc=test,' . config('services.ldap.base_dn'), + 'mail' => [$this->mockUser->email], + ]]; + $this->commonLdapMocks(1, 1, 4, 5, 4, 2); + $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4) + ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array')) + ->andReturn($userResp, ['count' => 1, + 0 => [ + 'dn' => 'dc=test,' . config('services.ldap.base_dn'), + 'memberof' => [ + 'count' => 1, + 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com', + ], + ], + ], [ + 'count' => 1, + 0 => [ + 'dn' => 'cn=ldaptester,ou=groups,dc=example,dc=com', + 'memberof' => [ + 'count' => 1, + 0 => 'cn=monsters,ou=groups,dc=example,dc=com', + ], + ] + ], ['count' => 0]); + + $resp = $this->mockUserLogin(); + $resp->assertJson([ + 'details_from_ldap' => [ + 'dn' => 'dc=test,' . config('services.ldap.base_dn'), + 'memberof' => [ + 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com', + 'count' => 1, + ] + ], + 'parsed_direct_user_groups' => [ + 'ldaptester', + ], + 'parsed_recursive_user_groups' => [ + 'ldaptester', + 'monsters', + ], + ]); + } + public function test_external_auth_id_visible_in_roles_page_when_ldap_active() { $role = Role::factory()->create(['display_name' => 'ldaptester', 'external_auth_id' => 'ex-auth-a, test-second-param']); From d2b49084b082f0aa28ca6969884b88acb2c63b96 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 25 Mar 2022 11:13:04 +0000 Subject: [PATCH 18/30] Added pre-render sizes to wysiwyg code blocks Sets sizes on WYSIWYG code block sections based on content lines as an early pre-codemirror height prediction to avoid excessive jumping in the editor. For #3326 --- resources/js/wysiwyg/plugin-codeeditor.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/resources/js/wysiwyg/plugin-codeeditor.js b/resources/js/wysiwyg/plugin-codeeditor.js index b0640a450..82ab7d3c8 100644 --- a/resources/js/wysiwyg/plugin-codeeditor.js +++ b/resources/js/wysiwyg/plugin-codeeditor.js @@ -97,11 +97,18 @@ function defineCodeBlockCustomElement(editor) { } this.cleanChildContent(); + const content = this.getContent(); + const lines = content.split('\n').length; + const height = (lines * 19.2) + 18 + 24; + this.style.height = `${height}px`; const container = this.shadowRoot.querySelector('.CodeMirrorContainer'); const renderCodeMirror = (Code) => { - this.cm = Code.wysiwygView(container, this.getContent(), this.getLanguage()); + this.cm = Code.wysiwygView(container, content, this.getLanguage()); Code.updateLayout(this.cm); + setTimeout(() => { + this.style.height = null; + }, 1); }; window.importVersioned('code').then((Code) => { From 2325a307a502790f1c6e904f679c047064bd8cfb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 25 Mar 2022 11:14:27 +0000 Subject: [PATCH 19/30] Applied latest styleCI changes --- app/Auth/Access/Guards/LdapSessionGuard.php | 6 +++--- app/Auth/Access/LdapService.php | 4 ++-- tests/Auth/LdapTest.php | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/Auth/Access/Guards/LdapSessionGuard.php b/app/Auth/Access/Guards/LdapSessionGuard.php index 18d4c289d..e7ed22704 100644 --- a/app/Auth/Access/Guards/LdapSessionGuard.php +++ b/app/Auth/Access/Guards/LdapSessionGuard.php @@ -58,13 +58,13 @@ class LdapSessionGuard extends ExternalBaseSessionGuard * Attempt to authenticate a user using the given credentials. * * @param array $credentials - * @param bool $remember + * @param bool $remember * - * @return bool * @throws LdapException*@throws \BookStack\Exceptions\JsonDebugException - * * @throws LoginAttemptException * @throws JsonDebugException + * + * @return bool */ public function attempt(array $credentials = [], $remember = false) { diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index f5d64dab3..2540fe2d8 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -295,8 +295,8 @@ class LdapService if ($this->config['dump_user_groups']) { throw new JsonDebugException([ - 'details_from_ldap' => $user, - 'parsed_direct_user_groups' => $userGroups, + 'details_from_ldap' => $user, + 'parsed_direct_user_groups' => $userGroups, 'parsed_recursive_user_groups' => $allGroups, ]); } diff --git a/tests/Auth/LdapTest.php b/tests/Auth/LdapTest.php index c59685ef5..03ef926cb 100644 --- a/tests/Auth/LdapTest.php +++ b/tests/Auth/LdapTest.php @@ -367,7 +367,7 @@ class LdapTest extends TestCase $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4) ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array')) ->andReturn($userResp, ['count' => 1, - 0 => [ + 0 => [ 'dn' => 'dc=test,' . config('services.ldap.base_dn'), 'memberof' => [ 'count' => 1, @@ -376,13 +376,13 @@ class LdapTest extends TestCase ], ], [ 'count' => 1, - 0 => [ - 'dn' => 'cn=ldaptester,ou=groups,dc=example,dc=com', + 0 => [ + 'dn' => 'cn=ldaptester,ou=groups,dc=example,dc=com', 'memberof' => [ 'count' => 1, 0 => 'cn=monsters,ou=groups,dc=example,dc=com', ], - ] + ], ], ['count' => 0]); $resp = $this->mockUserLogin(); @@ -390,9 +390,9 @@ class LdapTest extends TestCase 'details_from_ldap' => [ 'dn' => 'dc=test,' . config('services.ldap.base_dn'), 'memberof' => [ - 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com', + 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com', 'count' => 1, - ] + ], ], 'parsed_direct_user_groups' => [ 'ldaptester', From 55d61fceb2f70209c29e6cc0dab4087a2b7b4312 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 26 Mar 2022 12:32:08 +0000 Subject: [PATCH 20/30] Added manual image thumbnail exif orientation handling Uses original image data to extract orientation exif to apply image transformations before scaling and save. Manually done due to issues with exif data loss during the existing Invervention image path. For #1854 --- app/Uploads/ImageService.php | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index e755be7e6..ee414aacb 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -5,6 +5,7 @@ namespace BookStack\Uploads; use BookStack\Exceptions\ImageUploadException; use ErrorException; use Exception; +use GuzzleHttp\Psr7\Utils; use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\Filesystem\Filesystem as Storage; @@ -14,6 +15,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; use Intervention\Image\Exception\NotSupportedException; +use Intervention\Image\Image as InterventionImage; use Intervention\Image\ImageManager; use League\Flysystem\Util; use Psr\SimpleCache\InvalidArgumentException; @@ -308,6 +310,8 @@ class ImageService throw new ImageUploadException(trans('errors.cannot_create_thumbs')); } + $this->orientImageToOriginalExif($thumb, $imageData); + if ($keepRatio) { $thumb->resize($width, $height, function ($constraint) { $constraint->aspectRatio(); @@ -328,6 +332,48 @@ class ImageService return $thumbData; } + /** + * Orientate the given intervention image based upon the given original image data. + * Intervention does have an `orientate` method but the exif data it needs is lost before it + * can be used (At least when created using binary string data) so we need to do some + * implementation on our side to use the original image data. + * Bulk of logic taken from: https://github.com/Intervention/image/blob/b734a4988b2148e7d10364b0609978a88d277536/src/Intervention/Image/Commands/OrientateCommand.php + * Copyright (c) Oliver Vogel, MIT License + */ + protected function orientImageToOriginalExif(InterventionImage $image, string $originalData): void + { + if (!extension_loaded('exif')) { + return; + } + + $stream = Utils::streamFor($originalData)->detach(); + $orientation = exif_read_data($stream)['Orientation'] ?? null; + + switch ($orientation) { + case 2: + $image->flip(); + break; + case 3: + $image->rotate(180); + break; + case 4: + $image->rotate(180)->flip(); + break; + case 5: + $image->rotate(270)->flip(); + break; + case 6: + $image->rotate(270); + break; + case 7: + $image->rotate(90)->flip(); + break; + case 8: + $image->rotate(90); + break; + } + } + /** * Get the raw data content from an image. * From 3625f12abe7c0cc052bd027af961d0214d6fcc7e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 26 Mar 2022 16:44:34 +0000 Subject: [PATCH 21/30] Added extendable/scalable formatter for webhook data Creates a new organsied formatting system for webhook data, with interfaces for extending with custom model formatting rules. Allows easy usage & extension of the default bookstack formatting behaviour when customizing webhook events via theme system, and keeps default data customizations organised. This also makes the following webhook data changes: - owned_by/created_by/updated_by user details are loaded for events with Entity details. (POTENTIALLY BREAKING CHANGE). - current_revision details are loaded for page update/create events. Added testing to cover added model formatting rules. For #3279 and #3218 --- app/Actions/DispatchWebhookJob.php | 62 +-------- app/Actions/WebhookFormatter.php | 123 ++++++++++++++++++ app/Entities/Models/Page.php | 44 ++++--- app/Entities/Models/PageRevision.php | 2 + .../Controllers/PageRevisionController.php | 7 +- app/Theming/ThemeEvents.php | 2 + .../webhooks/parts/format-example.blade.php | 31 ++++- tests/Actions/WebhookFormatTesting.php | 52 ++++++++ tests/Entity/PageRevisionTest.php | 14 +- 9 files changed, 247 insertions(+), 90 deletions(-) create mode 100644 app/Actions/WebhookFormatter.php create mode 100644 tests/Actions/WebhookFormatTesting.php diff --git a/app/Actions/DispatchWebhookJob.php b/app/Actions/DispatchWebhookJob.php index 8f78150a9..2d805228c 100644 --- a/app/Actions/DispatchWebhookJob.php +++ b/app/Actions/DispatchWebhookJob.php @@ -3,17 +3,14 @@ namespace BookStack\Actions; use BookStack\Auth\User; -use BookStack\Entities\Models\Entity; use BookStack\Facades\Theme; use BookStack\Interfaces\Loggable; -use BookStack\Model; use BookStack\Theming\ThemeEvents; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; @@ -24,31 +21,16 @@ class DispatchWebhookJob implements ShouldQueue use Queueable; use SerializesModels; - /** - * @var Webhook - */ - protected $webhook; - - /** - * @var string - */ - protected $event; + protected Webhook $webhook; + protected string $event; + protected User $initiator; + protected int $initiatedTime; /** * @var string|Loggable */ protected $detail; - /** - * @var User - */ - protected $initiator; - - /** - * @var int - */ - protected $initiatedTime; - /** * Create a new job instance. * @@ -70,8 +52,8 @@ class DispatchWebhookJob implements ShouldQueue */ public function handle() { - $themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $this->event, $this->webhook, $this->detail); - $webhookData = $themeResponse ?? $this->buildWebhookData(); + $themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime); + $webhookData = $themeResponse ?? WebhookFormatter::getDefault($this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime)->format(); $lastError = null; try { @@ -97,36 +79,4 @@ class DispatchWebhookJob implements ShouldQueue $this->webhook->save(); } - - protected function buildWebhookData(): array - { - $textParts = [ - $this->initiator->name, - trans('activities.' . $this->event), - ]; - - if ($this->detail instanceof Entity) { - $textParts[] = '"' . $this->detail->name . '"'; - } - - $data = [ - 'event' => $this->event, - 'text' => implode(' ', $textParts), - 'triggered_at' => Carbon::createFromTimestampUTC($this->initiatedTime)->toISOString(), - 'triggered_by' => $this->initiator->attributesToArray(), - 'triggered_by_profile_url' => $this->initiator->getProfileUrl(), - 'webhook_id' => $this->webhook->id, - 'webhook_name' => $this->webhook->name, - ]; - - if (method_exists($this->detail, 'getUrl')) { - $data['url'] = $this->detail->getUrl(); - } - - if ($this->detail instanceof Model) { - $data['related_item'] = $this->detail->attributesToArray(); - } - - return $data; - } } diff --git a/app/Actions/WebhookFormatter.php b/app/Actions/WebhookFormatter.php new file mode 100644 index 000000000..48b1a3929 --- /dev/null +++ b/app/Actions/WebhookFormatter.php @@ -0,0 +1,123 @@ +webhook = $webhook; + $this->event = $event; + $this->initiator = $initiator; + $this->initiatedTime = $initiatedTime; + $this->detail = is_object($detail) ? clone $detail : $detail; + } + + public function format(): array + { + $data = [ + 'event' => $this->event, + 'text' => $this->formatText(), + 'triggered_at' => Carbon::createFromTimestampUTC($this->initiatedTime)->toISOString(), + 'triggered_by' => $this->initiator->attributesToArray(), + 'triggered_by_profile_url' => $this->initiator->getProfileUrl(), + 'webhook_id' => $this->webhook->id, + 'webhook_name' => $this->webhook->name, + ]; + + if (method_exists($this->detail, 'getUrl')) { + $data['url'] = $this->detail->getUrl(); + } + + if ($this->detail instanceof Model) { + $data['related_item'] = $this->formatModel(); + } + + return $data; + } + + /** + * @param callable(string, Model):bool $condition + * @param callable(Model):void $format + */ + public function addModelFormatter(callable $condition, callable $format): void + { + $this->modelFormatters[] = [ + 'condition' => $condition, + 'format' => $format, + ]; + } + + public function addDefaultModelFormatters(): void + { + // Load entity owner, creator, updater details + $this->addModelFormatter( + fn($event, $model) => ($model instanceof Entity), + fn($model) => $model->load(['ownedBy', 'createdBy', 'updatedBy']) + ); + + // Load revision detail for page update and create events + $this->addModelFormatter( + fn($event, $model) => ($model instanceof Page && ($event === ActivityType::PAGE_CREATE || $event === ActivityType::PAGE_UPDATE)), + fn($model) => $model->load('currentRevision') + ); + } + + protected function formatModel(): array + { + /** @var Model $model */ + $model = $this->detail; + $model->unsetRelations(); + + foreach ($this->modelFormatters as $formatter) { + if ($formatter['condition']($this->event, $model)) { + $formatter['format']($model); + } + } + + return $model->toArray(); + } + + protected function formatText(): string + { + $textParts = [ + $this->initiator->name, + trans('activities.' . $this->event), + ]; + + if ($this->detail instanceof Entity) { + $textParts[] = '"' . $this->detail->name . '"'; + } + + return implode(' ', $textParts); + } + + public static function getDefault(string $event, Webhook $webhook, $detail, User $initiator, int $initiatedTime): self + { + $instance = new static($event, $webhook, $detail, $initiator, $initiatedTime); + $instance->addDefaultModelFormatters(); + return $instance; + } +} \ No newline at end of file diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index c28b9a305..c8217af57 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -10,19 +10,22 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOne; /** * Class Page. * - * @property int $chapter_id - * @property string $html - * @property string $markdown - * @property string $text - * @property bool $template - * @property bool $draft - * @property int $revision_count - * @property Chapter $chapter - * @property Collection $attachments + * @property int $chapter_id + * @property string $html + * @property string $markdown + * @property string $text + * @property bool $template + * @property bool $draft + * @property int $revision_count + * @property Chapter $chapter + * @property Collection $attachments + * @property Collection $revisions + * @property PageRevision $currentRevision */ class Page extends BookChild { @@ -82,6 +85,19 @@ class Page extends BookChild ->orderBy('id', 'desc'); } + /** + * Get the current revision for the page if existing. + * + * @return PageRevision|null + */ + public function currentRevision(): HasOne + { + return $this->hasOne(PageRevision::class) + ->where('type', '=', 'version') + ->orderBy('created_at', 'desc') + ->orderBy('id', 'desc'); + } + /** * Get all revision instances assigned to this page. * Includes all types of revisions. @@ -117,16 +133,6 @@ class Page extends BookChild return url('/' . implode('/', $parts)); } - /** - * Get the current revision for the page if existing. - * - * @return PageRevision|null - */ - public function getCurrentRevision() - { - return $this->revisions()->first(); - } - /** * Get this page for JSON display. */ diff --git a/app/Entities/Models/PageRevision.php b/app/Entities/Models/PageRevision.php index 4daf50536..aacc94586 100644 --- a/app/Entities/Models/PageRevision.php +++ b/app/Entities/Models/PageRevision.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class PageRevision. * + * @property mixed $id * @property int $page_id * @property string $slug * @property string $book_slug @@ -27,6 +28,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; class PageRevision extends Model { protected $fillable = ['name', 'html', 'text', 'markdown', 'summary']; + protected $hidden = ['html', 'markdown', 'restricted', 'text']; /** * Get the user that created the page revision. diff --git a/app/Http/Controllers/PageRevisionController.php b/app/Http/Controllers/PageRevisionController.php index d595a6e26..c6a4926d2 100644 --- a/app/Http/Controllers/PageRevisionController.php +++ b/app/Http/Controllers/PageRevisionController.php @@ -124,11 +124,8 @@ class PageRevisionController extends Controller throw new NotFoundException("Revision #{$revId} not found"); } - // Get the current revision for the page - $currentRevision = $page->getCurrentRevision(); - - // Check if its the latest revision, cannot delete latest revision. - if (intval($currentRevision->id) === intval($revId)) { + // Check if it's the latest revision, cannot delete the latest revision. + if (intval($page->currentRevision->id ?? null) === intval($revId)) { $this->showErrorNotification(trans('entities.revision_cannot_delete_latest')); return redirect($page->getUrl('/revisions')); diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php index 48073416c..ce99c817c 100644 --- a/app/Theming/ThemeEvents.php +++ b/app/Theming/ThemeEvents.php @@ -93,6 +93,8 @@ class ThemeEvents * @param string $event * @param \BookStack\Actions\Webhook $webhook * @param string|\BookStack\Interfaces\Loggable $detail + * @param \BookStack\Auth\User $initiator + * @param int $initiatedTime */ const WEBHOOK_CALL_BEFORE = 'webhook_call_before'; } diff --git a/resources/views/settings/webhooks/parts/format-example.blade.php b/resources/views/settings/webhooks/parts/format-example.blade.php index 135d3193b..e10a6c69e 100644 --- a/resources/views/settings/webhooks/parts/format-example.blade.php +++ b/resources/views/settings/webhooks/parts/format-example.blade.php @@ -23,12 +23,37 @@ "priority": 2, "created_at": "2021-12-11T21:53:24.000000Z", "updated_at": "2021-12-11T22:25:10.000000Z", - "created_by": 1, - "updated_by": 1, + "created_by": { + "id": 1, + "name": "Benny", + "slug": "benny" + }, + "updated_by": { + "id": 1, + "name": "Benny", + "slug": "benny" + }, "draft": false, "revision_count": 9, "template": false, - "owned_by": 1 + "owned_by": { + "id": 1, + "name": "Benny", + "slug": "benny" + }, + "current_revision": { + "id": 597, + "page_id": 2598, + "name": "My wonderful updated page", + "created_by": 1, + "created_at": "2021-12-11T21:53:24.000000Z", + "updated_at": "2021-12-11T21:53:24.000000Z", + "slug": "my-wonderful-updated-page", + "book_slug": "my-awesome-book", + "type": "version", + "summary": "Updated the title and fixed some spelling", + "revision_number": 2 + } } } \ No newline at end of file diff --git a/tests/Actions/WebhookFormatTesting.php b/tests/Actions/WebhookFormatTesting.php new file mode 100644 index 000000000..56a569ca9 --- /dev/null +++ b/tests/Actions/WebhookFormatTesting.php @@ -0,0 +1,52 @@ + Book::query()->first(), + ActivityType::CHAPTER_CREATE => Chapter::query()->first(), + ActivityType::PAGE_MOVE => Page::query()->first(), + ]; + + foreach ($events as $event => $entity) { + $data = $this->getWebhookData($event, $entity); + + $this->assertEquals($entity->createdBy->name, Arr::get($data, 'related_item.created_by.name')); + $this->assertEquals($entity->updatedBy->id, Arr::get($data, 'related_item.updated_by.id')); + $this->assertEquals($entity->ownedBy->slug, Arr::get($data, 'related_item.owned_by.slug')); + } + } + + public function test_page_create_and_update_events_show_revision_info() + { + /** @var Page $page */ + $page = Page::query()->first(); + $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); + + $data = $this->getWebhookData(ActivityType::PAGE_UPDATE, $page); + $this->assertEquals($page->currentRevision->id, Arr::get($data, 'related_item.current_revision.id')); + $this->assertEquals($page->currentRevision->type, Arr::get($data, 'related_item.current_revision.type')); + $this->assertEquals('Update a', Arr::get($data, 'related_item.current_revision.summary')); + } + + protected function getWebhookData(string $event, $detail): array + { + $webhook = Webhook::factory()->make(); + $user = $this->getEditor(); + $formatter = WebhookFormatter::getDefault($event, $webhook, $detail, $user, time()); + return $formatter->format(); + } +} \ No newline at end of file diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index 2ed7d3b41..fc6678788 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -144,13 +144,14 @@ class PageRevisionTest extends TestCase public function test_revision_deletion() { - $page = Page::first(); + /** @var Page $page */ + $page = Page::query()->first(); $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - $page = Page::find($page->id); + $page->refresh(); $this->asEditor()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html', 'summary' => 'Update a']); - $page = Page::find($page->id); + $page->refresh(); $beforeRevisionCount = $page->revisions->count(); // Delete the first revision @@ -158,18 +159,17 @@ class PageRevisionTest extends TestCase $resp = $this->asEditor()->delete($revision->getUrl('/delete/')); $resp->assertRedirect($page->getUrl('/revisions')); - $page = Page::find($page->id); + $page->refresh(); $afterRevisionCount = $page->revisions->count(); $this->assertTrue($beforeRevisionCount === ($afterRevisionCount + 1)); // Try to delete the latest revision $beforeRevisionCount = $page->revisions->count(); - $currentRevision = $page->getCurrentRevision(); - $resp = $this->asEditor()->delete($currentRevision->getUrl('/delete/')); + $resp = $this->asEditor()->delete($page->currentRevision->getUrl('/delete/')); $resp->assertRedirect($page->getUrl('/revisions')); - $page = Page::find($page->id); + $page->refresh(); $afterRevisionCount = $page->revisions->count(); $this->assertTrue($beforeRevisionCount === $afterRevisionCount); } From b5281bc9ca9adb6fffc9fcba80a95b0d43e45bdd Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 26 Mar 2022 20:38:03 +0000 Subject: [PATCH 22/30] Fixed tests, applied StyleCI changes --- app/Actions/WebhookFormatter.php | 15 ++++++++------- app/Entities/Models/PageRevision.php | 2 +- app/Uploads/ImageService.php | 5 +++-- tests/Actions/WebhookFormatTesting.php | 7 ++++--- tests/ThemeTest.php | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/Actions/WebhookFormatter.php b/app/Actions/WebhookFormatter.php index 48b1a3929..5b64a747a 100644 --- a/app/Actions/WebhookFormatter.php +++ b/app/Actions/WebhookFormatter.php @@ -60,13 +60,13 @@ class WebhookFormatter /** * @param callable(string, Model):bool $condition - * @param callable(Model):void $format + * @param callable(Model):void $format */ public function addModelFormatter(callable $condition, callable $format): void { $this->modelFormatters[] = [ 'condition' => $condition, - 'format' => $format, + 'format' => $format, ]; } @@ -74,14 +74,14 @@ class WebhookFormatter { // Load entity owner, creator, updater details $this->addModelFormatter( - fn($event, $model) => ($model instanceof Entity), - fn($model) => $model->load(['ownedBy', 'createdBy', 'updatedBy']) + fn ($event, $model) => ($model instanceof Entity), + fn ($model) => $model->load(['ownedBy', 'createdBy', 'updatedBy']) ); // Load revision detail for page update and create events $this->addModelFormatter( - fn($event, $model) => ($model instanceof Page && ($event === ActivityType::PAGE_CREATE || $event === ActivityType::PAGE_UPDATE)), - fn($model) => $model->load('currentRevision') + fn ($event, $model) => ($model instanceof Page && ($event === ActivityType::PAGE_CREATE || $event === ActivityType::PAGE_UPDATE)), + fn ($model) => $model->load('currentRevision') ); } @@ -118,6 +118,7 @@ class WebhookFormatter { $instance = new static($event, $webhook, $detail, $initiator, $initiatedTime); $instance->addDefaultModelFormatters(); + return $instance; } -} \ No newline at end of file +} diff --git a/app/Entities/Models/PageRevision.php b/app/Entities/Models/PageRevision.php index aacc94586..800e5e7f2 100644 --- a/app/Entities/Models/PageRevision.php +++ b/app/Entities/Models/PageRevision.php @@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class PageRevision. * - * @property mixed $id + * @property mixed $id * @property int $page_id * @property string $slug * @property string $book_slug diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index ee414aacb..ca0db997b 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -338,7 +338,7 @@ class ImageService * can be used (At least when created using binary string data) so we need to do some * implementation on our side to use the original image data. * Bulk of logic taken from: https://github.com/Intervention/image/blob/b734a4988b2148e7d10364b0609978a88d277536/src/Intervention/Image/Commands/OrientateCommand.php - * Copyright (c) Oliver Vogel, MIT License + * Copyright (c) Oliver Vogel, MIT License. */ protected function orientImageToOriginalExif(InterventionImage $image, string $originalData): void { @@ -347,7 +347,8 @@ class ImageService } $stream = Utils::streamFor($originalData)->detach(); - $orientation = exif_read_data($stream)['Orientation'] ?? null; + $exif = @exif_read_data($stream); + $orientation = $exif ? ($exif['Orientation'] ?? null) : null; switch ($orientation) { case 2: diff --git a/tests/Actions/WebhookFormatTesting.php b/tests/Actions/WebhookFormatTesting.php index 56a569ca9..4e9ba5e47 100644 --- a/tests/Actions/WebhookFormatTesting.php +++ b/tests/Actions/WebhookFormatTesting.php @@ -16,9 +16,9 @@ class WebhookFormatTesting extends TestCase public function test_entity_events_show_related_user_info() { $events = [ - ActivityType::BOOK_UPDATE => Book::query()->first(), + ActivityType::BOOK_UPDATE => Book::query()->first(), ActivityType::CHAPTER_CREATE => Chapter::query()->first(), - ActivityType::PAGE_MOVE => Page::query()->first(), + ActivityType::PAGE_MOVE => Page::query()->first(), ]; foreach ($events as $event => $entity) { @@ -47,6 +47,7 @@ class WebhookFormatTesting extends TestCase $webhook = Webhook::factory()->make(); $user = $this->getEditor(); $formatter = WebhookFormatter::getDefault($event, $webhook, $detail, $user, time()); + return $formatter->format(); } -} \ No newline at end of file +} diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index 775be92fc..cad2369f8 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -186,7 +186,7 @@ class ThemeTest extends TestCase dispatch((new DispatchWebhookJob($webhook, $event, $detail))); - $this->assertCount(3, $args); + $this->assertCount(5, $args); $this->assertEquals($event, $args[0]); $this->assertEquals($webhook->id, $args[1]->id); $this->assertEquals($detail->id, $args[2]->id); From 31dbf132b932bd0777edc87605f62c3681d94b32 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 26 Mar 2022 21:36:05 +0000 Subject: [PATCH 23/30] Started playing with new settings view layout --- resources/icons/palette.svg | 1 + resources/sass/_layout.scss | 3 + resources/sass/_lists.scss | 12 +- resources/views/settings/audit.blade.php | 6 +- resources/views/settings/index.blade.php | 510 +++++++++--------- .../views/settings/maintenance.blade.php | 2 +- .../parts/navbar-with-version.blade.php | 17 - .../views/settings/parts/navbar.blade.php | 2 +- .../settings/recycle-bin/destroy.blade.php | 4 +- .../settings/recycle-bin/index.blade.php | 4 +- .../settings/recycle-bin/restore.blade.php | 4 +- .../views/settings/roles/create.blade.php | 4 +- .../views/settings/roles/delete.blade.php | 4 +- resources/views/settings/roles/edit.blade.php | 4 +- .../views/settings/roles/index.blade.php | 4 +- .../views/settings/webhooks/create.blade.php | 4 +- .../views/settings/webhooks/delete.blade.php | 4 +- .../views/settings/webhooks/edit.blade.php | 4 +- .../views/settings/webhooks/index.blade.php | 4 +- resources/views/users/create.blade.php | 4 +- resources/views/users/delete.blade.php | 4 +- resources/views/users/edit.blade.php | 4 +- resources/views/users/index.blade.php | 4 +- resources/views/users/profile.blade.php | 2 +- 24 files changed, 300 insertions(+), 315 deletions(-) create mode 100644 resources/icons/palette.svg delete mode 100644 resources/views/settings/parts/navbar-with-version.blade.php diff --git a/resources/icons/palette.svg b/resources/icons/palette.svg new file mode 100644 index 000000000..114386302 --- /dev/null +++ b/resources/icons/palette.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index 69882d40d..b1c80cb53 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -8,6 +8,9 @@ margin-inline-end: auto; padding-inline-start: $-m; padding-inline-end: $-m; + &.medium { + max-width: 1100px; + } &.small { max-width: 840px; } diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 8febdcffc..ea10f66bf 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -681,7 +681,17 @@ ul.pagination { } a:hover { @include lightDark(background-color, rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)); - border-radius: 3px; + border-radius: 4px; text-decoration: none; } + &.in-sidebar { + a { + display: block; + margin-bottom: $-xs; + } + a.active { + border-radius: 4px; + @include lightDark(background-color, rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)); + } + } } diff --git a/resources/views/settings/audit.blade.php b/resources/views/settings/audit.blade.php index 48e46a59d..ca5dba527 100644 --- a/resources/views/settings/audit.blade.php +++ b/resources/views/settings/audit.blade.php @@ -3,11 +3,7 @@ @section('body')
-
-
- @include('settings.parts.navbar', ['selected' => 'audit']) -
-
+ @include('settings.parts.navbar', ['selected' => 'audit'])

{{ trans('settings.audit') }}

diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php index 8b5615658..d22da360c 100644 --- a/resources/views/settings/index.blade.php +++ b/resources/views/settings/index.blade.php @@ -1,275 +1,297 @@ @extends('layouts.simple') @section('body') -
+
- @include('settings.parts.navbar-with-version', ['selected' => 'settings']) - -
-

{{ trans('settings.app_features_security') }}

-
- {!! csrf_field() !!} - - -
- - -
-
- -

{!! trans('settings.app_public_access_desc') !!}

- @if(userCan('users-manage')) -

- {!! trans('settings.app_public_access_desc_guest') !!} -

- @endif -
-
- @include('form.toggle-switch', [ - 'name' => 'setting-app-public', - 'value' => setting('app-public'), - 'label' => trans('settings.app_public_access_toggle'), - ]) -
-
- -
-
- -

{{ trans('settings.app_secure_images_desc') }}

-
-
- @include('form.toggle-switch', [ - 'name' => 'setting-app-secure-images', - 'value' => setting('app-secure-images'), - 'label' => trans('settings.app_secure_images_toggle'), - ]) -
-
- -
-
- -

{!! trans('settings.app_disable_comments_desc') !!}

-
-
- @include('form.toggle-switch', [ - 'name' => 'setting-app-disable-comments', - 'value' => setting('app-disable-comments'), - 'label' => trans('settings.app_disable_comments_toggle'), - ]) -
-
+ @include('settings.parts.navbar', ['selected' => 'settings']) +
+ -
-

{{ trans('settings.app_customization') }}

-
- {!! csrf_field() !!} - +
+
+

{{ trans('settings.app_features_security') }}

+ + {!! csrf_field() !!} + -
+
-
-
- -

{{ trans('settings.app_name_desc') }}

-
-
- - @include('form.toggle-switch', [ - 'name' => 'setting-app-name-header', - 'value' => setting('app-name-header'), - 'label' => trans('settings.app_name_header'), - ]) -
-
-
-
- -

{{ trans('settings.app_editor_desc') }}

-
-
- -
-
- -
-
- -

{!! trans('settings.app_logo_desc') !!}

-
-
- @include('form.image-picker', [ - 'removeName' => 'setting-app-logo', - 'removeValue' => 'none', - 'defaultImage' => url('/logo.png'), - 'currentImage' => setting('app-logo'), - 'name' => 'app_logo', - 'imageClass' => 'logo-image', - ]) -
-
- - -
-
- -

{!! trans('settings.app_primary_color_desc') !!}

-
-
- - -
- - | - +
+
+ +

{!! trans('settings.app_public_access_desc') !!}

+ @if(userCan('users-manage')) +

+ {!! trans('settings.app_public_access_desc_guest') !!} +

+ @endif +
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-app-public', + 'value' => setting('app-public'), + 'label' => trans('settings.app_public_access_toggle'), + ]) +
+
+ +
+
+ +

{{ trans('settings.app_secure_images_desc') }}

+
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-app-secure-images', + 'value' => setting('app-secure-images'), + 'label' => trans('settings.app_secure_images_toggle'), + ]) +
+
+ +
+
+ +

{!! trans('settings.app_disable_comments_desc') !!}

+
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-app-disable-comments', + 'value' => setting('app-disable-comments'), + 'label' => trans('settings.app_disable_comments_toggle'), + ]) +
-
-
- -
-
- -

{!! trans('settings.content_colors_desc') !!}

-
+ +
+ +
+ +
+ +
+

{{ trans('settings.app_customization') }}

+
+ {!! csrf_field() !!} + + +
+ +
+
+ +

{{ trans('settings.app_name_desc') }}

+
+
+ + @include('form.toggle-switch', [ + 'name' => 'setting-app-name-header', + 'value' => setting('app-name-header'), + 'label' => trans('settings.app_name_header'), + ]) +
+
+ +
+
+ +

{{ trans('settings.app_editor_desc') }}

+
+
+ +
+
+ +
+
+ +

{!! trans('settings.app_logo_desc') !!}

+
+
+ @include('form.image-picker', [ + 'removeName' => 'setting-app-logo', + 'removeValue' => 'none', + 'defaultImage' => url('/logo.png'), + 'currentImage' => setting('app-logo'), + 'name' => 'app_logo', + 'imageClass' => 'logo-image', + ]) +
+
+ + +
+
+ +

{!! trans('settings.app_primary_color_desc') !!}

+
+
+ + +
+ + | + +
+ +
+
+ + +
+
+ +

{!! trans('settings.content_colors_desc') !!}

+
+
+
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'bookshelf']) + @include('settings.parts.setting-entity-color-picker', ['type' => 'book']) + @include('settings.parts.setting-entity-color-picker', ['type' => 'chapter']) +
+
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'page']) + @include('settings.parts.setting-entity-color-picker', ['type' => 'page-draft']) +
+
+
+ +
+
+ +

{{ trans('settings.app_homepage_desc') }}

+
+
+ + + +
+
+
- @include('settings.parts.setting-entity-color-picker', ['type' => 'bookshelf']) - @include('settings.parts.setting-entity-color-picker', ['type' => 'book']) - @include('settings.parts.setting-entity-color-picker', ['type' => 'chapter']) + +

{{ trans('settings.app_footer_links_desc') }}

+ @include('settings.parts.footer-links', ['name' => 'setting-app-footer-links', 'value' => setting('app-footer-links', [])])
+ +
- @include('settings.parts.setting-entity-color-picker', ['type' => 'page']) - @include('settings.parts.setting-entity-color-picker', ['type' => 'page-draft']) + +

{{ trans('settings.app_custom_html_desc') }}

+ +

{{ trans('settings.app_custom_html_disabled_notice') }}

-
-
-
-
- -

{{ trans('settings.app_homepage_desc') }}

-
-
- - + +
+ +
+ +
+ +
+

{{ trans('settings.reg_settings') }}

+
+ {!! csrf_field() !!} + + +
+
+
+ +

{!! trans('settings.reg_enable_desc') !!}

+
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-registration-enabled', + 'value' => setting('registration-enabled'), + 'label' => trans('settings.reg_enable_toggle') + ]) + + @if(in_array(config('auth.method'), ['ldap', 'saml2', 'oidc'])) +
{{ trans('settings.reg_enable_external_warning') }}
+ @endif + + + +
+ +
+
+ +

{!! trans('settings.reg_confirm_restrict_domain_desc') !!}

+
+
+ +
+
+ +
+
+ +

{{ trans('settings.reg_confirm_email_desc') }}

+
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-registration-confirmation', + 'value' => setting('registration-confirmation'), + 'label' => trans('settings.reg_email_confirmation_toggle') + ]) +
+
+
-
- -
- -

{{ trans('settings.app_footer_links_desc') }}

- @include('settings.parts.footer-links', ['name' => 'setting-app-footer-links', 'value' => setting('app-footer-links', [])]) -
- - -
- -

{{ trans('settings.app_custom_html_desc') }}

- -

{{ trans('settings.app_custom_html_disabled_notice') }}

-
- +
+ +
+
+
-
- -
- -
- -
-

{{ trans('settings.reg_settings') }}

-
- {!! csrf_field() !!} - - -
-
-
- -

{!! trans('settings.reg_enable_desc') !!}

-
-
- @include('form.toggle-switch', [ - 'name' => 'setting-registration-enabled', - 'value' => setting('registration-enabled'), - 'label' => trans('settings.reg_enable_toggle') - ]) - - @if(in_array(config('auth.method'), ['ldap', 'saml2', 'oidc'])) -
{{ trans('settings.reg_enable_external_warning') }}
- @endif - - - -
-
- -
-
- -

{!! trans('settings.reg_confirm_restrict_domain_desc') !!}

-
-
- -
-
- -
-
- -

{{ trans('settings.reg_confirm_email_desc') }}

-
-
- @include('form.toggle-switch', [ - 'name' => 'setting-registration-confirmation', - 'value' => setting('registration-confirmation'), - 'label' => trans('settings.reg_email_confirmation_toggle') - ]) -
-
- -
- -
- -
-
diff --git a/resources/views/settings/maintenance.blade.php b/resources/views/settings/maintenance.blade.php index ea94413f2..a2a9ebc81 100644 --- a/resources/views/settings/maintenance.blade.php +++ b/resources/views/settings/maintenance.blade.php @@ -3,7 +3,7 @@ @section('body')
- @include('settings.parts.navbar-with-version', ['selected' => 'maintenance']) + @include('settings.parts.navbar', ['selected' => 'maintenance'])

{{ trans('settings.recycle_bin') }}

diff --git a/resources/views/settings/parts/navbar-with-version.blade.php b/resources/views/settings/parts/navbar-with-version.blade.php deleted file mode 100644 index bec41146b..000000000 --- a/resources/views/settings/parts/navbar-with-version.blade.php +++ /dev/null @@ -1,17 +0,0 @@ -{{-- -$selected - String name of the selected tab -$version - Version of bookstack to display ---}} -
-
- @include('settings.parts.navbar', ['selected' => $selected]) -
-
-
-
-
- \ No newline at end of file diff --git a/resources/views/settings/parts/navbar.blade.php b/resources/views/settings/parts/navbar.blade.php index f2fad378c..e229f5e65 100644 --- a/resources/views/settings/parts/navbar.blade.php +++ b/resources/views/settings/parts/navbar.blade.php @@ -1,5 +1,5 @@ -