3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-14 04:30:24 +01:00

Read an image alone on an alert's marker line or the lines after it as the image, as a reader panel expected; item 88 filed

This commit is contained in:
2026-10-09 10:23:28 +02:00
committed by Lillem4n
parent ee3086ad1e
commit d677222050
6 changed files with 41 additions and 18 deletions
+15 -10
View File
@@ -20,10 +20,12 @@ import { readBlockDirectiveNode } from './directive-nodes.ts'
import { spellStringAttribute, unsupportedNodeShape } from '../directive-syntax.ts'
import { spellsEmpty } from '../empty-keys.ts'
// A paragraph a marker opens: it shares its markdown paragraph with the marker, so no image there stands alone.
type MarkerParagraph = { kind: 'markerParagraph'; position: SourcePosition; text: string }
type Paragraph = Extract<Block, { kind: 'paragraph' }>
type ReadBlock = Block | MarkerParagraph
// The paragraph a task marker opens: a task item holds no image, so a lone one there reads as its linked alt text.
type TaskParagraph = { kind: 'taskParagraph'; position: SourcePosition; text: string }
type ReadBlock = Block | TaskParagraph
// `inExpand` is whether an expand holds the blocks, which makes a folded callout a nestedExpand.
type Reading = { claims: Claims; definitions: LinkDefinitions; inExpand: boolean; memo: SpellingMemo }
@@ -123,8 +125,6 @@ function readBlock(block: ReadBlock, reading: Reading, path: ConvertErrorPath, d
return contentNode({ attrs: { level: block.level }, type: 'heading' }, block.text, reading, path, 'heading')
case 'html':
return failure('unmappable-html', `no raw HTML converts at this version: ${block.construct}`, path)
case 'markerParagraph':
return contentNode({ type: 'paragraph' }, block.text, reading, path, 'paragraph')
case 'orderedList':
return listNode({ attrs: { order: block.start }, type: 'orderedList' }, block.items, reading, path, depth)
case 'paragraph':
@@ -133,6 +133,8 @@ function readBlock(block: ReadBlock, reading: Reading, path: ConvertErrorPath, d
return success({ type: 'rule' })
case 'table':
return tableNode(block.rows, reading, path)
case 'taskParagraph':
return contentNode({ type: 'paragraph' }, block.text, reading, path, 'paragraph')
}
}
@@ -149,8 +151,9 @@ function markerLine(text: string): { line: string; rest: string } {
return { line: (hardBreak ? line.slice(0, -1) : line).replace(/^[ \t]+/, ''), rest: lineEnd === -1 ? '' : text.slice(lineEnd + 1) }
}
function markerParagraphs(position: SourcePosition, ...texts: string[]): MarkerParagraph[] {
return texts.filter((text) => text !== '').map((text) => ({ kind: 'markerParagraph', position, text }))
// docs/decisions.md, An image reads as an image only alone in its paragraph: an alert's marker line stands apart from the lines after it.
function paragraphsOf(position: SourcePosition, ...texts: string[]): Paragraph[] {
return texts.filter((text) => text !== '').map((text) => ({ kind: 'paragraph', position, text }))
}
function quoteNode(blocks: readonly Block[], reading: Reading, path: ConvertErrorPath, depth: number): Result<AdfNode> {
@@ -159,12 +162,12 @@ function quoteNode(blocks: readonly Block[], reading: Reading, path: ConvertErro
if (led === undefined) return containerNode({ type: 'blockquote' }, blocks, reading, path, depth)
const { folded, panelType } = led.marker
const { line, rest } = markerLine(led.text)
if (!folded) return filledNode({ attrs: { panelType }, type: 'panel' }, readBlocks([...markerParagraphs(led.position, line, rest), ...body], reading, path, depth + 1))
if (!folded) return filledNode({ attrs: { panelType }, type: 'panel' }, readBlocks([...paragraphsOf(led.position, line, rest), ...body], reading, path, depth + 1))
const title = parseInlineContent(line, reading.definitions, path, 'paragraph', reading.claims)
if (!title.ok) return title
const text = titleText(title.value.nodes)
const type = reading.inExpand ? 'nestedExpand' : 'expand'
return filledNode(text === '' ? { type } : { attrs: { title: text }, type }, readBlocks([...markerParagraphs(led.position, rest), ...body], { ...reading, inExpand: true }, path, depth + 1))
return filledNode(text === '' ? { type } : { attrs: { title: text }, type }, readBlocks([...paragraphsOf(led.position, rest), ...body], { ...reading, inExpand: true }, path, depth + 1))
}
// docs/decisions.md, A callout title keeps its link targets.
@@ -203,7 +206,9 @@ function bulletNode(items: readonly Block[][], reading: Reading, path: ConvertEr
}
const tasks: AdfNode[] = []
for (const [index, { marker, others, position, text }] of led.entries()) {
const read = readBlocks([...markerParagraphs(position, text.replace(/^(?:[ \t\n]|\\\n)+/, '')), ...others], reading, [...path, 'content', index], depth + 1)
const body = text.replace(/^(?:[ \t\n]|\\\n)+/, '')
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
@@ -231,12 +231,15 @@ test('reads what CommonMark reads where no row reads, a directive and a carry fe
assert.deepEqual(read('a !adf:carry{json="{}"} \\!adf:x\n'), [said('a !adf:carry{json="{}"} !adf:x')])
assert.deepEqual(read('```adf:blockCard\n{}\n```\n'), [node('codeBlock', { language: 'adf:blockCard' }, text('{}'))])
const linked = paragraph(text('a', { attrs: { href: 'u' }, type: 'link' }))
assert.deepEqual(read('> [!tip] ![a](u)\n'), [panel('tip', linked)])
assert.deepEqual(read('> [!tip]\n> ![a](u)\n'), [panel('tip', linked)])
const image = node('mediaSingle', { layout: 'center' }, { attrs: { alt: 'a', type: 'external', url: 'u' }, type: 'media' })
assert.deepEqual(read('> [!tip] ![a](u)\n'), [panel('tip', image)])
assert.deepEqual(read('> [!tip]\n> ![a](u)\n'), [panel('tip', image)])
assert.deepEqual(read('> [!tip]\n>\n> ![a](u)\n'), [panel('tip', image)])
assert.deepEqual(read('> [!tip]\n> ![a](u) b\n'), [panel('tip', paragraph(...(linked.content ?? []), text(' b')))])
assert.deepEqual(read('> [!NOTE]- t\n> ![a](u)\n'), [node('expand', { title: 't' }, image)])
assert.deepEqual(read('> [!NOTE]- ![a](u)\n'), [node('expand', { title: 'a (u)' }, paragraph())])
assert.deepEqual(read('> [!NOTE]- t\n> ![a](u)\n'), [node('expand', { title: 't' }, linked)])
assert.deepEqual(read('- [x] ![a](u)\n'), [bare('taskList', task('DONE', ...(linked.content ?? [])))])
assert.deepEqual(read('> [!tip]\n>\n> ![a](u)\n'), [panel('tip', node('mediaSingle', { layout: 'center' }, { attrs: { alt: 'a', type: 'external', url: 'u' }, type: 'media' }))])
assert.deepEqual(read('- [x]\n ![a](u)\n'), [bare('taskList', task('DONE', ...(linked.content ?? [])))])
let deep = 'x\n'
for (let level = 0; level < largestNesting; level += 1) deep = `> ${deep}`
assert.equal(typeof read(deep), 'object')