3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-13 04:08:29 +01:00

Answer prose round 1: a task item keeps the task lists right after its paragraphs nested, and the README, changelog, decision, comment, item 49 and a test title say it plainly

This commit is contained in:
2026-10-09 13:27:40 +02:00
committed by Lillem4n
parent 0c56a479bb
commit cd6cb8bece
9 changed files with 32 additions and 28 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ test('a generated document emits markdown that reads back to it', { timeout: pro
)
})
test('a generated document writes portable markdown refusing only what the guard refuses, and that markdown reads back to its text and to itself, a block task item holding paragraphs alone', { timeout: propertyTimeout }, () => {
test('a generated document writes portable markdown refusing only what the guard refuses, and that markdown reads back to its text, to itself and to block task items of paragraphs alone', { timeout: propertyTimeout }, () => {
fc.assert(
fc.property(adfDocument, (document) => {
const written = adfToPortableMarkdown(document)
+1 -5
View File
@@ -198,7 +198,6 @@ function filledNode(node: AdfNode, content: Result<AdfNode[]>): Result<AdfNode>
return success({ ...node, content: content.value.length === 0 ? [{ type: 'paragraph' }] : content.value })
}
// A task list trailing an item's blocks stands beside it, as ADF nests one.
function bulletNode(items: readonly Block[][], reading: Reading, path: ConvertErrorPath, depth: number): Result<AdfNode> {
const led = []
for (const [first, ...others] of items) {
@@ -212,10 +211,7 @@ function bulletNode(items: readonly Block[][], reading: Reading, path: ConvertEr
const marked: TaskParagraph[] = body === '' ? [] : [{ kind: 'taskParagraph', position, text: body }]
const read = readBlocks([...marked, ...others], reading, [...path, 'content', index], depth + 1)
if (!read.ok) return read
let beside = read.value.length
while (read.value[beside - 1]?.type === 'taskList') beside -= 1
tasks.push({ attrs: { state: marker.state }, content: read.value.slice(0, beside), type: 'blockTaskItem' })
for (const nested of read.value.slice(beside)) tasks.push(nested)
tasks.push({ attrs: { state: marker.state }, content: read.value, type: 'blockTaskItem' })
}
return success({ content: tasks, type: 'taskList' })
}
@@ -167,6 +167,11 @@ test('stands a block a task item cannot hold, and what follows it in the item, a
bare('codeBlock', text('x')),
])
assert.deepEqual(read('- [x] A\n - plain\n- [ ] B\n'), [bare('taskList', task('DONE', text('A'))), bare('bulletList', bare('listItem', said('plain'))), bare('taskList', task('TODO', text('B')))])
assert.deepEqual(read('- [ ] A\n - [ ] Child\n\n # h\n- [ ] B\n'), [
bare('taskList', task('TODO', text('A')), bare('taskList', task('TODO', text('Child')))),
node('heading', { level: 1 }, text('h')),
bare('taskList', task('TODO', text('B'))),
])
assert.deepEqual(read('- [ ] A\n\n # h\n - [ ] Child\n- [ ] B\n'), [
bare('taskList', task('TODO', text('A'))),
node('heading', { level: 1 }, text('h')),
@@ -304,17 +304,17 @@ function reduceTaskList(node: AdfNode, reduction: Reduction): Result<AdfNode[]>
continue
}
if (isTask(child)) {
nestIn(tasks, nested)
pushMerged(tasks, nested)
nested = []
}
for (const block of blocks) (isTask(child) ? tasks : nested).push(block)
}
nestIn(tasks, nested)
pushMerged(tasks, nested)
if (regular) return success(splitTaskList({ content: tasks, type: 'taskList' }))
return success(listOf(nodeContent(tasksAsText({ content: tasks, type: 'taskList' })), 'bulletList'))
}
function nestIn(tasks: AdfNode[], nested: readonly AdfNode[]): void {
function pushMerged(tasks: AdfNode[], nested: readonly AdfNode[]): void {
for (const block of mergedLists(nested)) tasks.push(block)
}
+8 -4
View File
@@ -1,7 +1,7 @@
import type { AdfNode } from '../../adf/document.ts'
import { nodeContent } from '../../adf/document.ts'
// Atlassian's schema holds a block task item to paragraphs: from its first other block, the item's blocks and the lists beside it stand after the list, which resumes at the next task.
// Atlassian's schema holds a blockTaskItem to paragraphs and extensions, and nests a task list beside its item.
export function splitTaskList(list: AdfNode): AdfNode[] {
const blocks: AdfNode[] = []
let tasks: AdfNode[] = []
@@ -12,9 +12,13 @@ export function splitTaskList(list: AdfNode): AdfNode[] {
let stands: readonly AdfNode[] = standing || !(isTask || child.type === 'taskList') ? [child] : []
if (child.type === 'blockTaskItem') {
const content = nodeContent(child)
const held = content.findIndex((block) => block.type !== 'paragraph')
tasks.push(heldTask(child, held === -1 ? content : content.slice(0, held)))
stands = held === -1 ? [] : content.slice(held)
let held = 0
while (content[held]?.type === 'paragraph') held += 1
let nested = held
while (content[nested]?.type === 'taskList') nested += 1
tasks.push(heldTask(child, content.slice(0, held)))
for (const taskList of content.slice(held, nested)) tasks.push(taskList)
stands = content.slice(nested)
} else if (stands.length === 0) tasks.push(child)
if (stands.length === 0) continue
if (tasks.length > 0) blocks.push({ ...list, content: tasks })