mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48:29 +01:00
193 lines
13 KiB
Markdown
193 lines
13 KiB
Markdown
# The HTML dialect
|
|
|
|
The HTML `adfToHtml` writes and `htmlToAdf` reads: semantic elements for the nodes and marks below,
|
|
and the carry element for every other node (`docs/decisions.md` §The HTML dialect). The written form
|
|
is contract (`docs/decisions.md` §The formats are API).
|
|
|
|
## Blocks
|
|
|
|
Blocks are separated by a line feed, and a document holding any ends with one. A document whose
|
|
`content` is an empty array is the empty string.
|
|
|
|
| ADF | HTML |
|
|
| --- | --- |
|
|
| `blockquote` | `<blockquote>`, a line feed, its blocks, a line feed, `</blockquote>` |
|
|
| `bulletList` | `<ul>`, a line feed, its items a line each, a line feed, `</ul>` |
|
|
| `codeBlock` | `<pre><code class="language-js">…</code></pre>`, the language one token holding no ASCII whitespace (a tab, line feed, form feed, carriage return or space); no `class` where it holds no `language` |
|
|
| `heading`, `level` 1 to 6 | `<h1>…</h1>` to `<h6>…</h6>` |
|
|
| `listItem` | its first paragraph straight after `<li>`: bare where it holds content and no attribute, `<li>Bolt</li>`, and as its `<p>` element where it holds an attribute, `<li><p data-local-id="p1">Bolt</p></li>`; where other blocks follow, each after a line feed, and a line feed before `</li>`. Where the first block is no paragraph, holds neither content nor an attribute, or rides the carry: `<li>`, a line feed, its blocks, a line feed, `</li>` |
|
|
| `mediaSingle`, centered, holding one external `media` | `<p><img src="…" alt="…"></p>`, no `alt` where the media holds none; `<p><a href="…"><img …></a></p>` where a link marks it |
|
|
| `orderedList` | as `bulletList` with `<ol>`; `<ol start="3">` for an `order` of 3, a safe integer other than `-0`; no `start` where the list holds no `order` |
|
|
| `paragraph` | `<p>…</p>` |
|
|
| `rule` | `<hr>` |
|
|
| `table` | `<table>`, a line feed, its sections parted by a line feed, a line feed, `</table>`: a `<thead>` where its first row holds cells that are all `tableHeader`, and a `<tbody>` for the other rows where there are any. Each section is its tag, a line feed, its rows a line each, a line feed, its closing tag |
|
|
| `tableCell`, `tableHeader` | `<td>`, `<th>`, each holding its blocks as a `listItem` does: `<td>Bolt M8</td>`; attributes `colspan`, `rowspan`, `style="background-color:#fffae6"` for `background`, and `data-colwidth="120,80"` for `colwidth`, first and in that order |
|
|
| `tableRow` | `<tr>`, its cells with no line feed between them, `</tr>` |
|
|
|
|
A container holding no `content` key is its element holding nothing: `<blockquote></blockquote>`,
|
|
`<ul></ul>`, `<li></li>`, `<table></table>`, `<tr></tr>`, `<td></td>`; so is a paragraph or heading,
|
|
`<p></p>`.
|
|
|
|
```html
|
|
<table>
|
|
<thead>
|
|
<tr><th>Part</th><th>Stock</th><th>Bin</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td rowspan="2">Bolt M8</td><td colspan="2" style="background-color:#fffae6" data-colwidth="120,80">Low</td></tr>
|
|
<tr><td>12</td><td>A4</td></tr>
|
|
</tbody>
|
|
</table>
|
|
```
|
|
|
|
A table and each of its rows count one nesting level; a cell doesn't count, nor does a list item.
|
|
|
|
On a paragraph, heading, quote, list, list item, table, row, cell, code block or rule, each attribute its type holds
|
|
(`spec/flavour.md` §Block nodes) that the element does not write as `level`, `order`, `language`,
|
|
`colspan`, `rowspan`, `background` or `colwidth` is a `data-*` attribute, named as HTML's `dataset` names it, `localId` as `data-local-id`, after the
|
|
element's own attributes and sorted by name. A string is its value; any other value is its JSON,
|
|
compact: `<ol start="3" data-local-id="91177a27-b0ff">`, `<pre data-wrap="true"><code>`. A code
|
|
block's `data-*` attributes stand on its `<pre>`.
|
|
|
|
## Inline
|
|
|
|
| ADF | HTML |
|
|
| --- | --- |
|
|
| `date` | `<time class="adf-date" datetime="2025-08-25" data-timestamp="1756080000000">2025-08-25</time>`, the day in UTC |
|
|
| `em`, `strong`, `code` | `<em>`, `<strong>`, `<code>` around the text |
|
|
| `emoji` | `<span class="adf-emoji" data-id="1f389" data-short-name=":tada:">🎉</span>`, its `text` the content |
|
|
| `hardBreak` | `<br>` |
|
|
| `inlineCard`, holding a `url` | `<a class="adf-inline-card" href="https://example.com/spec">https://example.com/spec</a>` |
|
|
| `inlineCard`, holding `data` and no `url` | `<a class="adf-inline-card" href="https://example.com/plan" data-data="{"name":"Release plan","url":"https://example.com/plan"}">Release plan</a>`, `href` the URL its `data` holds and the text its name, else that URL |
|
|
| `link`, holding `href` and an optional `title` | `<a href="…" title="…">` around the text |
|
|
| `mention` | `<span class="adf-mention" data-access-level="CONTAINER" data-id="5b10a2" data-user-type="DEFAULT">@Mikael</span>`, its `text` the content |
|
|
| `status` | `<span class="adf-status" data-color="yellow">In review</span>`, its `text` the content |
|
|
| `text` | its text, escaped |
|
|
|
|
A `date`, `emoji`, `inlineCard`, `mention` or `status` writes each other attribute its type holds as a
|
|
`data-*` attribute, named and sorted as on a block, `data-local-id` among them.
|
|
|
|
A text node's marks nest in the order its `marks` array lists them, the first outermost:
|
|
`<strong><em>manual</em></strong>`. Each text node writes its own elements, so two text nodes a link
|
|
marks are two `<a>` elements.
|
|
|
|
## Escaping
|
|
|
|
Text and attribute values escape `&`, `<`, `>` and `"` as `&`, `<`, `>` and `"`.
|
|
Attribute values are double-quoted. Every URL is written as the ADF holds it, whatever its scheme
|
|
(`docs/decisions.md` §HTML writes a URL as the ADF holds it, whatever its scheme): a link's `href`,
|
|
an image's `src`, a card's `href`, and the `href` of each link a carry element shows.
|
|
|
|
HTML text and attribute values cannot hold these (WHATWG HTML §13.1.3): any control but a tab, line
|
|
feed or form feed, so a carriage return too, which a parser reads as a line feed; a noncharacter;
|
|
and a lone surrogate, which UTF-8 cannot encode. A node holding one in a value its element writes
|
|
rides the carry.
|
|
|
|
## The carry element
|
|
|
|
A node the sections above cannot write where it stands is the carry element: `<div>` in block
|
|
position, `<span>` inline, of class `adf-carry`, holding the node's JSON in `data-adf` and showing
|
|
its content. A carry element may hold a node the sections above write, as the markdown carry may:
|
|
it reads back as the node it holds, so HTML stored while a node rode the carry reads the same once a
|
|
later release writes it as its own element.
|
|
|
|
```html
|
|
<div class="adf-carry" data-adf="{"attrs":{"panelType":"info"},…,"type":"panel"}"><p>Read the <a href="https://spec.commonmark.org/">CommonMark spec</a> first.</p><p>Then import.</p></div>
|
|
<p>Ask <span class="adf-carry" data-adf="{"attrs":{"id":"5b10a2"},"type":"mention"}">@5b10a2</span></p>
|
|
```
|
|
|
|
- `data-adf` holds the node's JSON compact, keys sorted, as `!adf:carry` does in markdown, its type
|
|
included, each character HTML cannot hold spelled as a JSON escape, `\u0085`. A document holding
|
|
no `content` key is the block carry of `{"type":"doc","version":1}`.
|
|
- The content shown is every text node's text below the node, and what a node holding no text shows
|
|
in its place: a mention's text, else `@` and its id where it has one; an emoji's text, else its
|
|
`shortName`; a status's text; a date as `2026-09-13` in UTC; a card's URL, else its name, else the
|
|
URL its data holds; media's `alt`, else an external one's URL; an extension's text; an expand's
|
|
title.
|
|
- In a `<div>`, each block's content is a `<p>`, and a code block's is
|
|
`<pre><code class="language-js">…</code></pre>`, keeping its line feeds and spaces. The `<code>`
|
|
takes the class the code block's own element takes, and none where the language is not one
|
|
token free of ASCII whitespace. In a `<span>`, a `<br>` parts each block's content from the
|
|
next.
|
|
- 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
|
|
its URL.
|
|
- 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)`,
|
|
the word `image` linking to the image:
|
|
`<a href="https://example.com/">The moon</a> (<a href="https://example.com/moon.jpg">image</a>)`.
|
|
- A node referencing what it cannot show is a note naming it, `<em>(image)</em>`: `image` for
|
|
media with no `alt` or URL, `link card` for a card with neither URL nor name, an extension's
|
|
key, else `extension`, for an extension with no text, and `synced block` for a `syncBlock`. A
|
|
link marking the node goes around the note: `<a href="…"><em>(image)</em></a>`.
|
|
- Characters HTML cannot hold are dropped.
|
|
- A node rides the carry where it holds a mark or a key its element does not write, an attribute
|
|
its type does not hold as that kind, an empty `attrs`, `content` or `marks`, or a value its
|
|
element cannot hold:
|
|
- text holding no character, or a character HTML cannot hold; text holding a mark twice, or a
|
|
mark with attributes besides a link's `href` and `title`;
|
|
- an unmarked text node straight after one written as plain text, which would read back as one;
|
|
- a link whose `href` or `title` holds a character HTML cannot hold, and an image whose `url` or
|
|
`alt` holds one or whose link's `href` or `title` does;
|
|
- an image holding an attribute besides its `center` layout and its media's `type`, `url` and
|
|
non-empty `alt`;
|
|
- a heading whose `level` is not 1 to 6, and an ordered list whose `order` is not a safe integer
|
|
or is `-0`;
|
|
- an emoji, mention or status with no `text`, and a date whose `timestamp` names no day from year
|
|
1 to 9999;
|
|
- a card holding an empty `url`, both a `url` and `data`, or neither, and one holding `data`
|
|
alone whose `url` is missing or holds only whitespace;
|
|
- a code block holding more than one text node, a marked one, or a language holding ASCII
|
|
whitespace;
|
|
- a hard break holding an attribute;
|
|
- a list holding a node that is no `listItem`, or an item holding a mark, an empty key or an
|
|
attribute its type does not hold: the whole list rides the carry;
|
|
- a table holding a node that is no `tableRow`, a row holding a node that is no cell, or a table,
|
|
row or cell holding a mark, an empty key or an attribute its type does not hold: the whole table
|
|
rides the carry. So does a cell whose `colspan` or `rowspan` isn't a positive safe integer, whose
|
|
`colwidth` isn't a list of numbers, or whose `background` isn't a string of one or more letters
|
|
and digits, after an optional `#`;
|
|
- a `listItem`, `tableRow` or cell outside its parent, an inline node in block position and a block
|
|
node inline.
|
|
- An inline card a link marks, a known block holding `text`, a known non-text inline node holding
|
|
`text` or non-empty `content`, and a known block that takes no content, such as `rule`, holding
|
|
non-empty `content` are refused as `unsupported-node-shape`, carried or not, by `adfToHtml` and by
|
|
`htmlToAdf` reading a carry element, since Atlassian's schema allows none of these.
|
|
- A block carry's JSON nests at most 500 levels less the depth it stands at, an inline carry's at
|
|
most 500, as in markdown.
|
|
|
|
## Reading
|
|
|
|
`htmlToAdf` reads the HTML above, so `htmlToAdf(adfToHtml(doc))` deep-equals `doc`. Wherever the HTML
|
|
above holds a spelling in the table's left column, `htmlToAdf` also reads the right one, as a browser
|
|
serializes HTML (WHATWG HTML §13.3) or a form post sends it, in a carry element's `data-adf` and in
|
|
what it shows too:
|
|
|
|
| The HTML above writes | Also read |
|
|
| --- | --- |
|
|
| `"` in text | a raw `"` |
|
|
| a no-break space | ` ` |
|
|
| `<` and `>` in an attribute value | a raw `<` and `>`, as older browsers write them |
|
|
| a line feed | `\r\n` |
|
|
|
|
A refusal's position counts the HTML as handed in. `htmlToAdf` reads a carry element's node from its
|
|
`data-adf`, and what the element shows, through its closing tag, must be what `adfToHtml` shows for
|
|
that node in either spelling: an edit there would be lost, so it is `unmappable-html`.
|
|
|
|
- Any other HTML is `unmappable-html`: another element, attribute or escape, attributes in another
|
|
order, a block on the line of another, a carriage return not followed by a line feed, or
|
|
non-empty HTML not ending in a line feed.
|
|
- An emoji, mention, status, date or card element with empty text, a date whose `datetime` or text
|
|
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`.
|
|
- 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,
|
|
is not compact with its keys sorted, or holds a number JSON cannot spell is
|
|
`unsupported-node-shape`, and so is the document's own carry element beside another block.
|
|
- Blocks nest at most 500 levels, and a carried node's JSON at most as many as its position leaves,
|
|
as in writing; past that is `unsupported-nesting-depth`.
|