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

15 KiB

Changelog

Unreleased

  • 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. Its comparison table lists every npm package running in Node that converts ADF to or from markdown, and other ecosystems' libraries and Atlassian's own paths.
  • adfToHtml writes ADF as HTML: paragraphs, headings, lists, quotes, 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. A paragraph, heading, quote, list, list item, code block or rule 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.
  • htmlToAdf reads adfToHtml's HTML back to the document it came from, a carry element from its data-adf. It refuses any other HTML, including an edit to what a carry element shows, with the codes README §The calls lists.
  • 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="…"}. MIGRATION.md says how to convert stored markdown.
  • 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.
  • 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 ![the moon](moon.png "Full") 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 [![alt](url)](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.
  • 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 &#13; 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 [![alt](url)](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).
  • 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, 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.