diff --git a/src/markdown/emit/inline-line.ts b/src/markdown/emit/inline-line.ts index a061840..e641bd0 100644 --- a/src/markdown/emit/inline-line.ts +++ b/src/markdown/emit/inline-line.ts @@ -54,7 +54,7 @@ export function openingLinkTakesDirective(nodes: readonly AdfNode[], path: Conve } export function portableLineFallback(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath): Result { - const emission = lineSegments(nodes, container, path, { carried: new Set(), flavour: 'portable', openingLinkAsDirective: false }) + const emission = lineSegments(nodes, container, path, freshFallbacks(nodes, 'portable')) if (!emission.ok) return emission if (emission.value.carry !== undefined) return failure('unsupported-node-shape', 'an inline node on a portable line has no spelling but the carry', path) const verdict = lineVerdict(emission.value.segments, container, 'portable') @@ -80,7 +80,7 @@ export function tryImageLine(alt: string | undefined, url: string, link?: LinkDe } function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath, flavour: WrittenFlavour): Result { - const fallbacks: LineFallbacks = { carried: new Set(), flavour, openingLinkAsDirective: false } + const fallbacks = freshFallbacks(nodes, flavour) // Terminates because takeFallback refuses a pass that took no new fallback. for (;;) { const emission = lineSegments(nodes, container, path, fallbacks) @@ -99,6 +99,16 @@ function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: Con } } +// Seeds the withheld-mark carries once per line, so no run at any depth reads a node's marks whole. +function freshFallbacks(nodes: readonly AdfNode[], flavour: WrittenFlavour): LineFallbacks { + const carried = new Set() + for (const [index, node] of nodes.entries()) { + const taken = takenMarkTypes(node) + if (taken !== undefined && nodeMarks(node).some((mark) => !taken.includes(mark.type))) carried.add(index) + } + return { carried, flavour, openingLinkAsDirective: false } +} + function takeFallback(fallbacks: LineFallbacks, fallback: NodeRange | 'opening-link', path: ConvertErrorPath): Result { if (fallback === 'opening-link') { if (fallbacks.openingLinkAsDirective) return failure('unsupported-node-shape', 'an opening link spelled as a directive still reads as a link definition, so the line has no spelling left', path) @@ -221,10 +231,9 @@ function nodePath(context: InlineContext, index: number): ConvertErrorPath { return [...context.path, 'content', index] } -// Decides the text-node, withheld-mark and slot-fault carries up front, so a line holding many of them is emitted once. +// Decides the text-node and slot-fault carries up front, so a line holding many of them is emitted once. function carries(node: AdfNode, carried: ReadonlySet, index: number): boolean { - const taken = takenMarkTypes(node) - if (carried.has(index) || (taken !== undefined && nodeMarks(node).some((mark) => !taken.includes(mark.type)))) return true + if (carried.has(index)) return true if (node.type === 'text') return !isSpellableText(node) || typeof node.text !== 'string' || node.text === '' const model = inlineNodeModel(node.type) if (model === undefined) return true diff --git a/src/markdown/parse/portable-markdown-to-adf.test.ts b/src/markdown/parse/portable-markdown-to-adf.test.ts index 64cb76a..14fa907 100644 --- a/src/markdown/parse/portable-markdown-to-adf.test.ts +++ b/src/markdown/parse/portable-markdown-to-adf.test.ts @@ -219,6 +219,7 @@ test('reads a == pair to the editor default highlight, Yellow200 #f8e6a0 in @atl assert.deepEqual(read('**==hi==** b\n'), [paragraph(text('hi', highlight, strong), text(' b'))]) assert.deepEqual(read('==**a**_b_ `c`==\n'), [paragraph(text('a', highlight, strong), text('b', highlight, em), text(' ', highlight), text('c', code))]) assert.deepEqual(read('==`a`==\n'), [paragraph(text('a', code))]) + assert.deepEqual(read('==[==a==](/u) b==\n'), [paragraph(text('a', { attrs: { href: '/u' }, type: 'link' }, highlight), text(' b', highlight))]) assert.deepEqual(read('x==y==z ==a == b==, (==c==) _d_==e==\n'), [paragraph(text('x==y==z '), text('a == b', highlight), text(', ('), text('c', highlight), text(') '), text('d', em), text('e', highlight))]) assert.deepEqual(read('😀==b== ==c==😀 é==d==\n'), [paragraph(text('😀'), text('b', highlight), text(' '), text('c', highlight), text('😀 é==d=='))]) assert.deepEqual(read('この機能は==日本語==でのみ、中文==重点==内容、ภาษา==ไทย==ดี 𠀀==𠀁==𠀂 이 기능은 ==한국어==에서만 サーバー==停止==中 このiPhone==専用==アプリ\n'), [