site/node_modules/mdast-util-gfm-task-list-item/lib/index.js

142 lines
3.7 KiB
JavaScript
Raw Permalink Normal View History

2024-10-14 06:09:33 +00:00
/**
* @typedef {import('mdast').ListItem} ListItem
* @typedef {import('mdast').Paragraph} Paragraph
* @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
* @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
* @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle
* @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension
* @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle
*/
import {ok as assert} from 'devlop'
import {defaultHandlers} from 'mdast-util-to-markdown'
/**
* Create an extension for `mdast-util-from-markdown` to enable GFM task
* list items in markdown.
*
* @returns {FromMarkdownExtension}
* Extension for `mdast-util-from-markdown` to enable GFM task list items.
*/
export function gfmTaskListItemFromMarkdown() {
return {
exit: {
taskListCheckValueChecked: exitCheck,
taskListCheckValueUnchecked: exitCheck,
paragraph: exitParagraphWithTaskListItem
}
}
}
/**
* Create an extension for `mdast-util-to-markdown` to enable GFM task list
* items in markdown.
*
* @returns {ToMarkdownExtension}
* Extension for `mdast-util-to-markdown` to enable GFM task list items.
*/
export function gfmTaskListItemToMarkdown() {
return {
unsafe: [{atBreak: true, character: '-', after: '[:|-]'}],
handlers: {listItem: listItemWithTaskListItem}
}
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function exitCheck(token) {
// Were always in a paragraph, in a list item.
const node = this.stack[this.stack.length - 2]
assert(node.type === 'listItem')
node.checked = token.type === 'taskListCheckValueChecked'
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function exitParagraphWithTaskListItem(token) {
const parent = this.stack[this.stack.length - 2]
if (
parent &&
parent.type === 'listItem' &&
typeof parent.checked === 'boolean'
) {
const node = this.stack[this.stack.length - 1]
assert(node.type === 'paragraph')
const head = node.children[0]
if (head && head.type === 'text') {
const siblings = parent.children
let index = -1
/** @type {Paragraph | undefined} */
let firstParaghraph
while (++index < siblings.length) {
const sibling = siblings[index]
if (sibling.type === 'paragraph') {
firstParaghraph = sibling
break
}
}
if (firstParaghraph === node) {
// Must start with a space or a tab.
head.value = head.value.slice(1)
if (head.value.length === 0) {
node.children.shift()
} else if (
node.position &&
head.position &&
typeof head.position.start.offset === 'number'
) {
head.position.start.column++
head.position.start.offset++
node.position.start = Object.assign({}, head.position.start)
}
}
}
}
this.exit(token)
}
/**
* @type {ToMarkdownHandle}
* @param {ListItem} node
*/
function listItemWithTaskListItem(node, parent, state, info) {
const head = node.children[0]
const checkable =
typeof node.checked === 'boolean' && head && head.type === 'paragraph'
const checkbox = '[' + (node.checked ? 'x' : ' ') + '] '
const tracker = state.createTracker(info)
if (checkable) {
tracker.move(checkbox)
}
let value = defaultHandlers.listItem(node, parent, state, {
...info,
...tracker.current()
})
if (checkable) {
value = value.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/, check)
}
return value
/**
* @param {string} $0
* @returns {string}
*/
function check($0) {
return $0 + checkbox
}
}