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

13 KiB

Changelog

Unreleased

  • 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: (image) follows the link, linked 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.
  • 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.