10c - adfToPlainMarkdown and plainMarkdownToAdf exported, whitespace-only leaves trimmed at a line end
CI / gate (push) Successful in 41s
CI / publish (push) Has been skipped

This commit is contained in:
2026-09-26 13:20:16 +02:00
parent 2f03e20549
commit 613dc0edbb
11 changed files with 100 additions and 12 deletions
+3 -2
View File
@@ -221,10 +221,11 @@ function leafEdges(leaf: AdfNode, previous: AdfNode | undefined, next: AdfNode |
const text = leaf.text
if (text === undefined || marks.some((mark) => mark.type === 'code')) return undefined
const lead = text.slice(0, text.search(/[^ \t]|$/))
const trail = lead === text ? '' : text.slice(text.search(/[ \t]*$/))
const trail = text.slice(Math.max(lead.length, text.search(/[ \t]*$/)))
const leadDepth = edgeDepth(marks, previous, lead)
const trailDepth = edgeDepth(marks, next, trail)
const trailDepth = edgeDepth(marks, next, lead === text ? text : trail)
if (leadDepth === marks.length && trailDepth === marks.length) return undefined
if (lead === text) return leadDepth === undefined || trailDepth === undefined ? [] : [textLeaf(text, marks.slice(0, Math.min(leadDepth, trailDepth)))]
const edges: AdfNode[] = []
if (lead !== '' && leadDepth !== undefined) edges.push(textLeaf(lead, marks.slice(0, leadDepth)))
const core = text.slice(lead.length, text.length - trail.length)
@@ -229,6 +229,7 @@ test('spells a node no row names, or one standing where no spelling holds it, as
assert.equal(plain(bulletList(said('stray'), item(said('b')), text('loose'))), '- stray\n- b\n- loose\n')
assert.equal(plain(bulletList()), '')
assert.equal(plain(bulletList(item({ content: [text('a\n \nb')], type: 'codeBlock' }))), '- ```\n a\n\n b\n ```\n')
assert.equal(plain(bulletList(item({ content: [text(' ')], type: 'codeBlock' }))), '- ```\n ```\n')
assert.equal(plain(bulletList(item(node('rule', {}), node('rule', {}), said('Install')), item(said('Configure')))), '- Install\n- Configure\n')
assert.equal(plain(bulletList(item(node('rule', {}), node('rule', {})), item(said('Configure')))), '-\n- Configure\n')
assert.equal(plain(bulletList(item(bulletList(item(bulletList(item())))))), '- -\n')
@@ -285,6 +286,7 @@ test('breaks a line at a newline and trims whitespace at every edge CommonMark s
assert.equal(plain(paragraph(text(' \n a \n b\n'))), 'a\\\nb\n')
assert.equal(plain(paragraph({ type: 'hardBreak' }, text('a'), { attrs: { text: '\n' }, type: 'hardBreak' }, text('b'), { type: 'hardBreak' })), 'a\\\nb\n')
assert.equal(plain(paragraph(text('a'), text(' b ', strong), text('c'))), 'a **b** c\n')
assert.equal(plain(paragraph(text(' '), text('b', strong), text(' \n'), text('c', strong), text(' '))), '**b**\\\n**c**\n')
assert.equal(plain(paragraph(text('a'), text(' b ', em, strong), text(' ', em), text('c', em))), 'a _**b** c_\n')
assert.equal(plain(paragraph(text(' x ', code))), '` x `\n')
assert.equal(plain(node('heading', { level: 1 }, text(' h\ni '))), '# h i\n')
+10 -4
View File
@@ -2,7 +2,7 @@ import type { AdfDocument, AdfNode } from '../../adf/document.ts'
import { adfDocumentFault, nodeAttrs, nodeContent } from '../../adf/document.ts'
import { alertMarker, foldedAlertMarker, taskMarker } from '../plain-conventions.ts'
import { blockNodeModel } from '../../adf/block-nodes.ts'
import { commonMarkSpelling, largestListMarker, type SpellingMemo } from './adf-to-markdown.ts'
import { adfToMarkdown, commonMarkSpelling, largestListMarker, type SpellingMemo } from './adf-to-markdown.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { inlineLeaves, isBlockNodeType, oneLine, reduceInline, writableHref } from './plain-inline.ts'
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
@@ -41,6 +41,11 @@ const blockReducers: Readonly<Record<string, BlockReducer>> = {
taskList: reduceTaskList,
}
export function adfToPlainMarkdown(document: AdfDocument): Result<string> {
const reduced = reduceToPlain(document)
return reduced.ok ? adfToMarkdown(reduced.value) : reduced
}
export function reduceToPlain(document: AdfDocument): Result<AdfDocument> {
const fault = adfDocumentFault(document)
if (fault !== undefined) return faulted(fault, [])
@@ -224,12 +229,13 @@ function listItem(blocks: Result<AdfNode[]>): Result<AdfNode[]> {
// A list item's first line reads as no rule and holds no line of spaces alone: the rule and the spaces give way.
function itemOf(blocks: readonly AdfNode[]): AdfNode {
const rules = blocks.findIndex((block) => block.type !== 'rule')
const content = blocks.slice(rules === -1 ? blocks.length : rules).map((block) => (block.type === 'codeBlock' ? { ...block, content: nodeContent(block).map(blankedLines) } : block))
const content = blocks.slice(rules === -1 ? blocks.length : rules).map((block) => (block.type === 'codeBlock' ? { ...block, content: blankedLines(nodeContent(block)) } : block))
return { content, type: 'listItem' }
}
function blankedLines(code: AdfNode): AdfNode {
return code.text === undefined ? code : { ...code, text: code.text.replace(/^[ \t]+$/gm, '') }
function blankedLines(code: readonly AdfNode[]): AdfNode[] {
const blanked = code.map((leaf) => leaf.text ?? '').join('').replace(/^[ \t]+$/gm, '')
return blanked === '' ? [] : [text(blanked)]
}
function reduceTaskList(node: AdfNode, reduction: Reduction): Result<AdfNode[]> {