mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48:29 +01:00
280 lines
22 KiB
Markdown
280 lines
22 KiB
Markdown
# Changelog
|
|
|
|
## Unreleased
|
|
|
|
- `readAdfDocument` and the three writers name the node `not-an-adf-document` refuses: `path` leads
|
|
to a node that is not an ADF node, and the message says what is wrong with it, such as which
|
|
attribute holds a value that is not JSON. A getter, an object holding itself, a throwing Proxy
|
|
and a bad array length are still refused at the empty path.
|
|
- **Breaking:** `portableMarkdownToAdf` reads more of GFM's pipe table. A body row holding fewer
|
|
cells than the header is padded with empty cells, where `0.3.1` returned `malformed-pipe-table`;
|
|
a table whose padding would add more cells than the table holds is `malformed-pipe-table`.
|
|
A line after the table that holds no `|` and opens no other block is a row of one cell, where it
|
|
was a paragraph. A row holding more cells stays an error result.
|
|
- **Breaking:** `a |` over `--- |` is a one-column table. `portableMarkdownToAdf` reads it as one,
|
|
and `losslessMarkdownToAdf` returns `malformed-pipe-table` for it, as for any table without
|
|
leading pipes, where `0.3.1` read both as a paragraph. `MIGRATION.md` shows the change.
|
|
- `adfToPortableMarkdown` keeps a link around the note standing in place of a node it marks, such
|
|
as a stored image: `[_(image not included)_](https://example.com/a)`.
|
|
- **Breaking:** `readAdfDocument` replaces `isAdfDocument`: it returns a `Result<AdfDocument>` whose
|
|
error names the branch that refused, and `MIGRATION.md` shows the change.
|
|
- `readAdfDocument`, `adfToHtml`, `adfToLosslessMarkdown` and `adfToPortableMarkdown` read the
|
|
document once into a plain copy. A document holding an accessor property, or a Proxy that throws,
|
|
returns `not-an-adf-document`, where `0.3.1` ran the getter, looped or threw. The copy keeps own
|
|
enumerable keys alone, so an inherited or non-enumerable key drops. A document of more than
|
|
10,000,000 JSON values returns `unsupported-node-shape`.
|
|
|
|
## 0.3.1
|
|
|
|
- The README opens with a one-line description, a list of the calls and the package's perks, and
|
|
`package.json`'s description, which npmjs shows, is now that same line, naming Jira and Confluence.
|
|
|
|
## 0.3.0
|
|
|
|
- **Breaking:** `adfToPortableMarkdown` refuses, as `unsupported-node-shape`, 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`. `adfToLosslessMarkdown` now refuses
|
|
them inside a carried node too, and `losslessMarkdownToAdf` refuses a carry holding one.
|
|
`MIGRATION.md` says how to convert stored markdown.
|
|
- **Breaking:** `losslessMarkdownToAdf` gives a `date`, `emoji`, `inlineCard`, `mention`, `status`
|
|
or `mediaInline` only the marks Atlassian's schema lets it hold: `**!adf:mention[@A]{id=…}**` now
|
|
reads as a plain mention. A link or directive mark around only nodes that cannot take it is
|
|
`unsupported-node-shape`, even in markdown `adfToLosslessMarkdown` wrote. A link around hard
|
|
breaks alone still reads as before. `adfToLosslessMarkdown` writes a node holding a mark its type
|
|
cannot take as `!adf:carry{json="…"}`, except an inline card a link marks, which it refuses.
|
|
`MIGRATION.md` says how to convert stored markdown.
|
|
- **Breaking:** `adfToLosslessMarkdown` and `adfToPortableMarkdown` refuse an inline card a link marks
|
|
as `unsupported-node-shape` at the card's path, and `losslessMarkdownToAdf` refuses a carry
|
|
holding one, since Atlassian's schema gives a card no link mark. `MIGRATION.md` says what to do.
|
|
- **Breaking:** every reader gives code text no mark but `link` and `annotation`, as Atlassian's
|
|
schema requires: ``**`npm ci`**`` now reads as plain code, and a directive mark other than a link
|
|
around only code spans is `unsupported-node-shape`. `adfToLosslessMarkdown` writes code text
|
|
holding another mark as `!adf:carry{json="…"}`, and `adfToPortableMarkdown` drops that mark.
|
|
- **Breaking:** `losslessMarkdownToAdf` gives text no `border` mark, as Atlassian's schema requires:
|
|
`!adf:border[x]{color="#091e42" size=2}` is `unsupported-node-shape`, and only a
|
|
`mediaInline` takes `border`. `adfToLosslessMarkdown` writes text holding `border` as `!adf:carry{json="…"}`.
|
|
`MIGRATION.md` says how to convert stored markdown.
|
|
- `docs/feature-table.md` sets example documents beside what `adfToLosslessMarkdown`,
|
|
`adfToPortableMarkdown` and `adfToHtml` write for them, and says whether each output reads back to
|
|
the same document.
|
|
- The package's description and keywords say it needs no React, Atlaskit or runtime dependency and
|
|
runs in Node, Forge, Deno, Bun and browsers, and the README opens with the call to take for each
|
|
need. The README's first comparison table lists the npm packages running in Node that convert ADF
|
|
to or from markdown, wrappers and forks aside; its second table lists other ecosystems'
|
|
libraries and Atlassian's own paths.
|
|
- `adfToHtml` writes ADF as HTML: paragraphs, headings, lists, quotes, tables, code blocks, rules,
|
|
hard breaks, a centered, uncaptioned external image holding no attribute but its URL and alt, and
|
|
`em`, `strong`, `code` and links as their elements. An unmarked emoji, mention, status, date
|
|
and card holding a URL, or `data` holding one, not both, are `<span class="adf-emoji">`,
|
|
`<span class="adf-mention">`, `<span class="adf-status">`, `<time class="adf-date">` and
|
|
`<a class="adf-inline-card">`. A paragraph, heading, quote, list, list item, table, row, cell,
|
|
code block, rule, emoji, mention, status, date or card writes its other attributes as `data-*` attributes,
|
|
`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.
|
|
It refuses, carried or not and as `unsupported-node-shape`, 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`.
|
|
- `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,
|
|
` `, a raw `<` or `>` in an attribute value) and as a form post sends it (`\r\n` for each
|
|
line feed). An edit to an emoji's, mention's or status's text becomes its `text`; an edit to a
|
|
date's or a card's text alone is refused. It refuses any other HTML, including an edit to what a
|
|
carry element shows, with the codes README §The calls lists.
|
|
It also refuses a carry element holding a shape `adfToHtml` refuses.
|
|
- `adfToLosslessMarkdown` and `adfToPortableMarkdown` write an `em`, `strike`, `strong` or `link`
|
|
mark once across the hard breaks between two texts it marks: `_One\` then `Two_`, where they
|
|
wrote `_One_\` then `_Two_`. They write an `em`, `strike` or `strong` mark once around a code
|
|
span: ``**Run `npm ci` first**``. `adfToLosslessMarkdown` also writes those three once across
|
|
mentions, emoji, dates, statuses, inline cards and inline media, as
|
|
`**Ping !adf:mention[@A]{id=x} now**`, and a link once across mentions, emoji, dates, statuses
|
|
and inline cards. The markdown reads back to the same ADF as before, except that
|
|
`adfToPortableMarkdown` drops whitespace beside a hard break a mark crosses, where CommonMark
|
|
strips it. Where the joined line would not read back, the writers write the line as before.
|
|
- Every reader keeps the URL and title of an image inside a link's text, where it dropped them:
|
|
after the link comes `(image)`, which links to the image, so
|
|
`[look at  tonight](/moon)` reads as
|
|
`[look at the moon tonight](/moon) ([image](moon.png "Full"))`. A link to the image's own URL
|
|
takes no `(image)`, unless the image's title is one the link lacks.
|
|
- `adfToPortableMarkdown` keeps both targets of a linked external image standing inline, as in a
|
|
table cell: `[alt](href) ([image](url))`, where it wrote `[alt](url)` and dropped the link's target.
|
|
- `adfToPortableMarkdown` keeps a link title holding a backslash or an entity reference such as
|
|
`&`, and in a table cell one holding a `|`, where it dropped the title. It escapes the
|
|
character where CommonMark would read it otherwise, as in `[docs](https://example.com "AT\&T")`,
|
|
and `portableMarkdownToAdf` reads the title back.
|
|
- `losslessMarkdownToAdf` names the CommonMark spelling to write when it refuses a directive whose
|
|
node CommonMark spells, such as `[](href)` for a linked `!adf:mediaSingle`.
|
|
- Every reader and writer takes time linear in the text when it holds many `<!--`, `<?`,
|
|
`<![CDATA[` or `<!` openers without a terminator, or many unclosed backtick runs outside a
|
|
directive's `[content]`. Such a text took time quadratic in its length: 160000 characters of `<?`
|
|
took seven seconds each way, now 50 ms.
|
|
- Every reader takes time linear in the markdown when it holds blank lines under list items nested
|
|
deeply. Such a list is still refused as `unsupported-nesting-depth`, now after 40 ms where 20000
|
|
nested items and as many blank lines took 10.7 seconds.
|
|
- Every reader takes time linear in the markdown when tab-indented lines sit under list items
|
|
nested deeply. Such a list is still refused as `unsupported-nesting-depth`, now after 107 ms where
|
|
20 lines of 40000 tabs under 80000 nested items took 1988 ms.
|
|
- `losslessMarkdownToAdf` takes time linear in the markdown when an inline directive's `[content]`
|
|
holds many unclosed backtick runs: 640000 characters of them took one second, now 100 ms.
|
|
- Every reader takes time linear in the inline markdown when emphasis spans many links, or many
|
|
closing brackets follow a link: 8000 links inside 8000 `*` took two seconds, now about 100 ms.
|
|
- Every reader takes time linear in the inline markdown when it holds many `](` that open no link,
|
|
as in `[a](b` repeated: 16000 of them took 25 seconds, now 80 ms.
|
|
- `adfToLosslessMarkdown` and `adfToPortableMarkdown` write faster a line of many marked texts or
|
|
inline nodes that need a carry or lose their mark, as in text `un` beside strong `-r`, repeated:
|
|
1000 of them took 10 seconds, now 150 ms. They also write faster a line mixing em, strike and
|
|
strong: text `un` beside `-r` marked em and strong, repeated 1000 times, took up to 9 seconds,
|
|
now under 170 ms, and `(` marked em, strike and strong beside a mention, repeated 1000 times,
|
|
took 18 seconds in `adfToPortableMarkdown`, now 150 ms. A line where em shares a delimiter run
|
|
with strong takes time linear in its length: `~~` between spaces marked em and strong, `)` marked
|
|
em, strike and strong, `a`, then `.unun` marked strike and strong, repeated 1000 times, took 56
|
|
seconds in `adfToLosslessMarkdown` and 130 in `adfToPortableMarkdown`, now 2.6 and 1.5 seconds. A
|
|
line still read back wrong after 32 tries is written with every node holding em, strike or
|
|
strong as `!adf:carry{json="…"}` in `adfToLosslessMarkdown`, and without those marks in
|
|
`adfToPortableMarkdown`.
|
|
- `adfToLosslessMarkdown` and `adfToPortableMarkdown` take time linear in the markdown when lists
|
|
and quotes nest deeply, and so does `losslessMarkdownToAdf`: a list nested 480 levels around
|
|
64 KB of paragraphs took 2 seconds to write, now 100 ms. A `!adf:blockquote` holding 2 MB of lines
|
|
in a list nested 480 levels is still refused as `unsupported-node-shape`, now after 0.8 seconds
|
|
where it took 2.6.
|
|
- Every reader leaves `[text]`, `[text][]`, `[text][label]` and a definition `[label]: url` literal
|
|
when the label runs over 999 characters, where it read them as a link or a definition. CommonMark
|
|
caps a label at 999 characters, an escape counting as two.
|
|
- Every reader leaves a link or a definition literal when its unbracketed destination holds a `(`
|
|
that is neither escaped nor closed, as CommonMark requires: `[a](b(c "t")` is now text, where it
|
|
read as a link to `b(c`. Markdown the writers wrote reads as before, since they escape such a
|
|
parenthesis.
|
|
- `adfToLosslessMarkdown` and `adfToPortableMarkdown` escape every text `[` in a line where one
|
|
would open a link, and every text backtick where one would open a code span: the text
|
|
`[a](b) [c]` is now written `\[a](b) \[c]`, where it was `\[a](b) [c]`. A text `[` that opens
|
|
nothing stays bare: `[a] b](c` is now written as is, where it was `\[a] b](c`. Markdown written
|
|
by 0.2.0 reads the same in 0.3.0.
|
|
- The README compares the package with seven other ADF converters, each checked against its
|
|
published version.
|
|
|
|
## 0.2.0
|
|
|
|
- **Breaking:** each call names the markdown it reads or writes. `adfToMarkdown` and `markdownToAdf`
|
|
are now `adfToLosslessMarkdown` and `losslessMarkdownToAdf`. A bot's or a model's markdown reads
|
|
in `portableMarkdownToAdf`, and markdown known to be strict CommonMark in the new
|
|
`commonMarkToAdf`, where a directive, a pipe table, `~~` and an `adf:` fence read as text and
|
|
code. `MIGRATION.md` says which call to take.
|
|
- **Breaking:** the markdown readers refuse a value that is not a string with the new code
|
|
`not-a-string`; `MIGRATION.md` lists what each value did before.
|
|
- **Breaking:** every call returns the new code `engine-limit` where a conversion passes the
|
|
JavaScript engine's longest array, `Map` or `Set`, or meets an out-of-memory throw the call can
|
|
catch, and, on JavaScriptCore, its longest string. A retry may succeed. Every other refusal stays deterministic.
|
|
- **Breaking:** an attribute value that is not a plain object, array or JSON primitive, such as a
|
|
`Date`, `Map`, typed array or class instance, makes `isAdfDocument` return `false` and the
|
|
converters refuse it as `not-an-adf-document`. Before, the converters wrote it as `{}` or its own
|
|
keys. Build it as JSON holds it: `date.toISOString()`, `Object.fromEntries(map)`,
|
|
`Array.from(bytes)`, `{ ...instance }`.
|
|
- **Breaking:** directives, the inline opaque carry among them (now `!adf:carry{json="…"}`), are
|
|
spelled under an `!adf:` prefix (`!adf:name … !adf:/name`, `!adf:name[content]{attrs}`,
|
|
`!adf:name arg {attrs}`) in place of the `:::`/`::`/`:name` forms: text holding an unescaped
|
|
`!adf:` is claimed. Convert stored markdown per `MIGRATION.md`.
|
|
- **Breaking:** the block carry is a code fence whose info string `adf:<type>` names the node's
|
|
type, its body the node's JSON without `type`, and a code fence whose info string opens `adf:` is
|
|
claimed, and `adf` is an ordinary code block language. Convert stored markdown per `MIGRATION.md`.
|
|
- **Breaking:** the markdown readers read markdown holding no block as a document whose `content` is
|
|
empty, as Atlassian's schema requires; `!adf:doc {content=none}` spells a document holding no
|
|
`content` key.
|
|
- **Breaking:** `unspellable-character`, `unspellable-line-start` and `unspellable-whitespace`
|
|
leave `ConvertErrorCode`. A line ending in an `emoji`, `mention` or `status` directive's content
|
|
slot is refused as `unsupported-node-shape`. A carriage return in text is spelled ` ` and a
|
|
null character `!adf:text{text="\u0000"}`; a paragraph line opening with a code span is spelled
|
|
as that code span, its backticks opening no fence; a code block holding either character, a code
|
|
span holding a null character, a text node holding no text or holding `content`, and an `emoji`,
|
|
`mention` or `status` whose `text` holds a line ending or a null character ride the carry. See
|
|
`MIGRATION.md`.
|
|
- **Breaking:** `unspellable-link` leaves `ConvertErrorCode`; a link whose `href` or `title` no
|
|
CommonMark escape spells is written as `!adf:link[text]{attrs}`.
|
|
- **Breaking:** some directive refusals carry `malformed-directive` where they carried
|
|
`unsupported-node-shape`, and an empty node's leaf and closed spellings swap which one parses;
|
|
`MIGRATION.md` lists each.
|
|
- **Breaking:** a link whose text holds another link keeps the inner link and leaves the outer
|
|
brackets literal text, where `0.1.0` split the outer link around it; see `MIGRATION.md`.
|
|
- **Breaking:** `unmappable-image` leaves `ConvertErrorCode`, and every image reads. An image reads
|
|
as an image only alone in its paragraph, and its title becomes the caption. Anywhere else, after
|
|
a task marker too, it reads as its alt text linked to its URL; inside a link, as plain text the
|
|
link marks. A link alone in its paragraph whose whole text is one image reads as that image, with
|
|
the link marking it. To keep an image an image, give it a paragraph of its own. See
|
|
`MIGRATION.md`.
|
|
- **Breaking:** an empty link text reads as its destination, `[](/url)` as `/url` linked, and
|
|
`[]()` as nothing, where both spellings stayed literal text. See `MIGRATION.md`.
|
|
- **Breaking:** a hard break inside emphasis, strike, a link or a directive mark reads holding no
|
|
marks, as Atlassian's schema requires, where every reader gave it the marks around it. A link
|
|
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
|
|
`unsupported-node-shape`. `adfToLosslessMarkdown` writes a hard break holding marks as
|
|
`!adf:carry{json="…"}`. See `MIGRATION.md`.
|
|
- **Breaking:** a list whose bullet character or ordered delimiter changes reads as two lists, as
|
|
CommonMark reads it: `- a` then `* b`, `1. a` then `1) b`. `adfToLosslessMarkdown` gives a list
|
|
right after one of its kind the other marker, `* ` or `1)`, in place of the `::listBreak`
|
|
separator. Convert stored markdown per `MIGRATION.md`.
|
|
- **Breaking:** a link reference definition between two list items, after a blank line and at the
|
|
list's own indentation, ends the list, as CommonMark reads it; `0.1.0` read one list. See
|
|
`MIGRATION.md`.
|
|
- **Breaking:** `adfToLosslessMarkdown` writes a centered, uncaptioned external image whose one
|
|
mark is a link holding only `href` and `title` as `[](href "title")`, where it wrote the
|
|
`mediaSingle` directive. Convert stored markdown per `MIGRATION.md`.
|
|
- **Breaking:** `adfToLosslessMarkdown` writes `colwidth` as its widths joined by commas,
|
|
`colwidth="340,420"`, and a block's `marks` as each mark's type and attributes,
|
|
`marks="breakout{mode=wide}"`, where it wrote both as JSON arrays, and `losslessMarkdownToAdf`
|
|
refuses an array the new spelling holds. `marks` stays a JSON array where the flavour does not list
|
|
a mark. Convert stored markdown per `MIGRATION.md`.
|
|
- Fix the time `adfToLosslessMarkdown` and `adfToPortableMarkdown` take on text holding a long run of
|
|
`*`, `_`, `~` or backticks, or a heading ending in a long run of `#`: the time is linear in the
|
|
run, and a run of 20000 `*` takes milliseconds where it took seconds.
|
|
- Fix the round-trip, so `losslessMarkdownToAdf(adfToLosslessMarkdown(doc))` deep-equals `doc` as
|
|
`JSON.parse` builds it. Two adjacent text nodes CommonMark would read back as one are parted by
|
|
`!adf:textBreak{}`, an empty `attrs`, `content` or `marks` is spelled `{attrs=empty}`,
|
|
`{content=empty}` or `{marks=empty}`, `-0` is spelled `-0`, and a `codeBlock` of several text
|
|
nodes is a fence per node. A `codeBlock` holding other than plain text nodes rides the block
|
|
carry, where it was refused.
|
|
- Fix a link `title` holding a null character: it is written as `!adf:link[text]{attrs}`, where it
|
|
read back as U+FFFD.
|
|
- Fix an image inside another image's description: it flattens into the alt text, where it was
|
|
refused.
|
|
- Fix a JSON key holding an escape, in a carried node or a JSON attribute such as `parameters`:
|
|
`losslessMarkdownToAdf` reads it as the key it spells, where on V8 13.6 and later (Node 24, Deno,
|
|
Chrome) `markdownToAdf` could refuse markdown `adfToMarkdown` wrote, as `unsupported-node-shape`,
|
|
once the process had read a key holding a backslash
|
|
([V8 bug](https://issues.chromium.org/issues/521080746)).
|
|
- Fix a cyclic document, in which an object holds itself: `adfToLosslessMarkdown` and
|
|
`adfToPortableMarkdown` refuse it as `not-an-adf-document` and `isAdfDocument` returns `false`,
|
|
where all three hung.
|
|
- Fix a document whose markdown would be longer than 536870888 characters, V8's longest string:
|
|
`adfToLosslessMarkdown` and `adfToPortableMarkdown` refuse it as `unsupported-node-shape` on every
|
|
engine, where they threw or ran out of memory.
|
|
- Fix a directive that, written out as plain CommonMark, would be longer than 536870888 characters:
|
|
`losslessMarkdownToAdf` refuses it as `unsupported-node-shape`, where a few megabytes of nested
|
|
list lines ran the call out of memory.
|
|
- Fix the markdown readers past V8's or SpiderMonkey's longest string: they return
|
|
`unsupported-node-shape`, where they threw.
|
|
- Fix a paragraph of tens of millions of characters: `adfToLosslessMarkdown` and
|
|
`adfToPortableMarkdown` need a few bytes a character on top of the document and its markdown,
|
|
where they needed about 50. Before, 80 million characters ran Node's default heap out of memory
|
|
and 20 million `*` threw `RangeError`. `losslessMarkdownToAdf` no longer throws `RangeError` on
|
|
such a paragraph inside a directive.
|
|
- Fix deeply nested compact JSON in an `adf:` fence: `losslessMarkdownToAdf` refuses it as
|
|
`unsupported-node-shape`, as it refuses any compact JSON there, where about a megabyte of it threw.
|
|
- Add `adfToPortableMarkdown` and `portableMarkdownToAdf`, a lossy pair converting ADF to and from
|
|
markdown GitHub, GitLab and Obsidian render: alerts, callouts, task lists, `==highlights==` and
|
|
pipe tables. A task item keeps its paragraphs, then its nested task lists; the rest of the item,
|
|
such as an image or a code block, stands after the task list. `portableMarkdownToAdf` reads GFM's
|
|
pipe table with or without leading pipes, and with alignment colons, dropping the alignment.
|
|
- Spell `rule`'s `color`, `style` and `weight`, `layoutSection`'s `columnRuleStyle` and a link's
|
|
`collection`, `id` and `occurrenceKey` directly where they rode the opaque carry.
|
|
- The README opens with what the package does and for whom, and `package.json` carries search
|
|
keywords.
|
|
- The source moves to [github.com/larvit/adf-codec](https://github.com/larvit/adf-codec), where
|
|
issues and pull requests go.
|
|
|
|
## 0.1.0
|
|
|
|
- First release: lossless conversion between ADF and an extended markdown flavour —
|
|
`adfToMarkdown`, `markdownToAdf` and `isAdfDocument`. Nothing throws, and every error carries a `code` from a
|
|
closed list.
|