diff --git a/src/markdown/portable/adf-to-portable-markdown.ts b/src/markdown/portable/adf-to-portable-markdown.ts index 2b4bff6..de11ad3 100644 --- a/src/markdown/portable/adf-to-portable-markdown.ts +++ b/src/markdown/portable/adf-to-portable-markdown.ts @@ -275,7 +275,7 @@ function reduceTaskList(node: AdfNode, reduction: Reduction): Result const standsAlone = !nestsTasks && !isTaskItem(child) && child.type !== 'taskList' for (const block of standsAlone ? [{ content: blocks, type: 'listItem' }] : blocks) tasks.push(block) } - const items = nestsTasks ? taskListItems({ content: tasks, type: 'taskList' }) : undefined + const items = nestsTasks ? taskListItems(tasks) : undefined return success(items === undefined ? listOf(tasksAsText(tasks), 'bulletList') : splitTaskList(items)) } diff --git a/src/markdown/portable/block-spelling.test.ts b/src/markdown/portable/block-spelling.test.ts index e889644..bba2051 100644 --- a/src/markdown/portable/block-spelling.test.ts +++ b/src/markdown/portable/block-spelling.test.ts @@ -4,16 +4,8 @@ import test from 'node:test' import { portableWriting } from './block-spelling.ts' import { writeMarkdown } from '../emit/adf-to-markdown.ts' -test('refuses a task list a child other than a task opens, where the portable reduction would have left none', () => { +test('refuses a task list that opens with a child other than a task', () => { const list = { content: [{ content: [{ text: 'stray', type: 'text' }], type: 'paragraph' }, { attrs: { state: 'TODO' }, type: 'taskItem' }], type: 'taskList' } const written = writeMarkdown({ content: [{ type: 'rule' }, list], type: 'doc', version: 1 }, portableWriting) assert.deepEqual(written.ok ? written.value : [written.error.code, written.error.path], ['unsupported-node-shape', ['content', 1]]) }) - -test('names the path of a failure inside a task list by the child it writes', () => { - const task = { attrs: { state: 'TODO' }, content: [{ text: 't', type: 'text' }], type: 'taskItem' } - const unwritable = { attrs: { state: 'TODO' }, content: [{ marks: [{ type: 'strong' }, { type: 'strong' }], text: 'x', type: 'text' }], type: 'taskItem' } - const list = { content: [task, { content: [task], type: 'taskList' }, unwritable], type: 'taskList' } - const written = writeMarkdown({ content: [list], type: 'doc', version: 1 }, portableWriting) - assert.deepEqual(written.ok ? written.value : written.error.path, ['content', 0, 'content', 2, 'content', 0]) -}) diff --git a/src/markdown/portable/block-spelling.ts b/src/markdown/portable/block-spelling.ts index 554f80b..d4709b5 100644 --- a/src/markdown/portable/block-spelling.ts +++ b/src/markdown/portable/block-spelling.ts @@ -6,7 +6,6 @@ import { blockText, emptyLine, joinedText, opening, prefixedText, type BlockText import { emitBlock, joinBlocks, quotedBlocks, spelledList, walkBlocks } from '../emit/adf-to-markdown.ts' import { emitInlineLine } from '../emit/inline-line.ts' import { failure, success } from '../../result.ts' -import { isTaskItem } from '../../adf/block-nodes.ts' import { largestNesting } from '../../nesting.ts' import { nodeAttrs, nodeContent } from '../../adf/document.ts' import { taskListItems } from './task-list.ts' @@ -41,21 +40,18 @@ function headed(head: string): (body: BlockText) => BlockText { } function tryTaskList(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing): Result { - const items = taskListItems(node) - // The portable reduction opens every task list with a task: a child nesting in no task would have no list item to stand in. + const items = taskListItems(nodeContent(node)) + // The portable reduction opens every task list with a task. if (items === undefined) return failure('unsupported-node-shape', 'a task list opens with a child other than a task', path) const inners: BlockText[] = [] let headroom = largestNesting - depth - 1 - let index = 0 for (const { after, task } of items) { const blocks: PlacedBlock[] = [] - for (const child of [task, ...after]) { - const childPath = [...path, 'content', index] - const walk = isTaskItem(child) ? taskBlocks(child, childPath, depth + 1, writing) : placedBlock(child, childPath, depth + 1, writing) + const walks = [taskBlocks(task, path, depth + 1, writing), ...after.map((child) => placedBlock(child, path, depth + 1, writing))] + for (const walk of walks) { if (!walk.ok) return walk headroom = Math.min(headroom, walk.value.headroom) for (const block of walk.value.blocks) blocks.push(block) - index += 1 } inners.push(joinBlocks(blocks, 'list-item')) } diff --git a/src/markdown/portable/task-list.test.ts b/src/markdown/portable/task-list.test.ts index 92cbd52..2bfe955 100644 --- a/src/markdown/portable/task-list.test.ts +++ b/src/markdown/portable/task-list.test.ts @@ -26,15 +26,15 @@ test('groups a task list into tasks, each with the children after it up to the n const first: AdfNode = { attrs: { state: 'TODO' }, type: 'taskItem' } const nested: AdfNode = { content: [first], type: 'taskList' } const second: AdfNode = { attrs: { state: 'DONE' }, content: [{ type: 'paragraph' }], type: 'blockTaskItem' } - assert.deepEqual(taskListItems({ content: [first, nested, nested, second], type: 'taskList' }), [ + assert.deepEqual(taskListItems([first, nested, nested, second]), [ { after: [nested, nested], task: first }, { after: [], task: second }, ]) - assert.deepEqual(taskListItems({ type: 'taskList' }), []) + assert.deepEqual(taskListItems([]), []) }) -test('groups no task list a child other than a task opens', () => { +test('returns no items for a task list that opens with a child other than a task', () => { const task: AdfNode = { attrs: { state: 'TODO' }, type: 'taskItem' } - assert.equal(taskListItems({ content: [{ type: 'paragraph' }, task], type: 'taskList' }), undefined) - assert.equal(taskListItems({ content: [{ content: [task], type: 'taskList' }, task], type: 'taskList' }), undefined) + assert.equal(taskListItems([{ type: 'paragraph' }, task]), undefined) + assert.equal(taskListItems([{ content: [task], type: 'taskList' }, task]), undefined) }) diff --git a/src/markdown/portable/task-list.ts b/src/markdown/portable/task-list.ts index 02a729b..0b61af2 100644 --- a/src/markdown/portable/task-list.ts +++ b/src/markdown/portable/task-list.ts @@ -11,7 +11,7 @@ export function splitTaskList(items: readonly TaskListItem[]): AdfNode[] { const { kept, rest } = itemParts(item) for (const node of kept) tasks.push(node) if (rest.length === 0) continue - if (tasks.length > 0) blocks.push({ content: tasks, type: 'taskList' }) + blocks.push({ content: tasks, type: 'taskList' }) tasks = [] for (const block of rest) blocks.push(block) } @@ -19,13 +19,12 @@ export function splitTaskList(items: readonly TaskListItem[]): AdfNode[] { return blocks } -// A task and the children after it up to the next task, which nest in it. +// A task, then every child before the next task. export type TaskListItem = { after: AdfNode[]; task: AdfNode } -// The list's items, or undefined where a child other than a task opens it. -export function taskListItems(list: AdfNode): TaskListItem[] | undefined { +export function taskListItems(children: readonly AdfNode[]): TaskListItem[] | undefined { const items: TaskListItem[] = [] - for (const child of nodeContent(list)) { + for (const child of children) { const item = items.at(-1) if (isTaskItem(child)) items.push({ after: [], task: child }) else if (item === undefined) return undefined