diff --git a/src/markdown/parse/inline-content.ts b/src/markdown/parse/inline-content.ts index 2265ed7..7d1c97e 100644 --- a/src/markdown/parse/inline-content.ts +++ b/src/markdown/parse/inline-content.ts @@ -64,6 +64,8 @@ type Scan = { // Pieces below this have been walked for openers to deactivate: an image close folds the link-marked piece into alt text, leaving this the only record that the brackets around it are doomed. deactivatedBefore: number definitions: LinkDefinitions + // Whether an `!adf:link` slot holds this content, where an image reads as its plain alt text. + inLink: boolean openingSpellableLink: boolean path: ConvertErrorPath pending: string @@ -86,7 +88,7 @@ const textBreak: TextBreak = { kind: 'textBreak' } // The outermost content: only here does a text break see both its neighbours. export function parseInlineContent(source: string, definitions: LinkDefinitions, path: ConvertErrorPath, container: LineContainer, claims: Claims): Result { - const scan = freshScan(source, { definitions, path }, { claims, container, spans: noSpans }) + const scan = freshScan(source, { definitions, path }, { claims, container, inLink: false, spans: noSpans }) const scanned = scanInline(scan) if (!scanned.ok) return scanned const nodes = partText(scanned.value.nodes, scan) @@ -100,7 +102,7 @@ export function parseInlineContent(source: string, definitions: LinkDefinitions, return success(image === undefined ? { nodes: nodes.value } : { image, nodes: nodes.value }) } -function freshScan(source: string, shared: SharedScan, own: Pick): Scan { +function freshScan(source: string, shared: SharedScan, own: Pick): Scan { return { ...shared, ...own, deactivatedBefore: 0, openingSpellableLink: false, pending: '', pieces: [], source } } @@ -281,7 +283,7 @@ function refuseLinkDirective(scan: Scan, mark: AdfMark, nodes: readonly Inline[] function slotContent(scan: Scan, span: DirectiveSpan): Result { if (span.content === undefined) return success(undefined) const { claims, definitions, path } = scan - const parsed = scanInline(freshScan(span.content, { definitions, path }, { claims, container: undefined, spans: span.spans })) + const parsed = scanInline(freshScan(span.content, { definitions, path }, { claims, container: undefined, inLink: scan.inLink || span.name === 'link', spans: span.spans })) if (!parsed.ok) return parsed return success({ nodes: parsed.value.nodes }) } @@ -297,7 +299,7 @@ function pushNode(scan: Scan, node: AdfNode): void { } function assemble(scan: Scan): Result { - const nodes = resolveNodes(scan.pieces.map(linkedAlt), scan, true) + const nodes = resolveNodes(scan.inLink ? scan.pieces : scan.pieces.map(linkedAlt), scan, true) if (!nodes.ok) return nodes const [only] = scan.pieces return success(scan.pieces.length === 1 && only?.kind === 'image' ? { image: mediaSingle(only), nodes: nodes.value } : { nodes: nodes.value }) diff --git a/src/markdown/parse/markdown-to-adf.test.ts b/src/markdown/parse/markdown-to-adf.test.ts index e2c0f4c..0e5812f 100644 --- a/src/markdown/parse/markdown-to-adf.test.ts +++ b/src/markdown/parse/markdown-to-adf.test.ts @@ -1037,6 +1037,9 @@ test('reads an image inside a link not alone in its paragraph as plain alt text 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'))]) + const unspellable = { attrs: { href: 'a\nb' }, type: 'link' } + assert.deepEqual(firstContent('!adf:link[b ![a](/u)]{href="a\\nb"}\n'), [marked('b a', unspellable)]) + assert.deepEqual(firstContent('!adf:link[!adf:underline[![a](/u)]]{href="a\\nb"}\n'), [marked('a', unspellable, { type: 'underline' })]) }) test('carries the mark a spelling nested inside its own kind names once', () => {