From f991948c4931ecf2d4e32dadc8b099207f76fa82 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 19 Mar 2022 16:04:33 +0000 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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',