diff --git a/packages/slidev/node/syntax/index.ts b/packages/slidev/node/syntax/index.ts index b3089c6035..41b083030c 100644 --- a/packages/slidev/node/syntax/index.ts +++ b/packages/slidev/node/syntax/index.ts @@ -2,7 +2,6 @@ import type { CodeblockTransformer, ResolvedSlidevOptions } from '@slidev/types' import type MagicString from 'magic-string' import type MarkdownExit from 'markdown-exit' import MarkdownItComark from '@comark/markdown-it' -import { taskLists as MarkdownItTaskList } from '@hedgedoc/markdown-it-plugins' // @ts-expect-error missing types import MarkdownItFootnote from 'markdown-it-footnote' import MarkdownItGitHubAlerts from 'markdown-it-github-alerts' @@ -15,6 +14,7 @@ import MarkdownItStyleScoped from './scoped' import MarkdownItShiki from './shiki' import MarkdownItSlotSugar from './slot-sugar' import MarkdownItSnippet from './snippet' +import MarkdownItTaskList from './task-list' export async function useMarkdownItPlugins( md: MarkdownExit, diff --git a/packages/slidev/node/syntax/task-list.test.ts b/packages/slidev/node/syntax/task-list.test.ts new file mode 100644 index 0000000000..5dcc5c4033 --- /dev/null +++ b/packages/slidev/node/syntax/task-list.test.ts @@ -0,0 +1,17 @@ +import MarkdownItComark from '@comark/markdown-it' +import MarkdownExit from 'markdown-exit' +import { expect, it } from 'vitest' +import MarkdownItTaskList from './task-list' + +it('does not render task list markers when comark is enabled', async () => { + const md = MarkdownExit({ html: true }) + md.use(MarkdownItTaskList, { enabled: true, lineNumber: true, label: true }) + md.use(MarkdownItComark) + + const result = await md.renderAsync('- [x] test\n- [ ] test') + + expect(result).toContain('') + expect(result).toContain('') + expect(result).not.toContain('x') + expect(result).not.toContain(' ') +}) diff --git a/packages/slidev/node/syntax/task-list.ts b/packages/slidev/node/syntax/task-list.ts new file mode 100644 index 0000000000..112446be04 --- /dev/null +++ b/packages/slidev/node/syntax/task-list.ts @@ -0,0 +1,51 @@ +import type { MarkdownExit } from 'markdown-exit' +import { taskLists as HedgeDocMarkdownItTaskList } from '@hedgedoc/markdown-it-plugins' + +type TaskListOptions = Parameters[1] +type Token = ReturnType[number] + +const TASK_LIST_MARKER_RE = /^\[[ x]\]\s/i +const COMARK_TASK_LIST_MARKER_RE = /^[ x]$/i + +export default function MarkdownItTaskList(md: MarkdownExit, options?: TaskListOptions) { + md.use(HedgeDocMarkdownItTaskList, options) + + md.core.ruler.after('task-lists', 'slidev_task_list_comark', (state) => { + for (const token of state.tokens) { + if (token.type !== 'inline' || !TASK_LIST_MARKER_RE.test(token.content)) + continue + + removeLeadingComarkMarker(token.children) + } + }) +} + +function removeLeadingComarkMarker(children?: Token[]) { + if (!children?.length) + return + + let index = children.findIndex(child => child.type === 'taskListItemCheckbox') + if (index < 0) + return + + index += 1 + if (children[index]?.type === 'taskListItemLabel_open') + index += 1 + + if ( + children[index]?.type !== 'mdc_inline_span' + || children[index]?.nesting !== 1 + || children[index + 1]?.type !== 'text' + || !COMARK_TASK_LIST_MARKER_RE.test(children[index + 1].content) + || children[index + 2]?.type !== 'mdc_inline_span' + || children[index + 2]?.nesting !== -1 + ) { + return + } + + children.splice(index, 3) + + const next = children[index] + if (next?.type === 'text' && next.content.startsWith(' ')) + next.content = next.content.slice(1) +}