mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 19:58:28 +01:00
Answer architecture round 4: the writers close a mark run at a hard break again, and item 91 plans the joined spelling
This commit is contained in:
+1
-2
@@ -75,8 +75,7 @@
|
|||||||
whose text is only hard breaks reads as its destination linked, followed by those breaks, and
|
whose text is only hard breaks reads as its destination linked, followed by those breaks, and
|
||||||
`losslessMarkdownToAdf` refuses a directive mark wrapping only hard breaks as
|
`losslessMarkdownToAdf` refuses a directive mark wrapping only hard breaks as
|
||||||
`unsupported-node-shape`. `adfToLosslessMarkdown` writes a hard break holding marks as
|
`unsupported-node-shape`. `adfToLosslessMarkdown` writes a hard break holding marks as
|
||||||
`!adf:carry{json="…"}`, and spells an emphasis or link across the hard breaks inside it once,
|
`!adf:carry{json="…"}`. See `MIGRATION.md`.
|
||||||
`_a\` then `b_`, where `adfToMarkdown` closed and reopened it. See `MIGRATION.md`.
|
|
||||||
- Fix a cyclic document, in which an object holds itself: `adfToLosslessMarkdown` and
|
- Fix a cyclic document, in which an object holds itself: `adfToLosslessMarkdown` and
|
||||||
`adfToPortableMarkdown` refuse it as `not-an-adf-document` and `isAdfDocument` returns `false`,
|
`adfToPortableMarkdown` refuse it as `not-an-adf-document` and `isAdfDocument` returns `false`,
|
||||||
where all three hung.
|
where all three hung.
|
||||||
|
|||||||
+2
-3
@@ -540,9 +540,8 @@ gives marks `[em, underline]`, `!adf:underline[_x_]` the reverse. `adfToLossless
|
|||||||
the order the array holds rather than sorting it — `docs/decisions.md` §Equality is deep restores
|
the order the array holds rather than sorting it — `docs/decisions.md` §Equality is deep restores
|
||||||
the array, not a set — and opens each spelling once over the longest run of adjacent inline nodes
|
the array, not a set — and opens each spelling once over the longest run of adjacent inline nodes
|
||||||
carrying an identical mark, attributes included, at that depth: `attrs: {}` differs from no `attrs`,
|
carrying an identical mark, attributes included, at that depth: `attrs: {}` differs from no `attrs`,
|
||||||
and a directive spells it `{attrs=empty}`. An emphasis or link spelling also spans the hard breaks
|
and a directive spells it `{attrs=empty}`. A run breaks at every node the emitter carries, so no
|
||||||
between two nodes of its run. A run breaks at every node the emitter carries, so no emitted carry
|
emitted carry sits inside a mark spelling.
|
||||||
sits inside a mark spelling.
|
|
||||||
|
|
||||||
An inline node whose marks no nesting spells — a hard break holding any mark, a mark type not listed
|
An inline node whose marks no nesting spells — a hard break holding any mark, a mark type not listed
|
||||||
here, an attrs key its spelling does not list, a value that is not the spelling's type, an attribute
|
here, an attrs key its spelling does not list, a value that is not the spelling's type, an attribute
|
||||||
|
|||||||
@@ -713,17 +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')
|
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', () => {
|
test('closes a mark run at the hard break between two nodes holding the mark', () => {
|
||||||
const em: AdfMark = { type: 'em' }
|
const em: AdfMark = { type: 'em' }
|
||||||
const link: AdfMark = { attrs: { href: '/u' }, type: 'link' }
|
const link: AdfMark = { attrs: { href: '/u' }, type: 'link' }
|
||||||
const marked = (text: string, ...marks: AdfMark[]): AdfNode => ({ marks, text, type: 'text' })
|
const marked = (text: string, ...marks: AdfMark[]): AdfNode => ({ marks, text, type: 'text' })
|
||||||
const hardBreak: AdfNode = { type: 'hardBreak' }
|
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', em), hardBreak, marked('b', em)))), '_a_\\\n_b_\n')
|
||||||
assert.equal(readsBack(document(paragraph(marked('a', link), hardBreak, marked('b', link)))), '[a\\\nb](/u)\n')
|
assert.equal(readsBack(document(paragraph(marked('a', link), hardBreak, marked('b', link)))), '[a](/u)\\\n[b](/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, link), hardBreak, marked('b', em)))), '_[a](/u)_\\\n_b_\n')
|
||||||
assert.equal(readsBack(document(paragraph(marked('a', em), hardBreak, hardBreak, marked('b', em)))), '_a\\\n\\\nb_\n')
|
assert.equal(readsBack(document(paragraph(marked('a', em), hardBreak, hardBreak, marked('b', em)))), '_a_\\\n\\\n_b_\n')
|
||||||
const directive: AdfMark = { attrs: { href: '/u', id: 'x' }, type: 'link' }
|
const directive: AdfMark = { attrs: { href: '/u', id: 'x' }, type: 'link' }
|
||||||
assert.equal(readsBack(document(paragraph(marked('a', directive), hardBreak, marked('b', directive)))), '!adf:link[a!adf:hardBreak{}b]{href="/u" id=x}\n')
|
assert.equal(readsBack(document(paragraph(marked('a', directive), hardBreak, marked('b', directive)))), '!adf:link[a]{href="/u" id=x}\\\n!adf:link[b]{href="/u" id=x}\n')
|
||||||
readsBack(document(paragraph(marked(']: /x', link, { type: 'code' }), hardBreak, marked('b', link))))
|
readsBack(document(paragraph(marked(']: /x', link, { type: 'code' }), hardBreak, marked('b', link))))
|
||||||
assert.equal(readsBack(document(paragraph({ text: 'x', type: 'text' }, marked('.', em), hardBreak, marked('b', em)))), 'x!adf:carry{json="{\\"marks\\":[{\\"type\\":\\"em\\"}],\\"text\\":\\".\\",\\"type\\":\\"text\\"}"}\\\n_b_\n')
|
assert.equal(readsBack(document(paragraph({ text: 'x', type: 'text' }, marked('.', em), hardBreak, marked('b', em)))), 'x!adf:carry{json="{\\"marks\\":[{\\"type\\":\\"em\\"}],\\"text\\":\\".\\",\\"type\\":\\"text\\"}"}\\\n_b_\n')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ type InlineContext = {
|
|||||||
bracketed: boolean
|
bracketed: boolean
|
||||||
carried: ReadonlySet<number>
|
carried: ReadonlySet<number>
|
||||||
flavour: WrittenFlavour
|
flavour: WrittenFlavour
|
||||||
joinsBreaks: boolean
|
|
||||||
openingLinkAsDirective: boolean
|
openingLinkAsDirective: boolean
|
||||||
path: ConvertErrorPath
|
path: ConvertErrorPath
|
||||||
spansLines: boolean
|
spansLines: boolean
|
||||||
@@ -37,7 +36,7 @@ type InlineRun = { index: number; kind: 'marked'; mark: AdfMark; nodes: AdfNode[
|
|||||||
|
|
||||||
type LineAttempt = { fallback: NodeRange | 'opening-link'; line?: undefined } | { fallback?: undefined; line: string }
|
type LineAttempt = { fallback: NodeRange | 'opening-link'; line?: undefined } | { fallback?: undefined; line: string }
|
||||||
|
|
||||||
type LineFallbacks = { carried: Set<number>; flavour: WrittenFlavour; joinsBreaks: boolean; openingLinkAsDirective: boolean }
|
type LineFallbacks = { carried: Set<number>; flavour: WrittenFlavour; openingLinkAsDirective: boolean }
|
||||||
|
|
||||||
export type PortableLineFallback = { kind: 'opening-link' } | { kind: 'unspellable-run'; runs: [MarkRun, ...MarkRun[]] }
|
export type PortableLineFallback = { kind: 'opening-link' } | { kind: 'unspellable-run'; runs: [MarkRun, ...MarkRun[]] }
|
||||||
|
|
||||||
@@ -54,8 +53,7 @@ export function openingLinkTakesDirective(nodes: readonly AdfNode[], path: Conve
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function portableLineFallback(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath): Result<PortableLineFallback | undefined> {
|
export function portableLineFallback(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath): Result<PortableLineFallback | undefined> {
|
||||||
// Unjoined: the spelling `emitLine` falls back to where the joined one fails.
|
const emission = lineSegments(nodes, container, path, { carried: new Set(), flavour: 'portable', openingLinkAsDirective: false })
|
||||||
const emission = lineSegments(nodes, container, path, { carried: new Set(), flavour: 'portable', joinsBreaks: false, openingLinkAsDirective: false })
|
|
||||||
if (!emission.ok) return emission
|
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)
|
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')
|
const verdict = lineVerdict(emission.value.segments, container, 'portable')
|
||||||
@@ -78,7 +76,7 @@ export function tryImageLine(alt: string | undefined, href: string): string | un
|
|||||||
}
|
}
|
||||||
|
|
||||||
function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath, flavour: WrittenFlavour): Result<EmittedLine> {
|
function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: ConvertErrorPath, flavour: WrittenFlavour): Result<EmittedLine> {
|
||||||
const fallbacks: LineFallbacks = { carried: new Set(), flavour, joinsBreaks: nodes.some((node) => node.type === 'hardBreak'), openingLinkAsDirective: false }
|
const fallbacks: LineFallbacks = { carried: new Set(), flavour, openingLinkAsDirective: false }
|
||||||
// Terminates because takeFallback refuses a pass that took no new fallback.
|
// Terminates because takeFallback refuses a pass that took no new fallback.
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const emission = lineSegments(nodes, container, path, fallbacks)
|
const emission = lineSegments(nodes, container, path, fallbacks)
|
||||||
@@ -97,12 +95,7 @@ function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A line's first fallback stops runs joining across hard breaks, so no fallback takes a break `joinedBreaks` joined.
|
|
||||||
function takeFallback(fallbacks: LineFallbacks, fallback: NodeRange | 'opening-link', path: ConvertErrorPath): Result<null> {
|
function takeFallback(fallbacks: LineFallbacks, fallback: NodeRange | 'opening-link', path: ConvertErrorPath): Result<null> {
|
||||||
if (fallbacks.joinsBreaks) {
|
|
||||||
fallbacks.joinsBreaks = false
|
|
||||||
return success(null)
|
|
||||||
}
|
|
||||||
if (fallback === 'opening-link') {
|
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)
|
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)
|
||||||
fallbacks.openingLinkAsDirective = true
|
fallbacks.openingLinkAsDirective = true
|
||||||
@@ -184,7 +177,7 @@ function emitRun(nodes: readonly AdfNode[], depth: number, firstIndex: number, c
|
|||||||
if (depth > largestNesting) {
|
if (depth > largestNesting) {
|
||||||
return failure('unsupported-nesting-depth', `the marks nest deeper than the ${largestNesting} levels the emitter carries`, context.path)
|
return failure('unsupported-nesting-depth', `the marks nest deeper than the ${largestNesting} levels the emitter carries`, context.path)
|
||||||
}
|
}
|
||||||
const runs = inlineRuns(nodes, depth, firstIndex, context)
|
const runs = inlineRuns(nodes, depth, firstIndex, context.carried)
|
||||||
const segments: InlineSegment[] = []
|
const segments: InlineSegment[] = []
|
||||||
for (const [offset, run] of runs.entries()) {
|
for (const [offset, run] of runs.entries()) {
|
||||||
if (takesTextBreak(runs[offset - 1], run, context.carried)) segments.push(syntax(textBreakSpelling))
|
if (takesTextBreak(runs[offset - 1], run, context.carried)) segments.push(syntax(textBreakSpelling))
|
||||||
@@ -197,47 +190,23 @@ function emitRun(nodes: readonly AdfNode[], depth: number, firstIndex: number, c
|
|||||||
return success({ segments })
|
return success({ segments })
|
||||||
}
|
}
|
||||||
|
|
||||||
function inlineRuns(nodes: readonly AdfNode[], depth: number, firstIndex: number, context: InlineContext): InlineRun[] {
|
function inlineRuns(nodes: readonly AdfNode[], depth: number, firstIndex: number, carried: ReadonlySet<number>): InlineRun[] {
|
||||||
const runs: InlineRun[] = []
|
const runs: InlineRun[] = []
|
||||||
for (let offset = 0; offset < nodes.length; offset += 1) {
|
for (const [offset, node] of nodes.entries()) {
|
||||||
const node = nodes[offset]
|
|
||||||
if (node === undefined) continue
|
|
||||||
const index = firstIndex + offset
|
const index = firstIndex + offset
|
||||||
// `depth` indexes each node's marks, outermost first; a carried node spells its marks inside the carry.
|
// `depth` indexes each node's marks, outermost first; a carried node spells its marks inside the carry.
|
||||||
const mark = carries(node, context.carried, index) ? undefined : nodeMarks(node)[depth]
|
const mark = carries(node, carried, index) ? undefined : nodeMarks(node)[depth]
|
||||||
const previous = runs[runs.length - 1]
|
|
||||||
const joined = mark === undefined && previous?.kind === 'marked' && context.joinsBreaks ? joinedBreaks(nodes, offset, firstIndex, previous.mark, depth, context.carried) : 0
|
|
||||||
if (previous?.kind === 'marked' && joined > 0) {
|
|
||||||
for (const held of nodes.slice(offset, offset + joined)) previous.nodes.push(held)
|
|
||||||
offset += joined - 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (mark === undefined) {
|
if (mark === undefined) {
|
||||||
runs.push({ index, kind: 'plain', node })
|
runs.push({ index, kind: 'plain', node })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
const previous = runs[runs.length - 1]
|
||||||
if (previous?.kind === 'marked' && identicalMark(previous.mark, mark)) previous.nodes.push(node)
|
if (previous?.kind === 'marked' && identicalMark(previous.mark, mark)) previous.nodes.push(node)
|
||||||
else runs.push({ index, kind: 'marked', mark, nodes: [node] })
|
else runs.push({ index, kind: 'marked', mark, nodes: [node] })
|
||||||
}
|
}
|
||||||
return runs
|
return runs
|
||||||
}
|
}
|
||||||
|
|
||||||
// The hard breaks from `offset` an emphasis or link run spans because the node after them holds its mark; a directive would spell each `!adf:hardBreak{}`.
|
|
||||||
function joinedBreaks(nodes: readonly AdfNode[], offset: number, firstIndex: number, mark: AdfMark, depth: number, carried: ReadonlySet<number>): number {
|
|
||||||
const kind = markSpelling(mark.type)?.kind
|
|
||||||
if (kind !== 'emphasis' && kind !== 'link') return 0
|
|
||||||
let ahead = offset
|
|
||||||
while (isUncarriedBreak(nodes[ahead], carried, firstIndex + ahead)) ahead += 1
|
|
||||||
const next = nodes[ahead]
|
|
||||||
if (next === undefined || carries(next, carried, firstIndex + ahead)) return 0
|
|
||||||
const nextMark = nodeMarks(next)[depth]
|
|
||||||
return nextMark !== undefined && identicalMark(nextMark, mark) ? ahead - offset : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function isUncarriedBreak(node: AdfNode | undefined, carried: ReadonlySet<number>, index: number): boolean {
|
|
||||||
return node?.type === 'hardBreak' && !carries(node, carried, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
function takesTextBreak(previous: InlineRun | undefined, run: InlineRun, carried: ReadonlySet<number>): boolean {
|
function takesTextBreak(previous: InlineRun | undefined, run: InlineRun, carried: ReadonlySet<number>): boolean {
|
||||||
if (previous?.kind !== 'plain' || run.kind !== 'plain') return false
|
if (previous?.kind !== 'plain' || run.kind !== 'plain') return false
|
||||||
if (carries(previous.node, carried, previous.index) || carries(run.node, carried, run.index)) return false
|
if (carries(previous.node, carried, previous.index) || carries(run.node, carried, run.index)) return false
|
||||||
@@ -313,7 +282,7 @@ function emitMarkedRun(nodes: readonly AdfNode[], mark: AdfMark, depth: number,
|
|||||||
if (spelling.kind === 'emphasis') return emitEmphasis(nodes, spelling.spelling, depth, range, context)
|
if (spelling.kind === 'emphasis') return emitEmphasis(nodes, spelling.spelling, depth, range, context)
|
||||||
const link = spelling.kind === 'link' ? tryLink(nodes, mark, depth, range, context) : undefined
|
const link = spelling.kind === 'link' ? tryLink(nodes, mark, depth, range, context) : undefined
|
||||||
if (link !== undefined) return link
|
if (link !== undefined) return link
|
||||||
const inner = emitRun(nodes, depth + 1, index, { ...context, bracketed: true, spansLines: false })
|
const inner = emitRun(nodes, depth + 1, index, { ...context, bracketed: true })
|
||||||
if (!inner.ok) return inner
|
if (!inner.ok) return inner
|
||||||
if (inner.value.carry !== undefined) return inner
|
if (inner.value.carry !== undefined) return inner
|
||||||
return success({ segments: [syntax(spellInlineDirectiveOpener(mark.type)), ...inner.value.segments, syntax(`]${attributes}`)] })
|
return success({ segments: [syntax(spellInlineDirectiveOpener(mark.type)), ...inner.value.segments, syntax(`]${attributes}`)] })
|
||||||
|
|||||||
@@ -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(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({ 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('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\\\nc**\n')
|
assert.equal(portable(paragraph(text(' '), text('b', strong), text(' \n'), text('c', strong), text(' '))), '**b**\\\n**c**\n')
|
||||||
assert.equal(portable(paragraph(text('x'), text('.', em), { type: 'hardBreak' }, text('b', em))), 'x.\\\n_b_\n')
|
assert.equal(portable(paragraph(text('x'), text('.', em), { type: 'hardBreak' }, text('b', em))), 'x.\\\n_b_\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('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(paragraph(text(' x ', code))), '` x `\n')
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
`Bar = 9`
|
`Bar = 9`
|
||||||
|
|
||||||
`Next ID = 91`
|
`Next ID = 92`
|
||||||
|
|
||||||
| Goal | W |
|
| Goal | W |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
| 52 | 0.3.0 | | **Spell `colwidth` as a comma list, `colwidth="340,420"`.** | 3 | 3 | 5 | 6 | 6 | 11.5 |
|
| 52 | 0.3.0 | | **Spell `colwidth` as a comma list, `colwidth="340,420"`.** | 3 | 3 | 5 | 6 | 6 | 11.5 |
|
||||||
| 51 | 0.3.0 | | **Match a reference label to its definition under Unicode case folding.** | 2 | 2 | 2 | 7 | 4, 5 | 10.9 |
|
| 51 | 0.3.0 | | **Match a reference label to its definition under Unicode case folding.** | 2 | 2 | 2 | 7 | 4, 5 | 10.9 |
|
||||||
| 53 | 0.3.0 | | **Put a block's `marks` spelling to the writer panel and adopt its pick.** | 4 | 5 | 5 | 6 | 6 | 10.0 |
|
| 53 | 0.3.0 | | **Put a block's `marks` spelling to the writer panel and adopt its pick.** | 4 | 5 | 5 | 6 | 6 | 10.0 |
|
||||||
|
| 91 | 0.3.0 | | **Spell an emphasis or link once across the hard breaks between two nodes it marks, in both writers.** | 5 | 5 | 5 | 6 | 6 | 9.0 |
|
||||||
| 73 | 0.4.0 | | **Announce the package where someone needing an ADF converter already reads: JRACLOUD-77436, the Atlassian developer community and Stack Overflow's ADF-to-markdown questions.** | 1 | 2 | 6 | 8 | 3 | 23.0 |
|
| 73 | 0.4.0 | | **Announce the package where someone needing an ADF converter already reads: JRACLOUD-77436, the Atlassian developer community and Stack Overflow's ADF-to-markdown questions.** | 1 | 2 | 6 | 8 | 3 | 23.0 |
|
||||||
| 9 | 0.4.0 | | **Ship an online sandbox: a web page with two textboxes converting between ADF and markdown on the library's browser build.** | 2 | 6 | 6 | 8 | 3 | 21.0 |
|
| 9 | 0.4.0 | | **Ship an online sandbox: a web page with two textboxes converting between ADF and markdown on the library's browser build.** | 2 | 6 | 6 | 8 | 3 | 21.0 |
|
||||||
| 38 | 0.4.0 | | **Spell a lone surrogate in a text node so it survives a UTF-8 encode.** | 2 | 2 | 4 | 7 | 1 | 19.5 |
|
| 38 | 0.4.0 | | **Spell a lone surrogate in a text node so it survives a UTF-8 encode.** | 2 | 2 | 4 | 7 | 1 | 19.5 |
|
||||||
@@ -145,8 +146,8 @@ types a node takes, and the emitter carries a node holding another.
|
|||||||
`adfToLosslessMarkdown` spends 23 s on one paragraph of 2000 × `un` plus `**-r**`: each run its
|
`adfToLosslessMarkdown` spends 23 s on one paragraph of 2000 × `un` plus `**-r**`: each run its
|
||||||
flanking cannot spell re-emits the whole line before riding the carry, quadratic in the runs, and
|
flanking cannot spell re-emits the whole line before riding the carry, quadratic in the runs, and
|
||||||
the portable reduction's `spellableLine` drops one mark per re-emit the same way. An inline node
|
the portable reduction's `spellableLine` drops one mark per re-emit the same way. An inline node
|
||||||
whose attributes no spelling writes re-emits the line the same way before riding the carry, and so
|
whose attributes no spelling writes re-emits the line the same way before riding the carry. Make all
|
||||||
does an emphasis or link run that parts at the hard breaks it spans. Make all four linear.
|
three linear.
|
||||||
|
|
||||||
### 7. Ship HTML: `adfToHtml`, `htmlToAdf`, and markdown to and from HTML composed through ADF, each call named by its flavour.
|
### 7. Ship HTML: `adfToHtml`, `htmlToAdf`, and markdown to and from HTML composed through ADF, each call named by its flavour.
|
||||||
|
|
||||||
@@ -280,6 +281,15 @@ Today `marks="[{\"attrs\":{\"mode\":\"wide\"},\"type\":\"breakout\"}]"`, the mar
|
|||||||
escaped JSON. Breaking where the panel picks another spelling: `MIGRATION.md`'s Spellings table
|
escaped JSON. Breaking where the panel picks another spelling: `MIGRATION.md`'s Spellings table
|
||||||
gains its row.
|
gains its row.
|
||||||
|
|
||||||
|
### 91. Spell an emphasis or link once across the hard breaks between two nodes it marks, in both writers.
|
||||||
|
|
||||||
|
Lands after item 61. No reader marks a hard break, so `*One\` then `Two*` writes back as `_One_\` then
|
||||||
|
`_Two_`, and a link typed across a line as two links. Joining the run across the break in
|
||||||
|
`inline-line.ts` ruled worse with two comprehension panels (PR #22): a joined run that cannot be
|
||||||
|
spelled needs its own fallback, first as a set of parted breaks, then as a flag the first fallback
|
||||||
|
clears. Once the emitter asks the reader how a line reads back, the joined spelling can be tried and
|
||||||
|
kept only where it reads back.
|
||||||
|
|
||||||
### 73. Announce the package where someone needing an ADF converter already reads: JRACLOUD-77436, the Atlassian developer community and Stack Overflow's ADF-to-markdown questions.
|
### 73. Announce the package where someone needing an ADF converter already reads: JRACLOUD-77436, the Atlassian developer community and Stack Overflow's ADF-to-markdown questions.
|
||||||
|
|
||||||
Lands after `0.3.0` is published with item 71, so each post links the GitHub repository, the
|
Lands after `0.3.0` is published with item 71, so each post links the GitHub repository, the
|
||||||
|
|||||||
Reference in New Issue
Block a user