You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
4.7 KiB
TypeScript

// @ts-nocheck
import markdown from 'markdown-it'
import hljs from 'highlight.js'
const PATTERN = /^\[(?:[\s_-])?([Xx])?\]\s(.*)/
const ALL_BOXES = /(?:-|\*|\d+\.)\s+\[(?:[\s_-])?([Xx])?\]\s/g
function taskify(
md,
{ labelClass, itemClass, listClass, checkboxClass, readOnly = true } = {}
) {
const checkboxAttrs = {
type: 'checkbox',
class: checkboxClass,
disabled: readOnly,
}
md.core.ruler.push('tasks', (state) => {
const allBoxes = [...state.src.matchAll(ALL_BOXES)].map(
(match) => match.index + match[0].indexOf('[') + 1
)
if (allBoxes.length === 0) return
const { Token, tokens } = state
let lists
for (let index = 0; index < tokens.length; index += 1) {
// Is this a checkbox?
if (
// must be an inline token
tokens[index].type !== 'inline' ||
// must have text child
tokens[index].children[0].type !== 'text' ||
// whose content matches the pattern
!PATTERN.test(tokens[index].children[0].content) ||
// whose immediate parent is a paragraph
tokens[index - 1]?.type !== 'paragraph_open' ||
// whose immediate parent's immediate parent is a list item
tokens[index - 2].type !== 'list_item_open'
) {
continue
}
// Grab the first child of children, the rest should also be within the label
const [first, ...rest] = tokens[index].children
// Grab the checkmark off the text node, leave the label
const [checked, initLabel] = [
...first.content.match(PATTERN),
].slice(1)
tokens[index].children = [
// This list will be post-processed into nodes
// args are type, tag, nesting, attributes, content
// Things that are already tokens just slide right in.
['label_open', 'label', 1, { class: labelClass }],
[
'checkbox_input',
'input',
0,
{
...checkboxAttrs,
'checked': !!checked,
'data-pos': allBoxes.shift(),
},
],
initLabel && ['text', '', 0, null, initLabel],
...rest,
['label_close', 'label', -1],
]
.filter((a) => !!a)
.map((spec) =>
Array.isArray(spec)
? Object.assign(new Token(...spec.slice(0, 3)), {
attrs: Object.keys(spec[3] ?? {})
.filter((key) => !!spec[3][key])
.map((key) => [key, String(spec[3][key])]),
content: spec[4],
})
: spec
)
if (itemClass) {
// index - 2 is the list item
tokens[index - 2].attrs = [
...(tokens[index - 2].attrs || []),
['class', itemClass],
]
}
if (listClass) {
// locate the list item's parent list and add it to a Set for decoration
if (!lists) lists = new Set()
let cur = index - 2
let listLevel = tokens[cur].level - 1
for (; cur >= 0 && tokens[cur].level !== listLevel; cur -= 1);
if (cur !== -1) {
lists.add(tokens[cur])
}
}
}
// Decorate all the lists we've collated.
if (listClass) {
for (const list of lists) {
list.attrs = [...(list.attrs || []), ['class', listClass]]
}
}
})
}
const stringToMarkdown = (string: string) => {
const md = markdown({
highlight: (code: string, lang: string) => {
if (lang && hljs.getLanguage(lang)) {
try {
return (
'<pre class="hljs"><code>' +
hljs.highlight(code, {
language: lang,
ignoreIllegals: true,
}).value +
'</code></pre>'
)
} catch (__) { }
}
return (
'<pre class="hljs"><code>' +
md.utils.escapeHtml(code) +
'</code></pre>'
)
},
}).use(taskify)
const parsedMarkdown = md.render(string)
return parsedMarkdown
}
export { stringToMarkdown }