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

Answer architecture round 1: one owner for the image's ADF shape, the readings' decision, marker paragraphs, the exceptions the README names; items 87 filed and 56 widened

This commit is contained in:
2026-10-09 03:37:29 +02:00
committed by Lillem4n
parent 8370230fee
commit 88aef1c8c4
14 changed files with 81 additions and 52 deletions
+3 -3
View File
@@ -6,6 +6,7 @@ import type { LineContainer } from '../line-container.ts'
import type { LinkDefinition } from '../commonmark/link-syntax.ts'
import { backslashEscape, decodeTextEscapes, inlineHtmlConstruct, readBracketedAutolink, readEmailAutolink, trimTrailingSpace } from '../commonmark/grammar.ts'
import { backtickRun, closingBacktickRun } from '../commonmark/backtick-runs.ts'
import { centeredImage, externalMedia } from '../external-image.ts'
import { commonMarkLink, linkHref } from '../mark-spellings.ts'
import { delimiterFlags, matchEmphasis, runLength } from '../commonmark/emphasis-matching.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
@@ -309,11 +310,10 @@ function linkedAlt(piece: Piece): Piece {
}
function mediaSingle({ alt, definition, link }: Image): AdfNode {
const attrs = alt === '' ? { type: 'external', url: definition.destination } : { alt, type: 'external', url: definition.destination }
const media: AdfNode = link === undefined ? { attrs, type: 'media' } : { attrs, marks: [linkMark(link)], type: 'media' }
const media = externalMedia(alt, definition.destination)
const { title } = definition
const caption: AdfNode[] = title === undefined || title === '' ? [] : [{ content: [{ text: title, type: 'text' }], type: 'caption' }]
return { attrs: { layout: 'center' }, content: [media, ...caption], type: 'mediaSingle' }
return centeredImage([link === undefined ? media : { ...media, marks: [linkMark(link)] }, ...caption])
}
function linkMark({ destination, title }: LinkDefinition): AdfMark {
+3 -1
View File
@@ -1027,9 +1027,11 @@ test('reads an image no paragraph holds alone as its alt text, linked to its des
assert.deepEqual(firstContent('# ![a](/u)\n'), [marked('a', link('/u'))])
assert.deepEqual(firstContent('!adf:paragraph {localId=p}\n![a](/u)\n!adf:/paragraph\n'), [marked('a', link('/u'))])
assert.deepEqual(firstContent('![a ![b](/c) d\n'), [text('![a '), marked('b', link('/c')), text(' d')])
assert.deepEqual(content(losslessMarkdownToAdf('![a]\n')), [paragraph('![a]')])
assert.deepEqual(content(losslessMarkdownToAdf('a ! b\n')), [paragraph('a ! b')])
})
test('reads an image beside other content in a link as its alt text, the link\'s own text', () => {
test('reads an image inside a link not alone in its paragraph as plain alt text the link marks, dropping its own destination', () => {
assert.deepEqual(firstContent('See [![a](/u "t")](/v).\n'), [text('See '), marked('a', link('/v')), text('.')])
assert.deepEqual(firstContent('See [![](/u)](/v).\n'), [text('See '), marked('/v', link('/v')), text('.')])
assert.deepEqual(firstContent('[b ![a](/u)](/v)\n'), [marked('b a', link('/v'))])
+8 -8
View File
@@ -21,9 +21,9 @@ import { spellStringAttribute, unsupportedNodeShape } from '../directive-syntax.
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 MarkedParagraph = { kind: 'markedParagraph'; position: SourcePosition; text: string }
type MarkerParagraph = { kind: 'markerParagraph'; position: SourcePosition; text: string }
type ReadBlock = Block | MarkedParagraph
type ReadBlock = Block | MarkerParagraph
// `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,7 +123,7 @@ 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 'markedParagraph':
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)
@@ -149,8 +149,8 @@ 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 paragraphsOf(position: SourcePosition, ...texts: string[]): MarkedParagraph[] {
return texts.filter((text) => text !== '').map((text) => ({ kind: 'markedParagraph', position, text }))
function markerParagraphs(position: SourcePosition, ...texts: string[]): MarkerParagraph[] {
return texts.filter((text) => text !== '').map((text) => ({ kind: 'markerParagraph', position, text }))
}
function quoteNode(blocks: readonly Block[], reading: Reading, path: ConvertErrorPath, depth: number): Result<AdfNode> {
@@ -159,12 +159,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([...paragraphsOf(led.position, line, rest), ...body], reading, path, depth + 1))
if (!folded) return filledNode({ attrs: { panelType }, type: 'panel' }, readBlocks([...markerParagraphs(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([...paragraphsOf(led.position, rest), ...body], { ...reading, inExpand: true }, path, depth + 1))
return filledNode(text === '' ? { type } : { attrs: { title: text }, type }, readBlocks([...markerParagraphs(led.position, rest), ...body], { ...reading, inExpand: true }, path, depth + 1))
}
// docs/decisions.md, A callout title keeps its link targets.
@@ -203,7 +203,7 @@ 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([...paragraphsOf(position, text.replace(/^(?:[ \t\n]|\\\n)+/, '')), ...others], reading, [...path, 'content', index], depth + 1)
const read = readBlocks([...markerParagraphs(position, text.replace(/^(?:[ \t\n]|\\\n)+/, '')), ...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