mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-13 04:08:29 +01:00
Answer product-owner round 1: an emphasis or link run spans an unmarked hard break, the recipe drops a hard break's marks, and the refusal names hard breaks
This commit is contained in:
@@ -713,6 +713,17 @@ test('spells the directive marks around the longest run they cover', () => {
|
||||
assert.equal(emitted(marked('a', underline), { type: 'hardBreak' }, marked('b', underline)), '!adf:underline[a]\\\n!adf:underline[b]\n')
|
||||
})
|
||||
|
||||
test('spells a mark run across the hard break between two nodes holding the mark', () => {
|
||||
const em: AdfMark = { type: 'em' }
|
||||
const link: AdfMark = { attrs: { href: '/u' }, type: 'link' }
|
||||
const marked = (text: string, ...marks: AdfMark[]): AdfNode => ({ marks, text, type: 'text' })
|
||||
const hardBreak: AdfNode = { type: 'hardBreak' }
|
||||
assert.equal(readsBack(document(paragraph(marked('a', em), hardBreak, marked('b', em)))), '_a\\\nb_\n')
|
||||
assert.equal(readsBack(document(paragraph(marked('a', link), hardBreak, marked('b', link)))), '[a\\\nb](/u)\n')
|
||||
assert.equal(readsBack(document(paragraph(marked('a', em, link), hardBreak, marked('b', em)))), '_[a](/u)\\\nb_\n')
|
||||
assert.equal(readsBack(document(paragraph(marked('a', em), hardBreak, hardBreak, marked('b', em)))), '_a_\\\n\\\n_b_\n')
|
||||
})
|
||||
|
||||
test('carries a hard break holding marks, which Atlassian\'s schema gives it none of', () => {
|
||||
const link: AdfMark = { attrs: { href: 'https://example.com/' }, type: 'link' }
|
||||
assert.equal(
|
||||
|
||||
@@ -196,17 +196,30 @@ function inlineRuns(nodes: readonly AdfNode[], depth: number, firstIndex: number
|
||||
const index = firstIndex + offset
|
||||
// `depth` indexes each node's marks, outermost first; a carried node spells its marks inside the carry.
|
||||
const mark = carries(node, carried, index) ? undefined : nodeMarks(node)[depth]
|
||||
const previous = runs[runs.length - 1]
|
||||
if (mark === undefined && previous?.kind === 'marked' && joinsRun(node, index, nodes[offset + 1], previous.mark, depth, carried)) {
|
||||
previous.nodes.push(node)
|
||||
continue
|
||||
}
|
||||
if (mark === undefined) {
|
||||
runs.push({ index, kind: 'plain', node })
|
||||
continue
|
||||
}
|
||||
const previous = runs[runs.length - 1]
|
||||
if (previous?.kind === 'marked' && identicalMark(previous.mark, mark)) previous.nodes.push(node)
|
||||
else runs.push({ index, kind: 'marked', mark, nodes: [node] })
|
||||
}
|
||||
return runs
|
||||
}
|
||||
|
||||
// The reader marks no hard break, so one between two nodes holding an emphasis or link mark reads back inside its spelling.
|
||||
function joinsRun(node: AdfNode, index: number, next: AdfNode | undefined, mark: AdfMark, depth: number, carried: ReadonlySet<number>): boolean {
|
||||
if (node.type !== 'hardBreak' || carries(node, carried, index) || next === undefined || carries(next, carried, index + 1)) return false
|
||||
const kind = markSpelling(mark.type)?.kind
|
||||
if (kind !== 'emphasis' && kind !== 'link') return false
|
||||
const nextMark = nodeMarks(next)[depth]
|
||||
return nextMark !== undefined && identicalMark(nextMark, mark)
|
||||
}
|
||||
|
||||
function takesTextBreak(previous: InlineRun | undefined, run: InlineRun, carried: ReadonlySet<number>): boolean {
|
||||
if (previous?.kind !== 'plain' || run.kind !== 'plain') return false
|
||||
if (carries(previous.node, carried, previous.index) || carries(run.node, carried, run.index)) return false
|
||||
|
||||
@@ -260,9 +260,12 @@ function directivePiece(scan: Scan, span: DirectiveSpan, index: number): Result<
|
||||
}
|
||||
|
||||
function directiveMarkPiece(scan: Scan, name: string, mark: AdfMark, slot: SlotContent | undefined, index: number): Result<Piece> {
|
||||
if (slot?.nodes.some(holdsOwnMarks) === true) return failure('unsupported-node-shape', ownMarksInMark, scan.path)
|
||||
if (slot === undefined || slot.nodes.every(takesNoMarks)) {
|
||||
return failure('unsupported-node-shape', `the ${name} mark wraps the [content] it marks: this one wraps nothing a mark rides`, scan.path)
|
||||
if (slot === undefined || slot.nodes.length === 0) {
|
||||
return failure('unsupported-node-shape', `the ${name} mark wraps the [content] it marks: this one wraps none`, scan.path)
|
||||
}
|
||||
if (slot.nodes.some(holdsOwnMarks)) return failure('unsupported-node-shape', ownMarksInMark, scan.path)
|
||||
if (slot.nodes.every(takesNoMarks)) {
|
||||
return failure('unsupported-node-shape', `the ${name} mark wraps the [content] it marks: this one wraps only hard breaks, which take no marks`, scan.path)
|
||||
}
|
||||
const refused = mark.type === 'link' ? refuseLinkDirective(scan, mark, slot.nodes, index) : undefined
|
||||
if (refused !== undefined) return refused
|
||||
|
||||
@@ -1181,11 +1181,12 @@ test('names the link a directive link wraps, no link holding another', () => {
|
||||
})
|
||||
|
||||
test('names the directive mark left without the content it wraps', () => {
|
||||
const named = 'unsupported-node-shape: the underline mark wraps the [content] it marks: this one wraps nothing a mark rides'
|
||||
const named = 'unsupported-node-shape: the underline mark wraps the [content] it marks: this one wraps none'
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:underline[]\n')), named)
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:underline{}\n')), named)
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:underline[!adf:hardBreak{}]\n')), named)
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:link[!adf:hardBreak{}]{href="/u" title=t}\n')), named.replace('underline', 'link'))
|
||||
const breaks = 'the [content] it marks: this one wraps only hard breaks, which take no marks'
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:underline[!adf:hardBreak{}]\n')), `unsupported-node-shape: the underline mark wraps ${breaks}`)
|
||||
assert.equal(content(losslessMarkdownToAdf('!adf:link[!adf:hardBreak{}]{href="/u" title=t}\n')), `unsupported-node-shape: the link mark wraps ${breaks}`)
|
||||
})
|
||||
|
||||
test('reads the text break only between two text nodes CommonMark joins, building no node', () => {
|
||||
|
||||
@@ -309,7 +309,7 @@ test('breaks a line at a newline and trims whitespace at every edge CommonMark s
|
||||
assert.equal(portable(paragraph(text(' \n a \n b\n'))), 'a\\\nb\n')
|
||||
assert.equal(portable(paragraph({ type: 'hardBreak' }, text('a'), { attrs: { text: '\n' }, type: 'hardBreak' }, text('b'), { type: 'hardBreak' })), 'a\\\nb\n')
|
||||
assert.equal(portable(paragraph(text('a'), text(' b ', strong), text('c'))), 'a **b** c\n')
|
||||
assert.equal(portable(paragraph(text(' '), text('b', strong), text(' \n'), text('c', strong), text(' '))), '**b**\\\n**c**\n')
|
||||
assert.equal(portable(paragraph(text(' '), text('b', strong), text(' \n'), text('c', strong), text(' '))), '**b\\\nc**\n')
|
||||
assert.equal(portable(paragraph(text('a'), text(' b ', em, strong), text(' ', em), text('c', em))), 'a _**b** c_\n')
|
||||
assert.equal(portable(paragraph(text(' x ', code))), '` x `\n')
|
||||
assert.equal(portable(node('heading', { level: 1 }, text(' h\ni '))), '# h i\n')
|
||||
|
||||
Reference in New Issue
Block a user