Skip to content

Commit

Permalink
Add support for task list checkboxes outside p
Browse files Browse the repository at this point in the history
Closes GH-80.
Closes GH-81.

Reviewed-by: Titus Wormer <tituswormer@gmail.com>
  • Loading branch information
Mr0grog committed Sep 30, 2023
1 parent 7be4e81 commit 183cbf9
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 33 deletions.
98 changes: 65 additions & 33 deletions lib/handlers/li.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,17 @@ import {phrasing} from 'hast-util-phrasing'
* mdast node.
*/
export function li(state, node) {
const head = node.children[0]
/** @type {boolean | null} */
let checked = null
/** @type {Element | undefined} */
let clone

// Check if this node starts with a checkbox.
if (head && head.type === 'element' && head.tagName === 'p') {
const checkbox = head.children[0]

if (
checkbox &&
checkbox.type === 'element' &&
checkbox.tagName === 'input' &&
checkbox.properties &&
(checkbox.properties.type === 'checkbox' ||
checkbox.properties.type === 'radio')
) {
checked = Boolean(checkbox.properties.checked)
clone = {
...node,
children: [
{...head, children: head.children.slice(1)},
...node.children.slice(1)
]
}
}
}
// If the list item starts with a checkbox, remove the checkbox and mark the
// list item as a GFM task list item.
const {cleanNode, checkbox} = extractLeadingCheckbox(node)
const checked = checkbox && Boolean(checkbox.properties.checked)

if (!clone) clone = node

const spread = spreadout(clone)
const children = state.toFlow(state.all(clone))
const spread = spreadout(cleanNode)
const children = state.toFlow(state.all(cleanNode))

/** @type {ListItem} */
const result = {type: 'listItem', spread, checked, children}
state.patch(clone, result)
state.patch(cleanNode, result)
return result
}

Expand Down Expand Up @@ -99,3 +73,61 @@ function spreadout(node) {

return false
}

/**
* If the first bit of content in an element is a checkbox, create a copy of
* the element that does not include the checkbox and return the cleaned up
* copy alongside the checkbox that was removed. If there was no leading
* checkbox, this returns the original element unaltered (not a copy).
*
* This detects trees like:
* `<li><input type="checkbox">Text</li>`
* And returns a tree like:
* `<li>Text</li>`
*
* Or with nesting:
* `<li><p><input type="checkbox">Text</p></li>`
* Which returns a tree like:
* `<li><p>Text</p></li>`
*
* @param {Readonly<Element>} node
* @returns {{cleanNode: Element, checkbox: Element | null}}
*/
function extractLeadingCheckbox(node) {
const head = node.children[0]

if (
head &&
head.type === 'element' &&
head.tagName === 'input' &&
head.properties &&
(head.properties.type === 'checkbox' || head.properties.type === 'radio')
) {
return {
cleanNode: {...node, children: node.children.slice(1)},
checkbox: head
}
}

// The checkbox may be nested in another element. If the first element has
// children, look for a leading checkbox inside it.
//
// NOTE: this only handles nesting in `<p>` elements, which is most common.
// It's possible a leading checkbox might be nested in other types of flow or
// phrasing elements (and *deeply* nested, which is not possible with `<p>`).
// Limiting things to `<p>` elements keeps this simpler for now.
if (head && head.type === 'element' && head.tagName === 'p') {
const {cleanNode: cleanHead, checkbox} = extractLeadingCheckbox(head)
if (checkbox) {
return {
cleanNode: {
...node,
children: [cleanHead, ...node.children.slice(1)]
},
checkbox
}
}
}

return {cleanNode: node, checkbox: null}
}
10 changes: 10 additions & 0 deletions test/fixtures/ol/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,14 @@
<li><p><input type="checkbox">Echo</p></li>
<li><p><input type="checkbox"><strong>Foxtrot</strong></p></li>
<li><p><input type="checkbox"> <strong>Golf</strong></p></li>
<li>
<p>
<input type="checkbox"> Hotel
</p>
</li>
<li><input type="checkbox"> India</li>
<li>
<input type="checkbox"> Juliet
</li>
<li><input type="checkbox"></li>
</ol>
8 changes: 8 additions & 0 deletions test/fixtures/ol/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,11 @@ Quuux.
5. [ ] **Foxtrot**

6. [ ] **Golf**

7. [ ] Hotel

8. [ ] India

9. [ ] Juliet

10.
10 changes: 10 additions & 0 deletions test/fixtures/ul/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,14 @@
<li><p><input type="checkbox">Echo</p></li>
<li><p><input type="checkbox"><strong>Foxtrot</strong></p></li>
<li><p><input type="checkbox"> <strong>Golf</strong></p></li>
<li>
<p>
<input type="checkbox"> Hotel
</p>
</li>
<li><input type="checkbox"> India</li>
<li>
<input type="checkbox"> Juliet
</li>
<li><input type="checkbox"></li>
</ul>
8 changes: 8 additions & 0 deletions test/fixtures/ul/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,11 @@ Quuux.
* [ ] **Foxtrot**

* [ ] **Golf**

* [ ] Hotel

* [ ] India

* [ ] Juliet

*

0 comments on commit 183cbf9

Please sign in to comment.