X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/b6be8a2bb9a640eef14e67376cbb57f999084ca9..ee24635e06a8c01d751f80caba47c57f76e8989d:/resources/js/wysiwyg/plugins-tasklist.js diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js index 3fbc2c1e8..4afbfa8e6 100644 --- a/resources/js/wysiwyg/plugins-tasklist.js +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -2,11 +2,70 @@ * @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 parentListEl = event.parents.find(el => el.nodeName === 'LI'); + const inList = parentListEl && parentListEl.classList.contains('task-list-item'); + api.setActive(Boolean(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 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(Boolean(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} + }); + }; + // 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 +73,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 +80,25 @@ 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(); } }); +} +/** + * @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'); } /** @@ -80,6 +147,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); @@ -88,7 +156,8 @@ function serializeTaskListNode(node) { inputAttrs.checked = 'checked'; } - const checkbox = new tinymce.html.Node.create('input', inputAttrs); + // Create & insert checkbox input element + const checkbox = tinymce.html.Node.create('input', inputAttrs); checkbox.shortEnded = true; node.firstChild ? node.insert(checkbox, node.firstChild, true) : node.append(checkbox); }