]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/plugins-tasklist.js
3fbc2c1e80fe81cefd30dd244b2d7ed17add4975
[bookstack] / resources / js / wysiwyg / plugins-tasklist.js
1 /**
2  * @param {Editor} editor
3  * @param {String} url
4  */
5
6 function register(editor, url) {
7
8     editor.on('PreInit', () => {
9
10         editor.parser.addNodeFilter('li', function(nodes) {
11             for (const node of nodes) {
12                 if (node.attributes.map.class === 'task-list-item') {
13                     parseTaskListNode(node);
14                 }
15             }
16         });
17
18         editor.serializer.addNodeFilter('li', function(nodes) {
19             for (const node of nodes) {
20                 if (node.attributes.map.class === 'task-list-item') {
21                     serializeTaskListNode(node);
22                 }
23             }
24         });
25
26     });
27
28     editor.on('click', function(event) {
29         const clickedEl = event.originalTarget;
30         if (clickedEl.nodeName === 'LI' && clickedEl.classList.contains('task-list-item')) {
31             handleTaskListItemClick(event, clickedEl, editor);
32         }
33     });
34
35 }
36
37 /**
38  * @param {MouseEvent} event
39  * @param {Element} clickedEl
40  * @param {Editor} editor
41  */
42 function handleTaskListItemClick(event, clickedEl, editor) {
43     const bounds = clickedEl.getBoundingClientRect();
44     const withinBounds = event.clientX <= bounds.right
45                         && event.clientX >= bounds.left
46                         && event.clientY >= bounds.top
47                         && event.clientY <= bounds.bottom;
48
49     // Outside of the task list item bounds mean we're probably clicking the pseudo-element.
50     if (!withinBounds) {
51         editor.undoManager.transact(() => {
52             if (clickedEl.hasAttribute('checked')) {
53                 clickedEl.removeAttribute('checked');
54             }  else {
55                 clickedEl.setAttribute('checked', 'checked');
56             }
57         });
58     }
59 }
60
61 /**
62  * @param {AstNode} node
63  */
64 function parseTaskListNode(node) {
65     // Force task list item class
66     node.attr('class', 'task-list-item');
67
68     // Copy checkbox status and remove checkbox within editor
69     for (const child of node.children()) {
70         if (child.name === 'input') {
71             if (child.attr('checked') === 'checked') {
72                 node.attr('checked', 'checked');
73             }
74             child.remove();
75         }
76     }
77 }
78
79 /**
80  * @param {AstNode} node
81  */
82 function serializeTaskListNode(node) {
83     const isChecked = node.attr('checked') === 'checked';
84     node.attr('checked', null);
85
86     const inputAttrs = {type: 'checkbox', disabled: 'disabled'};
87     if (isChecked) {
88         inputAttrs.checked = 'checked';
89     }
90
91     const checkbox = new tinymce.html.Node.create('input', inputAttrs);
92     checkbox.shortEnded = true;
93     node.firstChild ? node.insert(checkbox, node.firstChild, true) : node.append(checkbox);
94 }
95
96 /**
97  * @param {WysiwygConfigOptions} options
98  * @return {register}
99  */
100 export function getPlugin(options) {
101     return register;
102 }