feat(markdown): support GitHub-style task lists

Renders `- [ ] task` syntax via `@mdit/plugin-tasklist`, enabled by
default and configurable through the new `tasklist` markdown option
(set to `false` to disable). The default theme hides list markers for
task items and pulls checkboxes into the list gutter, GitHub-style.

close #413
close #1923
close #3648
close #5110

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/5335/head
Divyansh Singh 2 months ago
parent 18380b0eb7
commit 97f87817ea

@ -42,6 +42,15 @@ describe('node/markdown/markdown', () => {
expect(await render(':tada:', { emoji: false })).toContain(':tada:') expect(await render(':tada:', { emoji: false })).toContain(':tada:')
}) })
test('tasklist', async () => {
const src = '- [ ] todo'
expect(await render(src)).toContain('<input type="checkbox"')
const disabled = await render(src, { tasklist: false })
expect(disabled).not.toContain('<input')
expect(disabled).toContain('[ ] todo')
})
test('toc', async () => { test('toc', async () => {
const src = '# Title\n\n[[toc]]' const src = '# Title\n\n[[toc]]'
expect(await render(src)).toContain('table-of-contents') expect(await render(src)).toContain('table-of-contents')
@ -139,6 +148,26 @@ describe('node/markdown/markdown', () => {
}) })
}) })
describe('tasklist', () => {
test('renders checkboxes with their checked state', async () => {
const html = await render('- [ ] todo\n- [x] done')
expect(html).toContain('<ul class="task-list-container">')
expect(html).toContain('<li class="task-list-item">')
const inputs = html.match(/<input[^>]*>/g)!
expect(inputs).toHaveLength(2)
expect(inputs[0]).not.toContain('checked')
expect(inputs[1]).toContain('checked')
for (const input of inputs) expect(input).toContain('disabled')
})
test('forwards options to the plugin', async () => {
const html = await render('- [ ] todo', { tasklist: { label: false } })
expect(html).toContain('<input type="checkbox"')
expect(html).not.toContain('<label')
})
})
// attrs applies at a fixed position in the core chain (before linkify), // attrs applies at a fixed position in the core chain (before linkify),
// while anchor pushes to its end, so anchor always sees user-defined ids // while anchor pushes to its end, so anchor always sees user-defined ids
// no matter which plugin is registered first // no matter which plugin is registered first

@ -100,6 +100,20 @@ For more details, see [Frontmatter](../reference/frontmatter-config).
| col 2 is | centered | \$12 | | col 2 is | centered | \$12 |
| zebra stripes | are neat | \$1 | | zebra stripes | are neat | \$1 |
## Task Lists
**Input**
```md
- [ ] Write the press release
- [x] Update the website
```
**Output**
- [ ] Write the press release
- [x] Update the website
## Emoji :tada: ## Emoji :tada:
**Input** **Input**

@ -132,6 +132,7 @@
"@mdit/plugin-anchor": "^1.1.1", "@mdit/plugin-anchor": "^1.1.1",
"@mdit/plugin-attrs": "^1.0.2", "@mdit/plugin-attrs": "^1.0.2",
"@mdit/plugin-emoji": "^1.1.0", "@mdit/plugin-emoji": "^1.1.0",
"@mdit/plugin-tasklist": "^1.0.1",
"@polka/compression": "^1.0.0-next.28", "@polka/compression": "^1.0.0-next.28",
"@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-alias": "^6.0.0",
"@rollup/plugin-commonjs": "^29.0.3", "@rollup/plugin-commonjs": "^29.0.3",

@ -116,6 +116,9 @@ importers:
'@mdit/plugin-emoji': '@mdit/plugin-emoji':
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.1.0(markdown-it@14.2.0) version: 1.1.0(markdown-it@14.2.0)
'@mdit/plugin-tasklist':
specifier: ^1.0.1
version: 1.0.1(markdown-it@14.2.0)
'@polka/compression': '@polka/compression':
specifier: ^1.0.0-next.28 specifier: ^1.0.0-next.28
version: 1.0.0-next.28 version: 1.0.0-next.28
@ -660,6 +663,15 @@ packages:
markdown-it: markdown-it:
optional: true optional: true
'@mdit/plugin-tasklist@1.0.1':
resolution: {integrity: sha512-Ks/Tihw5ibbkP2qOZzltkbiTk8RfpxDRvqdn4hl1wIs3VmeKITIQ36gN4DsuotSauA9SBe/mL0QJIfuSCrXqrw==}
engines: {node: '>=22'}
peerDependencies:
markdown-it: ^14.2.0
peerDependenciesMeta:
markdown-it:
optional: true
'@napi-rs/wasm-runtime@1.1.6': '@napi-rs/wasm-runtime@1.1.6':
resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==}
peerDependencies: peerDependencies:
@ -3259,6 +3271,12 @@ snapshots:
optionalDependencies: optionalDependencies:
markdown-it: 14.2.0 markdown-it: 14.2.0
'@mdit/plugin-tasklist@1.0.1(markdown-it@14.2.0)':
dependencies:
'@types/markdown-it': 14.1.2
optionalDependencies:
markdown-it: 14.2.0
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies: dependencies:
'@emnapi/core': 1.11.1 '@emnapi/core': 1.11.1

@ -163,6 +163,16 @@
margin: 8px 0 0; margin: 8px 0 0;
} }
.vp-doc li.task-list-item {
list-style: none;
}
.vp-doc .task-list-item-checkbox {
margin: 0 4px 2px -1.25rem;
vertical-align: middle;
accent-color: var(--vp-c-brand-1);
}
/** /**
* Table * Table
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */

@ -20,6 +20,10 @@ import {
type MarkdownItAttrsOptions type MarkdownItAttrsOptions
} from '@mdit/plugin-attrs' } from '@mdit/plugin-attrs'
import { fullEmoji as emojiPlugin } from '@mdit/plugin-emoji' import { fullEmoji as emojiPlugin } from '@mdit/plugin-emoji'
import {
tasklist as tasklistPlugin,
type MarkdownItTaskListOptions
} from '@mdit/plugin-tasklist'
import type { import type {
CodeToHastOptions, CodeToHastOptions,
LanguageInput, LanguageInput,
@ -193,6 +197,12 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
* @see https://mdit-plugins.github.io/emoji.html * @see https://mdit-plugins.github.io/emoji.html
*/ */
emoji?: EmojiPluginOptions | false emoji?: EmojiPluginOptions | false
/**
* Options for `@mdit/plugin-tasklist` (GitHub-style task lists,
* `- [ ] task`). Set to `false` to disable.
* @see https://mdit-plugins.github.io/tasklist.html
*/
tasklist?: MarkdownItTaskListOptions | false
/** /**
* Improves emphasis (`**bold**`) handling in Japanese, Chinese, and * Improves emphasis (`**bold**`) handling in Japanese, Chinese, and
* Korean text. * Korean text.
@ -365,6 +375,9 @@ export async function createMarkdownRenderer(
if (options.emoji !== false) { if (options.emoji !== false) {
emojiPlugin(md, options.emoji) emojiPlugin(md, options.emoji)
} }
if (options.tasklist !== false) {
tasklistPlugin(md, options.tasklist)
}
if (options.cjkFriendlyEmphasis !== false) { if (options.cjkFriendlyEmphasis !== false) {
mditCjkFriendly(md) mditCjkFriendly(md)
} }

Loading…
Cancel
Save