3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-12 11:48:29 +01:00

Refuse an inline card a link marks in adfToHtml as unsupported-node-shape

This commit is contained in:
2026-11-04 19:37:08 +01:00
committed by Lillem4n
parent 81d741d3b6
commit 64d4267218
7 changed files with 32 additions and 20 deletions
+2 -1
View File
@@ -17,7 +17,8 @@
`localId` as `data-local-id`. Every other node is an element of class `adf-carry` holding the
node's JSON in `data-adf` and showing its content, links included. Every URL is written as the
ADF holds it, `javascript:` included: sanitize HTML from an author you do not trust before
showing it.
showing it. It refuses an inline card a link marks, which Atlassian's schema does not allow, as
`unsupported-node-shape`.
- `htmlToAdf` reads `adfToHtml`'s HTML back to the document it came from, a carry element from its
`data-adf`. It reads that HTML as a browser's `innerHTML` writes it too (a raw `"` in text,
`&nbsp;`, a raw `<` or `>` in an attribute value) and as a form post sends it (`\r\n` for each
+2
View File
@@ -646,6 +646,8 @@ Every call can raise these:
twice, a block or a non-text inline node holding `text`, or a leaf block or a non-text inline node
holding `content`. Keep the ADF and show the document read-only; where you built the ADF, fix
what the message names.
- **Writing, in `adfToHtml`**: an inline card marked by a link, which Atlassian's schema does not
allow. Remove the mark; where you hold the ADF, the message's path names the card.
- **Writing, in `adfToPortableMarkdown`**: a line or task list the portable writer cannot spell.
This is a bug in this library: report it.
- **Past the engine's longest string**:
+4 -3
View File
@@ -441,9 +441,10 @@ it joins the second panel's `<a>` to that portable spelling.
Inside a carry element, a card a link marks links to the link's target, and shows its own URL as its
text where it has one, so both stay. A card showing only its name keeps its URL in `data-adf` alone.
Atlassian's schema gives no card a link mark, so only a hand-built document meets this;
`todo.md` item 133 settles, in 0.3.0, how such a card is written. Recorded 2026-10-24
without a panel of its own: it puts the second panel's `<a>` around the text a card shows.
Atlassian's schema gives no card a link mark, so only a hand-built document meets this, and
`adfToHtml` refuses such a card among the inline nodes it writes as `unsupported-node-shape`: no
element holds both its link and the card's own. Inside a carried block it shows as above, since a
carried node's JSON is not checked. Recorded 2026-10-24 without a panel of its own: it puts the second panel's `<a>` around the text a card shows.
A fifth panel (2026-11-04, three readers in the same roles, then four more for what the first three
split on) was asked how to write a mention, emoji, status, date and inline card, which until then
+3 -1
View File
@@ -94,7 +94,7 @@ later release writes it as its own element.
- A hard break is `<br>`, and so is a line feed in text outside a `<div>`'s code block.
- Text a link marks is `<a href="…" title="…">` around it, or the text alone where HTML cannot
hold the link's `href` or `title`; a `title` that is no string is left out. A card is a link to
the target of a link marking it, else to its URL.
its URL; inside a carry element, to the target of a link marking it.
- An external image with no link to another target is a link to its URL. An image marked by a
link to another target shows as portable markdown writes it inline. First comes the link,
around the image's `alt`, else the link's target, else the image's URL. Then comes ` (image)`,
@@ -155,6 +155,8 @@ that node in either spelling: an edit there would be lost, so it is `unmappable-
is not the day its `data-timestamp` names, a card without `data-data` whose `href` and text are
not one URL, and a card with `data-data` whose `href` is not the URL its `data` holds or whose
text is not its name, else that URL, are `unmappable-html`.
- `adfToHtml` refuses an inline card a link marks, which Atlassian's schema gives no link mark, as
`unsupported-node-shape`.
- A `data-*` attribute that its element does not take, such as `data-level` on `<h1>`, or that holds
a value outside its attribute's kind or not written as compact JSON, is `unsupported-node-shape`.
- A carry element's `data-adf` holding invalid JSON is `unmappable-html`. JSON that is no ADF node,
+15 -3
View File
@@ -172,7 +172,6 @@ test('carries an emoji, mention, status, date or card holding what its element c
for (const timestamp of ['253402300799999', '-62135596800000']) assert.equal(carried({ attrs: { timestamp }, type: 'date' }), false)
assert.equal(carried({ attrs: { data: { name: 'Roadmap' } }, type: 'inlineCard' }), true)
assert.equal(carried({ attrs: { data: { name: 'Roadmap' }, url: 'https://example.com/' }, type: 'inlineCard' }), true)
assert.equal(carried({ attrs: { url: 'https://example.com/' }, marks: [{ attrs: { href: 'https://example.org/' }, type: 'link' }], type: 'inlineCard' }), true)
assert.equal(carried({ attrs: { url: '' }, type: 'inlineCard' }), true)
assert.equal(carried({ attrs: { url: 'https://example.com/\u0001' }, type: 'inlineCard' }), true)
assert.equal(carried({ attrs: { data: { name: 'Roadmap', url: 'https://example.com/' } }, marks: [comment], type: 'inlineCard' }), true)
@@ -278,9 +277,22 @@ test('shows a carried image marked by a link to another target as that link, the
assert.equal(inline({ attrs: { href: 'https://example.com/', title: 5 }, type: 'link' }), '<a href="https://example.com/">The moon</a> (<a href="https://example.com/moon.jpg">image</a>)')
})
test('links a carried card a link marks to that link\'s target alone', () => {
test('links a card a link marks, inside a carried node, to that link\'s target alone', () => {
const card: AdfNode = { attrs: { url: 'https://example.com/roadmap' }, marks: [{ attrs: { href: 'https://example.com/' }, type: 'link' }], type: 'inlineCard' }
assert.equal(shown(html(adfToHtml(document(paragraph(card))))), '<a href="https://example.com/">https://example.com/roadmap</a>')
assert.equal(shown(html(adfToHtml(document({ content: [paragraph(card)], type: 'panel' })))), '<p><a href="https://example.com/">https://example.com/roadmap</a></p>')
})
test('refuses an inline card a link marks, which ADF\'s schema gives no link mark', () => {
const marks: AdfMark[] = [{ attrs: { href: 'https://example.com/' }, type: 'link' }]
const cards: AdfNode[] = [{ attrs: { url: 'https://example.com/roadmap' }, marks, type: 'inlineCard' }, { attrs: { data: { name: 'Roadmap', url: 'https://example.com/roadmap' } }, marks, type: 'inlineCard' }]
for (const card of cards) {
const refused = adfToHtml(document(paragraph(text('See '), card)))
assert.equal(!refused.ok && refused.error.code, 'unsupported-node-shape')
assert.deepEqual(!refused.ok && refused.error.path, ['content', 0, 'content', 1])
assert.match(!refused.ok ? refused.error.message : '', /inlineCard.*link/)
}
const nested = adfToHtml(document({ content: [{ content: [paragraph(cards[0] ?? text('x'))], type: 'listItem' }], type: 'bulletList' }))
assert.deepEqual(!nested.ok && nested.error.path, ['content', 0, 'content', 0, 'content', 0, 'content', 0])
})
test('shows a note naming what a carried node references where it shows no text, linked where a link marks the node', () => {
+4 -1
View File
@@ -5,13 +5,16 @@ import { elementOfMark } from '../mark-elements.ts'
import { holdsOnlyAttributes, isSpellableText, isUnmarkedSpellableText, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
import { linkAttrs } from '../../adf/mark-attributes.ts'
import { nodeElement } from './node-element.ts'
import { success, type ConvertErrorPath, type Result } from '../../result.ts'
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { writableValue } from '../escaping.ts'
export function inlineHtml(nodes: readonly AdfNode[], path: ConvertErrorPath): Result<string> {
let html = ''
let afterPlainText = false
for (const [index, node] of nodes.entries()) {
if (node.type === 'inlineCard' && nodeMarks(node).some(({ type }) => type === 'link')) {
return failure('unsupported-node-shape', 'An inlineCard takes no link mark in ADF.', [...path, 'content', index])
}
// Two unmarked text nodes side by side read back as one, so the second rides the carry.
const written: string | undefined = afterPlainText && isUnmarkedSpellableText(node) ? undefined : tryInlineNode(node)
if (written === undefined) {
+2 -11
View File
@@ -12,13 +12,12 @@
| ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 133 | 0.3.0 | | question | **Record how `adfToHtml` writes an inline card a link marks in `docs/decisions.md` §The HTML dialect, and make `adfToHtml` and `htmlToAdf` follow it.** | 2 | 2 | 2 | 4 | 4 | 6 by 3 | 3.0 |
| 163 | 0.3.0 | | | **Gather a corpus of real-world ADF documents from public Jira and Confluence sites, each recording where it came from and under what licence.** | 3 | 2 | 4 | 7 | 4 | 6 by 3 | 3.0 |
| 164 | 0.3.0 | 163 | | **Publish a feature table setting chosen corpus examples beside what each call writes for them, readable at a glance by people and parseable by LLMs, and kept current by the gate.** | 3 | 2 | 1 | 4 | 5 | 3 by 6, 6 by 4 | 6.2 |
| 165 | 0.3.0 | 164 | | **Have a reader panel seated from `## Audience` judge each feature-table row reasonable or not, and file an item for each row it rejects.** | 2 | 2 | 1 | 3 | 3 | 6 by 5 | 7.5 |
| 132 | 0.3.0 | | | **Write tables as `<table>` in `adfToHtml` and read them back in `htmlToAdf`, in place of the carry element.** | 2 | 2 | 2 | 4 | 5 | 6 by 4 | 2.4 |
| 73 | 0.3.0 | 132, 133, 163, 165 | | **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 | 2 | 3 | 2 | 3 by 8 | 16.9 |
| 72 | 0.3.0 | 132, 133 | decision | **Re-create `gitea.larvit.se/larvit/adf-codec` as a read-only pull mirror of GitHub, its issues and Actions off and its description pointing at GitHub.** | 1 | 2 | 1 | 2 | 1 | 3 by 1 | 2.5 |
| 73 | 0.3.0 | 132, 163, 165 | | **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 | 2 | 3 | 2 | 3 by 8 | 16.9 |
| 72 | 0.3.0 | 132 | decision | **Re-create `gitea.larvit.se/larvit/adf-codec` as a read-only pull mirror of GitHub, its issues and Actions off and its description pointing at GitHub.** | 1 | 2 | 1 | 2 | 1 | 3 by 1 | 2.5 |
| 153 | 0.4.0 | | question | **Decide whether `src/markdown/` root may import `emit/`, `parse/` or `portable/`, and hold the answer in `.oxlintrc.json` and §The source parts by ADF and format.** | 1 | 2 | 2 | 3 | 2 | code health by 3 | 26.2 |
| 156 | 0.4.0 | | | **Stop a leg's container when its script is interrupted, so Ctrl-C ends `ci.sh` or `mutation-tests/run.sh` at once.** | 1 | 2 | 1 | 2 | 1 | code health by 2 | 20.9 |
| 128 | 0.4.0 | | | **Pin the portable flavour's block reading and writing with tests: callout titles past one line, a link beside other marks in a title, the nesting limit of callouts and task lists, refusals inside tasks and their paths, and empty or non-paragraph tasks.** | 2 | 1 | 1 | 2 | 3 | code health by 3 | 19.6 |
@@ -69,14 +68,6 @@
## Details
### 133. Record how `adfToHtml` writes an inline card a link marks in `docs/decisions.md` §The HTML dialect, and make `adfToHtml` and `htmlToAdf` follow it.
Class: the HTML dialect's spellings. Earlier entries: a card is `<a class="adf-inline-card" href …>`,
and HTML nests no `<a>` in an `<a>`, so the card's element leaves no room for the link. Candidate
entry: "A card a link marks rides the carry, which links to the link's target and shows the card's
URL where it has one; no document Atlassian's editor writes holds one." Its instance is today's
output. The item ends with that entry or with a spelling the maintainer names.
### 163. Gather a corpus of real-world ADF documents from public Jira and Confluence sites, each recording where it came from and under what licence.
The maintainer asked for it on 2026-11-03: paragraphs such as bold around a code span or italics over a line break occur all the time, and the project holds no evidence of what real documents contain. Find public sites that serve ADF without an account, such as Jira Cloud issues on public projects through the REST v3 API, and fetch enough documents to cover the node and mark types the flavour spells.