X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/397a36cfd05d26faecc1350817fa1d2fb20aa6ff..24e6dc4b37f857ffe6f8eab85ca177ed290bb38a:/resources/js/wysiwyg/plugins-tasklist.js diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js index 4afbfa8e6..191f83649 100644 --- a/resources/js/wysiwyg/plugins-tasklist.js +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -1,9 +1,82 @@ /** + * @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 * @param {Editor} editor - * @param {String} url */ -function register(editor, url) { +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'); + } + }); + } +} + +/** + * @param {AstNode} node + */ +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 (child.name === 'input') { + if (child.attr('checked') === 'checked') { + node.attr('checked', 'checked'); + } + child.remove(); + } + } +} + +/** + * @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); + + const inputAttrs = {type: 'checkbox', disabled: 'disabled'}; + if (isChecked) { + inputAttrs.checked = 'checked'; + } + + // Create & insert checkbox input element + const checkbox = window.tinymce.html.Node.create('input', inputAttrs); + checkbox.shortEnded = true; + + if (node.firstChild) { + node.insert(checkbox, node.firstChild, true); + } else { + node.append(checkbox); + } +} + +/** + * @param {Editor} editor + */ +function register(editor) { // Tasklist UI buttons editor.ui.registry.addIcon('tasklist', ''); editor.ui.registry.addToggleButton('tasklist', { @@ -28,13 +101,13 @@ function register(editor, url) { 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) { + existingBullListButton.onSetup = function customBullListOnSetup(api) { editor.on('NodeChange', event => { const parentList = event.parents.find(el => el.nodeName === 'LI'); const inTaskList = parentList && parentList.classList.contains('task-list-item'); @@ -42,38 +115,38 @@ function register(editor, url) { api.setActive(Boolean(inUlList && !inTaskList)); }); }; - existingBullListButton.onAction = function() { + existingBullListButton.onAction = function customBullListOnAction() { // 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} + 'list-item-attributes': {class: null}, }); } editor.execCommand('InsertUnorderedList', null, { - 'list-item-attributes': {class: 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() { + existingNumListButton.onAction = function customNumListButtonOnAction() { editor.execCommand('InsertOrderedList', null, { - 'list-item-attributes': {class: null} + 'list-item-attributes': {class: null}, }); }; // Setup filters on pre-init editor.on('PreInit', () => { - editor.parser.addNodeFilter('li', function(nodes) { + editor.parser.addNodeFilter('li', nodes => { for (const node of nodes) { if (node.attributes.map.class === 'task-list-item') { parseTaskListNode(node); } } }); - editor.serializer.addNodeFilter('li', function(nodes) { + editor.serializer.addNodeFilter('li', nodes => { for (const node of nodes) { if (node.attributes.map.class === 'task-list-item') { serializeTaskListNode(node); @@ -83,7 +156,7 @@ function register(editor, url) { }); // Handle checkbox click in editor - editor.on('click', function(event) { + editor.on('click', event => { const clickedEl = event.target; if (clickedEl.nodeName === 'LI' && clickedEl.classList.contains('task-list-item')) { handleTaskListItemClick(event, clickedEl, editor); @@ -93,79 +166,8 @@ 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 - * @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'); - } - }); - } -} - -/** - * @param {AstNode} node - */ -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 (child.name === 'input') { - if (child.attr('checked') === 'checked') { - node.attr('checked', 'checked'); - } - child.remove(); - } - } -} - -/** - * @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); - - const inputAttrs = {type: 'checkbox', disabled: 'disabled'}; - if (isChecked) { - inputAttrs.checked = 'checked'; - } - - // 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); -} - -/** - * @param {WysiwygConfigOptions} options * @return {register} */ -export function getPlugin(options) { +export function getPlugin() { return register; -} \ No newline at end of file +}