mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48:29 +01:00
Answer architecture round 1: 2 🔴, 4 🟡; refuse a link around only nodes that take none, mark the change breaking, file code-text marks
This commit is contained in:
@@ -27,7 +27,8 @@ In `docs/decisions.md`:
|
||||
- CommonMark is a subset
|
||||
- Tables
|
||||
- Links
|
||||
- An image reads as an image only alone in its paragraph, else as its alt text linked; a link text holding no node that takes a link reads as its destination, then those nodes
|
||||
- An image reads as an image only alone in its paragraph, else as its alt text linked; a link text of hard breaks alone reads as its destination, then the breaks
|
||||
- A mark skips a node that cannot hold it, and a link wrapping only such nodes is refused
|
||||
- A linked image is written `[](href)`, a captioned image as its directive
|
||||
- Ids stay site-local
|
||||
- Portable task ids come from position
|
||||
|
||||
+6
-4
@@ -4,10 +4,12 @@
|
||||
|
||||
- `losslessMarkdownToAdf` names the CommonMark spelling to write when it refuses a directive whose
|
||||
node CommonMark spells, such as `[](href)` for a linked `!adf:mediaSingle`.
|
||||
- `losslessMarkdownToAdf` gives a `date`, `emoji`, `inlineCard`, `mention`, `status` or
|
||||
`mediaInline` no mark Atlassian's schema withholds from it: `**!adf:mention[@A]{id=…}**` reads as
|
||||
a plain mention, and a link around only such nodes reads as its destination, then the nodes.
|
||||
`adfToLosslessMarkdown` writes a node holding such a mark as `!adf:carry`, so it still round-trips.
|
||||
- **Breaking:** a mark spelling skips a `date`, `emoji`, `inlineCard`, `mention`, `status` or
|
||||
`mediaInline` Atlassian's schema withholds the mark from, where `losslessMarkdownToAdf` gave it
|
||||
the marks around it: `**!adf:mention[@A]{id=…}**` reads as a plain mention. A link around only
|
||||
such nodes, or a directive mark around nodes none of which takes it, is `unsupported-node-shape`.
|
||||
`adfToLosslessMarkdown` writes a node holding such a mark as `!adf:carry{json="…"}`. See
|
||||
`MIGRATION.md`.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# Migrating
|
||||
|
||||
## From `0.2.0` to `0.3.0`
|
||||
|
||||
A mark spelling skips a node Atlassian's schema withholds the mark from:
|
||||
|
||||
| Input | `0.2.0` | `0.3.0` |
|
||||
| --- | --- | --- |
|
||||
| a `date`, `emoji`, `inlineCard`, `mention`, `status` or `mediaInline` inside a mark's spelling (`**!adf:mention[@A]{id=x}**`) | the node holds the mark | the node holds only marks its type takes; text beside it keeps the mark |
|
||||
| a link or directive mark around such nodes alone (`[!adf:status[Done]{color=green}](/u)`) | the nodes hold the mark | `unsupported-node-shape`; write the mark around text beside them |
|
||||
|
||||
`0.2.0` wrote the first row's spelling for a node holding such a mark, which `0.3.0` writes as
|
||||
`!adf:carry{json="…"}`. Markdown stored from such a document reads back without the mark: to keep
|
||||
it, read the markdown with `0.2.0` and write it with `0.3.0`, installed side by side.
|
||||
|
||||
## From `0.1.0` to `0.2.0`
|
||||
|
||||
Each call names the markdown it reads or writes:
|
||||
|
||||
@@ -463,6 +463,8 @@ Every call can raise these:
|
||||
replace a line ending with a space, or, for either, write the node as `!adf:carry`;
|
||||
- wraps a directive mark around nothing, or only around nodes that take no such mark, such as a
|
||||
hard break or a mention;
|
||||
- writes a link around nodes none of which takes a link, more than hard breaks alone, such as
|
||||
`[!adf:status[Done]{color=green}](/u)`: write the link around text beside them;
|
||||
- writes as a directive a node or mark the lossless flavour spells as CommonMark;
|
||||
- puts `!adf:textBreak{}` where it parts nothing, or `!adf:doc` anywhere but as the whole
|
||||
document.
|
||||
@@ -505,8 +507,7 @@ Serves [Goals](#goals) 1, 4 and 5.
|
||||
- These CommonMark spellings parse without an error but build a document the reference
|
||||
implementation renders differently, because ADF holds no node for what the reference renders:
|
||||
- An empty link text reads as its destination, `[](/url)` as `/url` linked to `/url`, and `[]()`
|
||||
as nothing. A link text holding no node that takes a link, such as a hard break or a mention,
|
||||
reads the same, followed by those nodes.
|
||||
as nothing. A link text holding only hard breaks reads the same, followed by its hard breaks.
|
||||
- An image reads as an image only alone in its paragraph. Anywhere else — beside other text, in
|
||||
a heading or a table cell, or in the paragraph a task marker opens — it reads as its
|
||||
alt text linked to its URL: `See .` reads as
|
||||
|
||||
+20
-8
@@ -228,20 +228,18 @@ link reference definition — and a directive link CommonMark could spell is ref
|
||||
link — the bracket form goes literal, the directive form refused — which is CommonMark's prose
|
||||
where its reference implementation nests one `<a>` in another.
|
||||
|
||||
## An image reads as an image only alone in its paragraph, else as its alt text linked; a link text holding no node that takes a link reads as its destination, then those nodes
|
||||
## An image reads as an image only alone in its paragraph, else as its alt text linked; a link text of hard breaks alone reads as its destination, then the breaks
|
||||
|
||||
2026-10-04, the maintainer and a writer panel, 3 of 3; `[]()` 2026-10-09, by Goals 4 and 7, as the
|
||||
maintainer's plan item asked; a link text of hard breaks alone 2026-10-09, the maintainer. Goals 4,
|
||||
5, 6 and 7. Valid while `mediaInline` carries no URL, ADF holds no empty text node, Atlassian's
|
||||
schema withholds the link from some inline nodes and no consumer has reported a link of such nodes
|
||||
alone.
|
||||
5, 6 and 7. Valid while `mediaInline` carries no URL, ADF holds no empty text node, a hard break
|
||||
takes no marks and no consumer has reported a link of hard breaks alone.
|
||||
|
||||
Every reader reads every CommonMark image and empty link, as `spec/flavour.md` §The CommonMark image
|
||||
and §Marks spell out. An image alone in its paragraph reads as the image, its title as the caption.
|
||||
Anywhere else it reads as its alt text linked to its URL, titled by its title. An empty alt text
|
||||
reads as the URL, and an empty link text as its destination. A link text holding no node that takes
|
||||
a link, such as a hard break or a mention, reads as its destination, then those nodes: four of seven
|
||||
readers expected that of hard breaks over dropping them.
|
||||
reads as the URL, and an empty link text as its destination. A link text holding only hard breaks
|
||||
reads as its destination, then those breaks: four of seven readers expected that over dropping them.
|
||||
`[]()` reads as nothing: it renders nothing, so Goal 7 loses no content. A link alone in its
|
||||
paragraph whose whole text is one image reads as that image, with the link marking its `media`.
|
||||
Inside any other link, the alt text is plain text the link marks, and the image's URL and title
|
||||
@@ -258,6 +256,19 @@ The same panel confirmed the other readings on 2026-10-09: a title as the captio
|
||||
text as linked alt text, and an empty link text as its URL, each 3 of 3; `[]()` as nothing, 5 of 7.
|
||||
Their CommonMark suite exceptions are `node-model` (§The CommonMark suite checks three ways).
|
||||
|
||||
## A mark skips a node that cannot hold it, and a link wrapping only such nodes is refused
|
||||
|
||||
2026-10-14, two reader panels of the viewer/editor app, the LLM pipeline and a support agent, 3 of
|
||||
3 each. Goals 1, 4 and 6. Valid while Atlassian's schema withholds marks from inline nodes.
|
||||
|
||||
`src/adf/inline-nodes.ts` holds the mark types Atlassian's schema gives each inline node. A mark
|
||||
spelling skips a node that takes none of its type, so `**Ping !adf:mention[@A]{id=x} now**` reads
|
||||
as bold text around a plain mention. A link whose text holds no node that takes a link, and more
|
||||
than hard breaks, is `unsupported-node-shape` in `losslessMarkdownToAdf`: the panel refused to have
|
||||
`[!adf:status[Done]{color=green}](/u)` read as `/u` linked beside the status. Hard breaks alone keep
|
||||
§An image reads as an image only alone in its paragraph's reading. `adfToLosslessMarkdown` writes a
|
||||
node holding a mark its type does not take as `!adf:carry`, so the round-trip holds.
|
||||
|
||||
## A linked image is written `[](href)`, a captioned image as its directive
|
||||
|
||||
2026-10-11, a writer panel of a product manager, an engineer and a support agent, 3 of 3 on each.
|
||||
@@ -690,7 +701,8 @@ has no fixture.
|
||||
holds.
|
||||
|
||||
For every node and mark the tables spell, the attribute names and kinds equal what `full.json` and
|
||||
`stage-0.json` (§Standards ship as data) hold between them. Value sets stay documentation, since
|
||||
`stage-0.json` (§Standards ship as data) hold between them, and so do the mark types each inline
|
||||
node takes. Value sets stay documentation, since
|
||||
any value round-trips. What the schema holds and the tables do not spell is pinned by name — an
|
||||
attribute as a gap, a type as carried — so a re-pin adding either goes red until someone spells it
|
||||
or pins it.
|
||||
|
||||
+6
-5
@@ -530,8 +530,9 @@ CommonMark could spell is a named error, and so is one spelling none. No link wr
|
||||
nesting, which is CommonMark's own rule: a `[text]` already holding one leaves the outer brackets
|
||||
literal text, and the directive form, open to no literal reading, is a named error. ADF holds no
|
||||
empty text node, so a link whose text is empty reads as its destination linked, `[](/url)` as
|
||||
`/url`, and one whose destination is empty too, `[]()`, as nothing. A link text holding no node
|
||||
that takes a link reads the same, followed by its nodes.
|
||||
`/url`, and one whose destination is empty too, `[]()`, as nothing. A link text holding only hard
|
||||
breaks reads the same, followed by its hard breaks; one holding other nodes, none of which takes a
|
||||
link, is a named error.
|
||||
|
||||
- `border` — Attributes: `color` (string, `#rrggbb` or `#rrggbbaa`), `size` (number, 1–3).
|
||||
- `code`, `em`, `strike`, `strong` — Attributes: none.
|
||||
@@ -548,9 +549,9 @@ A block's `marks` key also holds these, which no inline spelling wraps:
|
||||
- `fragment` — Attributes: `localId` (string), `name` (string).
|
||||
- `indentation` — Attributes: `level` (number, 1–6).
|
||||
|
||||
A spelling adds its mark to every inline node it wraps but one Atlassian's schema withholds the mark
|
||||
from: `hardBreak`, `date`, `emoji`, `inlineCard`, `mention` and `status` take none a spelling makes,
|
||||
and `mediaInline` only `border` and `link`. Nesting is the marks array in order, outermost first:
|
||||
A spelling adds its mark to every text node it wraps, and to every other inline node Atlassian's
|
||||
schema gives that mark: `hardBreak`, `date`, `emoji`, `inlineCard`, `mention` and `status` take none
|
||||
a spelling makes, and `mediaInline` only `border` and `link`. Nesting is the marks array in order, outermost first:
|
||||
`_!adf:underline[x]_` gives marks `[em, underline]`, `!adf:underline[_x_]` the reverse. `adfToLosslessMarkdown` nests in
|
||||
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
|
||||
|
||||
+13
-8
@@ -2,15 +2,15 @@ import type { AttributeVocabulary } from './attribute-vocabulary.ts'
|
||||
|
||||
export type InlineNodeModel = {
|
||||
attributes: AttributeVocabulary
|
||||
marks: readonly string[]
|
||||
markTypes: readonly string[]
|
||||
textAttribute?: string
|
||||
}
|
||||
|
||||
export const inlineNodes: Readonly<Record<string, InlineNodeModel>> = {
|
||||
date: { attributes: { localId: 'string', timestamp: 'string' }, marks: ['annotation'] },
|
||||
emoji: { attributes: { id: 'string', localId: 'string', shortName: 'string', text: 'string' }, marks: ['annotation'], textAttribute: 'text' },
|
||||
hardBreak: { attributes: { localId: 'string', text: 'string' }, marks: [] },
|
||||
inlineCard: { attributes: { data: 'json', localId: 'string', url: 'string' }, marks: ['annotation'] },
|
||||
date: { attributes: { localId: 'string', timestamp: 'string' }, markTypes: ['annotation'] },
|
||||
emoji: { attributes: { id: 'string', localId: 'string', shortName: 'string', text: 'string' }, markTypes: ['annotation'], textAttribute: 'text' },
|
||||
hardBreak: { attributes: { localId: 'string', text: 'string' }, markTypes: [] },
|
||||
inlineCard: { attributes: { data: 'json', localId: 'string', url: 'string' }, markTypes: ['annotation'] },
|
||||
mediaInline: {
|
||||
attributes: {
|
||||
alt: 'string',
|
||||
@@ -23,12 +23,17 @@ export const inlineNodes: Readonly<Record<string, InlineNodeModel>> = {
|
||||
type: 'string',
|
||||
width: 'number',
|
||||
},
|
||||
marks: ['annotation', 'border', 'dataConsumer', 'link'],
|
||||
markTypes: ['annotation', 'border', 'dataConsumer', 'link'],
|
||||
},
|
||||
mention: { attributes: { accessLevel: 'string', id: 'string', localId: 'string', text: 'string', userType: 'string' }, marks: ['annotation'], textAttribute: 'text' },
|
||||
status: { attributes: { color: 'string', localId: 'string', style: 'string', text: 'string' }, marks: ['annotation'], textAttribute: 'text' },
|
||||
mention: { attributes: { accessLevel: 'string', id: 'string', localId: 'string', text: 'string', userType: 'string' }, markTypes: ['annotation'], textAttribute: 'text' },
|
||||
status: { attributes: { color: 'string', localId: 'string', style: 'string', text: 'string' }, markTypes: ['annotation'], textAttribute: 'text' },
|
||||
}
|
||||
|
||||
export function inlineNodeModel(type: string): InlineNodeModel | undefined {
|
||||
return Object.hasOwn(inlineNodes, type) ? inlineNodes[type] : undefined
|
||||
}
|
||||
|
||||
// An unknown type is no inline leaf the table holds, so the table withholds nothing from it.
|
||||
export function takesMark(type: string, markType: string): boolean {
|
||||
return inlineNodeModel(type)?.markTypes.includes(markType) ?? true
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ test("the ADF JSON Schemas hold no type the tables leave unspelled, the pinned c
|
||||
|
||||
test('the inline node table gives a type exactly the mark types the ADF JSON Schemas give it, in order', () => {
|
||||
const { marked } = schemaTypes()
|
||||
for (const [type, model] of Object.entries(inlineNodes)) assert.deepEqual(model.marks, [...(marked.get(type) ?? [])].sort(), type)
|
||||
for (const [type, model] of Object.entries(inlineNodes)) assert.deepEqual(model.markTypes, [...(marked.get(type) ?? [])].sort(), type)
|
||||
})
|
||||
|
||||
function spelled(): Spelled[] {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { failure, success, type ConvertErrorPath, type Result } from '../../resu
|
||||
import { flavourClaims, highlightDelimiter } from '../portable/conventions.ts'
|
||||
import { holdsNullCharacter, trimTrailingSpace } from '../commonmark/grammar.ts'
|
||||
import { identicalMark, isSpellableText, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
|
||||
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||
import { inlineNodeModel, takesMark } from '../../adf/inline-nodes.ts'
|
||||
import { joinsWhenRead, textBreakSpelling } from '../adjacent-text.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { longestBacktickRun } from '../commonmark/backtick-runs.ts'
|
||||
@@ -226,7 +226,7 @@ function carries(node: AdfNode, carried: ReadonlySet<number>, index: number): bo
|
||||
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 || nodeMarks(node).some((mark) => !model.marks.includes(mark.type))) return true
|
||||
if (model === undefined || nodeMarks(node).some((mark) => !takesMark(node.type, mark.type))) return true
|
||||
const slot = model.textAttribute === undefined ? undefined : nodeAttrs(node)[model.textAttribute]
|
||||
return typeof slot === 'string' && slotFault(node.type, slot) !== undefined && nodeContent(node).length === 0 && node.text === undefined
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { decodeTextEscapes, trimTrailingSpace } from '../commonmark/grammar.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { highlightDelimiter } from '../portable/conventions.ts'
|
||||
import { identicalMarks, mergeAdjacentText, nodeAttrs, nodeMarks } from '../../adf/document.ts'
|
||||
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||
import { inlineNodeModel, takesMark } from '../../adf/inline-nodes.ts'
|
||||
import { joinsWhenRead, textBreakName, textBreakSpelling } from '../adjacent-text.ts'
|
||||
import { matchEmphasis } from '../commonmark/emphasis-matching.ts'
|
||||
import { normalizeLabel, readInlineTarget, readLabel } from '../commonmark/link-syntax.ts'
|
||||
@@ -84,6 +84,7 @@ const ownMarksInMark = 'move the opaque carry, or the node spelling marks=empty,
|
||||
const editorHighlight: AdfMark = { attrs: { color: '#f8e6a0' }, type: 'backgroundColor' }
|
||||
const hreflessLink = 'the link mark spells its href: this one spells none'
|
||||
const linkInLink = 'no link wraps a link: the [content] this one marks already holds one'
|
||||
const unlinkable = 'the text of this link holds only nodes that take no link: write the link around text beside them'
|
||||
const spellableLink = 'link takes the directive form only where CommonMark cannot spell it: this one it can, as [text](url "title") or <url>'
|
||||
const textBreak: TextBreak = { kind: 'textBreak' }
|
||||
|
||||
@@ -210,7 +211,7 @@ function directiveMarkPiece(scan: Scan, name: string, mark: AdfMark, slot: SlotC
|
||||
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.some((node) => takesMark(node, mark.type))) {
|
||||
if (!slot.nodes.some((node) => marksItem(node, mark.type))) {
|
||||
return failure('unsupported-node-shape', `the ${name} mark wraps the [content] it marks: this one wraps only nodes that take no ${name} mark`, scan.path)
|
||||
}
|
||||
const refused = mark.type === 'link' ? refuseLinkDirective(scan, mark, slot.nodes, index) : undefined
|
||||
@@ -266,9 +267,9 @@ function mediaSingle({ alt, definition, link }: Image): AdfNode {
|
||||
return centeredImage([externalMedia(alt, definition.destination, link), ...caption])
|
||||
}
|
||||
|
||||
// ADF holds no empty text node and some nodes take no link, so the destination text carries the link ahead of the text's nodes; an empty destination leaves them alone.
|
||||
// ADF holds no empty text node and a hard break takes no link, so the destination text carries the link ahead of the text's hard breaks; an empty destination leaves the breaks alone.
|
||||
function linkTo(nodes: readonly Inline[], definition: LinkDefinition): Inline[] {
|
||||
if (nodes.some((node) => takesMark(node, 'link'))) return applyMark(nodes, linkMark(definition))
|
||||
if (nodes.some((node) => marksItem(node, 'link'))) return applyMark(nodes, linkMark(definition))
|
||||
return definition.destination === '' ? [...nodes] : [{ marks: [linkMark(definition)], text: definition.destination, type: 'text' }, ...nodes]
|
||||
}
|
||||
|
||||
@@ -290,8 +291,8 @@ function partText(items: readonly Inline[], scan: Scan): Result<AdfNode[]> {
|
||||
}
|
||||
|
||||
// spec/flavour.md, Marks: a spelling skips a node Atlassian's schema withholds its mark from.
|
||||
function takesMark(item: Inline, type: string): boolean {
|
||||
return !isNode(item) || (inlineNodeModel(item.type)?.marks.includes(type) ?? true)
|
||||
function marksItem(item: Inline, type: string): boolean {
|
||||
return !isNode(item) || takesMark(item.type, type)
|
||||
}
|
||||
|
||||
function isNode(item: Inline): item is AdfNode {
|
||||
@@ -400,7 +401,11 @@ function linkPiece(scan: Scan, inner: readonly Piece[], definition: LinkDefiniti
|
||||
const [only] = inner
|
||||
if (inner.length === 1 && only?.kind === 'image') return success({ ...only, link: definition })
|
||||
const nodes = resolveNodes(inner, scan, true)
|
||||
return nodes.ok ? success({ kind: 'nodes', nodes: linkTo(nodes.value, definition) }) : nodes
|
||||
if (!nodes.ok) return nodes
|
||||
if (!nodes.value.some((node) => marksItem(node, 'link')) && nodes.value.some((node) => isNode(node) && node.type !== 'hardBreak')) {
|
||||
return failure('unsupported-node-shape', unlinkable, scan.path)
|
||||
}
|
||||
return success({ kind: 'nodes', nodes: linkTo(nodes.value, definition) })
|
||||
}
|
||||
|
||||
// CommonMark: no link nests inside another, though an image's description holds one.
|
||||
@@ -591,7 +596,7 @@ function markType(character: string, used: number): string {
|
||||
// `*(*a*)*` builds one `em`: a node holds at most one mark of each type.
|
||||
function applyMark(nodes: readonly Inline[], mark: AdfMark): Inline[] {
|
||||
return nodes.map((node) => {
|
||||
if (!isNode(node) || !takesMark(node, mark.type)) return node
|
||||
if (!isNode(node) || !marksItem(node, mark.type)) return node
|
||||
const marks = nodeMarks(node)
|
||||
return marks.some((carried) => carried.type === mark.type) ? node : { ...node, marks: [mark, ...marks] }
|
||||
})
|
||||
|
||||
@@ -911,7 +911,6 @@ test('gives the hard break inside a mark spelling no marks, and reads a link of
|
||||
|
||||
test('gives an inline node no mark Atlassian\'s schema withholds from it', () => {
|
||||
const mention: AdfNode = { attrs: { id: 'b1c2', text: '@A' }, type: 'mention' }
|
||||
const status: AdfNode = { attrs: { color: 'green', text: 'Done' }, type: 'status' }
|
||||
const media: AdfNode = { attrs: { id: 'm1' }, type: 'mediaInline' }
|
||||
const border: AdfMark = { attrs: { color: '#091e42', size: 2 }, type: 'border' }
|
||||
const read = (markdown: string): AdfNode[] | string => content(losslessMarkdownToAdf(markdown))
|
||||
@@ -920,7 +919,9 @@ test('gives an inline node no mark Atlassian\'s schema withholds from it', () =>
|
||||
assert.deepEqual(read('!adf:underline[a !adf:emoji[🎉]{shortName=":tada:"}]\n'), [
|
||||
{ content: [marked('a ', underline), { attrs: { shortName: ':tada:', text: '🎉' }, type: 'emoji' }], type: 'paragraph' },
|
||||
])
|
||||
assert.deepEqual(read('[!adf:status[Done]{color=green}](/u)\n'), [{ content: [marked('/u', link('/u')), status], type: 'paragraph' }])
|
||||
const unlinkable = 'unsupported-node-shape: the text of this link holds only nodes that take no link: write the link around text beside them'
|
||||
assert.equal(read('[!adf:status[Done]{color=green}](/u)\n'), unlinkable)
|
||||
assert.equal(read('[!adf:status[Done]{color=green}\\\n](/u)\n'), unlinkable)
|
||||
assert.deepEqual(read('[a !adf:inlineCard{url="https://x/"}](/u)\n'), [{ content: [marked('a ', link('/u')), { attrs: { url: 'https://x/' }, type: 'inlineCard' }], type: 'paragraph' }])
|
||||
assert.deepEqual(read('_!adf:mediaInline{id=m1}_\n'), [{ content: [media], type: 'paragraph' }])
|
||||
assert.deepEqual(read('!adf:border[!adf:mediaInline{id=m1}]{color="#091e42" size=2}\n'), [{ content: [{ ...media, marks: [border] }], type: 'paragraph' }])
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
`Bar = 9`
|
||||
|
||||
`Next ID = 114`
|
||||
`Next ID = 115`
|
||||
|
||||
| Goal | W |
|
||||
|---|---|
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
| ID | Release | Exempt | Item | R | S | C | A | G | Goals | Score |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 114 | 0.2.1 | defect | **Build text marked `code` holding no mark but `link` and `annotation`, in every reader, and carry any other in `adfToLosslessMarkdown`.** | 3 | 4 | 5 | 5 | 7 | 4 | 15.4 |
|
||||
| 99 | 0.2.1 | defect | **Write a linked external image standing inline or in a table cell in the spelling a reader panel picks, in `adfToPortableMarkdown`.** | 3 | 3 | 5 | 6 | 7 | 7 | 12.9 |
|
||||
| 100 | 0.2.1 | decision | **Put the reading of an image's title as its caption back to a reader panel, now that a writer panel reads a title as hover text.** | 3 | 3 | 3 | 4 | 5 | 4, 6 | 10.9 |
|
||||
| 60 | 0.2.1 | decision | **Collect the questions `parse/` asks `emit/` into one named module.** | 3 | 4 | 4 | 2 | 6 | 2 | 10.7 |
|
||||
@@ -51,7 +52,7 @@
|
||||
| 107 | 0.3.0 | decision | **Spell the block carry's JSON so its length stays linear in the node's JSON.** | 4 | 5 | 7 | 3 | 5 | 1 | 10.8 |
|
||||
| 51 | 0.3.0 | | **Match a reference label to its definition under Unicode case folding.** | 2 | 4 | 5 | 2 | 7 | 4, 5 | 10.4 |
|
||||
| 95 | 0.3.0 | decision | **Find the code-span closers an escape forms, the mark runs a line cannot spell and the text `[` that opens link text by reading the line back, in place of `escapeClosedRuns`, `unspellableRuns` and `lastLinkClose` predicting them.** | 5 | 6 | 9 | 2 | 6 | 1 | 9.5 |
|
||||
| 91 | 0.3.0 | | **Spell an emphasis or link once across the hard breaks between two nodes it marks, in both writers.** | 5 | 5 | 8 | 5 | 6 | 6 | 9.0 |
|
||||
| 91 | 0.3.0 | | **Spell an emphasis or link once across the hard breaks, dates, emoji, inline cards, mentions, statuses and inline media between two nodes it marks, in both writers.** | 5 | 5 | 8 | 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 | 1 | 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 | 4 | 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 | 5 | 4 | 7 | 1 | 19.5 |
|
||||
@@ -68,6 +69,13 @@
|
||||
|
||||
## Details
|
||||
|
||||
### 114. Build text marked `code` holding no mark but `link` and `annotation`, in every reader, and carry any other in `adfToLosslessMarkdown`.
|
||||
|
||||
`**`npm ci`**` builds text holding `[strong, code]`; `code_inline_node` (`spec/adf-schema/full.json`)
|
||||
gives code text `code`, `link` and `annotation` alone, so a site may reject the document. The `==`
|
||||
highlight already skips code (`highlighted`, `src/markdown/parse/inline-content.ts`). Moves the
|
||||
reading of stored markdown, so it ships where semver allows a breaking change.
|
||||
|
||||
### 99. Write a linked external image standing inline or in a table cell in the spelling a reader panel picks, in `adfToPortableMarkdown`.
|
||||
|
||||
`mediaLeaves` (`src/markdown/portable/inline-reduction.ts`) writes such an image `[alt](url)`, dropping
|
||||
@@ -290,11 +298,13 @@ text. Only the property tests catch drift between these and the reader, and it h
|
||||
under the comprehension floor. Reading the line back costs one parse each time a line is tried. The
|
||||
run it carries may differ from today's, which changes lossless output (§The formats are API).
|
||||
|
||||
### 91. Spell an emphasis or link once across the hard breaks between two nodes it marks, in both writers.
|
||||
### 91. Spell an emphasis or link once across the hard breaks, dates, emoji, inline cards, mentions, statuses and inline media between two nodes it marks, in both writers.
|
||||
|
||||
Lands after item 95, which lets the joined spelling be tried and kept only where it reads back. 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
|
||||
typed across a line as two links. No reader gives those other nodes a mark Atlassian's schema
|
||||
withholds from them, so `**Ping !adf:mention[@A]{id=x} now**` writes back with each strong run's
|
||||
inner space spelled `!adf:text{text=" "}`. 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user