diff --git a/README.md b/README.md index e1a4224..5bdcba8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ shows, edits, exports or generates the rich text Atlassian products store as ADF | Markdown from a bot, a model or a person | ADF | `portableMarkdownToAdf` | reads CommonMark plus GFM's tables, task lists and alerts, `~~`, and Obsidian's callouts and `==`; `commonMarkToAdf` reads strict CommonMark alone | | ADF | markdown to edit, then ADF again | `adfToLosslessMarkdown`, then `losslessMarkdownToAdf` | gives back the document it started from, panels, mentions, statuses and node types this version does not know included | | ADF | markdown that GitHub, GitLab and Obsidian render | `adfToPortableMarkdown` | keeps the text, images by URL and link targets and drops the rest | -| ADF | HTML | `adfToHtml` | writes the elements a browser shows; `htmlToAdf` reads that HTML back to the same document, and refuses any other HTML | +| ADF | HTML | `adfToHtml` | writes paragraphs, headings, lists, quotes, code blocks, links and marks as HTML elements, and every other node as an element holding its JSON; `htmlToAdf` reads that HTML back to the same document, and refuses any other HTML | Every call returns a result, never a throw. A refusal carries a code from a closed list, and every refusal but `engine-limit` repeats on the same input ([The guarantees](#the-guarantees)). @@ -244,8 +244,9 @@ place, and on CI failing a change that breaks a guarantee. ## Other ADF converters -Serves [Goals](#goals) 3. The first table lists every npm package that converts ADF to or from -markdown, whatever its downloads, unless its own documentation calls it experimental or incomplete. +Serves [Goals](#goals) 3. The first table lists every npm package that runs in Node and converts +ADF to or from markdown, whatever its downloads; a package that fails ordinary nodes stays, with how it fails. A +wrapper or fork of a listed package is left out. On 2026-10-28, [`converter-comparison/`](https://github.com/larvit/adf-codec/tree/main/converter-comparison) ran each package's published code, at the version the table names, with its default options. A package writing markdown got a document holding marks, a link, a mention, a status, a panel and a @@ -256,8 +257,11 @@ holding the same marks and link. | --- | --- | --- | --- | --- | --- | | `@larvit/adf-codec` | both ways | the same document | kept | markdown: lossless, portable; CommonMark in; HTML out; its own HTML in | 0 | | `@atlaskit/editor-markdown-transformer` 6.3.1 | markdown to ADF, through `@atlaskit/editor-json-transformer` 10.0.0; ADF to markdown throws "This is not implemented yet" | – | – | markdown | 5 | +| `@complexthings/md-to-adf` 1.0.0 | markdown to ADF; writes each list item as a list of its own | – | – | markdown | 1 | | `@faulander/md2adf` 1.0.0 | both ways | a different document: the status is gone, the mention's `id` becomes its text and the panel a quote | dropped with its text | markdown | 2 | | `@nthings.dev/adf4j-wasm` 1.0.2 | ADF to markdown | – | replaced by the text `[Unsupported: futureThing]` | GitHub Flavored Markdown, from WebAssembly | 0 | +| `@wrtnlabs/markdown-to-adf` 1.0.2 | markdown to a list of blocks, not a document; `***bold italic***` comes out as italic text holding asterisks | – | – | markdown | 2 | +| `adf-converter` 0.1.0 | ADF to markdown | – | its text kept, with a console message | markdown | 0 | | `adf-md` 0.2.0 | both ways | a different document: the status without its `localId`, the marks in the other order | dropped with its text | markdown with MDX tags for panels, mentions and statuses | 6 | | `adf-to-markdown` 1.0.1 | ADF to markdown | – | dropped with its text | markdown | 0 | | `adf-to-md` 1.2.1 | ADF to markdown | – | dropped with its text, as are mentions, statuses and panels | markdown | 1 | @@ -266,14 +270,17 @@ holding the same marks and link. | `github-markdown-adf` 1.3.0 | both ways | a different document: the mention comes back as text, the status as inline code, the marks in the other order | dropped with its text | markdown | 3 | | `markdown-to-adf` 0.2.20 | markdown to ADF; writes a quote as the node type `blockQuote`, which ADF spells `blockquote` | – | – | markdown | 3 | | `marklassian` 1.2.1 | markdown to ADF | – | – | markdown | 1 | +| `md-adf` 0.2.2 | both ways | a different document: the mention and the status come back as text, the marks in the other order, the panel as a quote | dropped with its text | markdown | 5 | | `md-to-adf` 0.6.4 | markdown to ADF | – | – | markdown | 2 | -| `mdast-util-from-adf` 2.3.0 | ADF to an mdast tree, which `mdast-util-to-markdown` writes as markdown; throws on a status | – | throws | markdown | 3 | +| `mdast-util-from-adf` 2.3.0 | ADF to an mdast tree, which `mdast-util-to-markdown` 2.2.1 writes as markdown; throws on a status | – | throws | markdown | 3 | -The next table lists converters in other ecosystems and Atlassian's own paths. They are not run -here; each row is what the project's documentation says, on 2026-10-28. +The next table lists converters this comparison does not run: other ecosystems' libraries, +Atlassian's own paths, and one npm package that needs a browser. Each row is what the project's +documentation says, on 2026-10-28. | Converter | Converts | Source | | --- | --- | --- | +| `adf2md` 2.0.0, JavaScript for browsers | ADF to and from GitHub Flavored Markdown with MDX-style tags for Atlassian nodes; ships a browser bundle that needs a page, not Node | [npm](https://www.npmjs.com/package/adf2md) | | `marklas` 0.8.6, Python | GitHub Flavored Markdown to and from ADF | [PyPI](https://pypi.org/project/marklas/) | | `pyadf` 0.5.2, Python | ADF to markdown; a node it cannot render becomes an HTML element holding its attributes | [README](https://github.com/YoungseokCh/pyadf) | | `jc-adf` 0.2.0, Rust | markdown to and from ADF; a node type it does not know goes through an `adf:` fenced block | [crates.io](https://crates.io/crates/jc-adf) | diff --git a/converter-comparison/compare.mjs b/converter-comparison/compare.mjs index 78c29e0..4d255ad 100644 --- a/converter-comparison/compare.mjs +++ b/converter-comparison/compare.mjs @@ -1,22 +1,26 @@ import * as adf2markdown from "adf2markdown"; import * as adfCodec from "@larvit/adf-codec"; +import * as adfConverter from "adf-converter"; import * as adfMd from "adf-md"; import * as adfToMarkdown from "adf-to-markdown"; import adfToMd from "adf-to-md"; -import { loadAdf4j } from "@nthings.dev/adf4j-wasm"; import * as atlaskitJson from "@atlaskit/editor-json-transformer"; import * as atlaskitMarkdown from "@atlaskit/editor-markdown-transformer"; import { createRequire } from "node:module"; import * as extendedParser from "extended-markdown-adf-parser"; import * as faulander from "@faulander/md2adf"; +import { fromADF } from "mdast-util-from-adf"; import * as githubMarkdownAdf from "github-markdown-adf"; import { isDeepStrictEqual } from "node:util"; -import * as marklassian from "marklassian"; +import { loadAdf4j } from "@nthings.dev/adf4j-wasm"; import * as markdownToAdf from "markdown-to-adf"; -import { fromADF } from "mdast-util-from-adf"; +import { markdownToJiraBlock } from "@wrtnlabs/markdown-to-adf"; +import * as marklassian from "marklassian"; +import * as mdAdf from "md-adf"; import mdToAdf from "md-to-adf"; -import { readFileSync } from "node:fs"; import { toMarkdown as mdastToMarkdown } from "mdast-util-to-markdown"; +import { readFileSync } from "node:fs"; +import translateGithubMarkdown from "@complexthings/md-to-adf"; const { defaultSchema } = createRequire(import.meta.url)("@atlaskit/adf-schema/schema-default"); @@ -60,15 +64,18 @@ const converters = { toAdf: (md) => json.encode(atlaskit.parse(md)), toMarkdown: (doc) => atlaskit.encode(json.parse(doc)), }, - "@larvit/adf-codec": { - toAdf: (md) => adfCodec.losslessMarkdownToAdf(md).value, - toMarkdown: (doc) => adfCodec.adfToLosslessMarkdown(doc).value, - }, + "@complexthings/md-to-adf": { toAdf: (md) => translateGithubMarkdown(md) }, "@faulander/md2adf": { toAdf: (md) => faulander.markdownToAdf(md), toMarkdown: (doc) => faulander.adfToMarkdown(doc), }, + "@larvit/adf-codec": { + toAdf: (md) => adfCodec.losslessMarkdownToAdf(md).value, + toMarkdown: (doc) => adfCodec.adfToLosslessMarkdown(doc).value, + }, "@nthings.dev/adf4j-wasm": { toMarkdown: (doc) => adf4j.convert(JSON.stringify(doc)) }, + "@wrtnlabs/markdown-to-adf": { toAdf: (md) => markdownToJiraBlock(md) }, + "adf-converter": { toMarkdown: (doc) => adfConverter.processADF(doc, adfConverter.markdownFormatter) }, "adf-md": { toAdf: (md) => succeeded(adfMd.markdownToAdf(md)), toMarkdown: (doc) => succeeded(adfMd.adfToMarkdown(doc)), @@ -76,18 +83,22 @@ const converters = { "adf-to-markdown": { toMarkdown: (doc) => adfToMarkdown.convertADFToMarkdown(doc) }, "adf-to-md": { toMarkdown: (doc) => adfToMd.convert(doc).result }, adf2markdown: { toMarkdown: (doc) => adf2markdown.adf2markdown(doc) }, + "extended-markdown-adf-parser": { + toAdf: (md) => extended.markdownToAdf(md), + toMarkdown: (doc) => extended.adfToMarkdown(doc), + }, "github-markdown-adf": { toAdf: (md) => githubMarkdownAdf.mdToAdf(md), toMarkdown: (doc) => githubMarkdownAdf.adfToMd(doc), }, "markdown-to-adf": { toAdf: (md) => markdownToAdf.markdownToAdf(md) }, - "mdast-util-from-adf": { toMarkdown: (doc) => mdastToMarkdown(fromADF(doc)) }, - "extended-markdown-adf-parser": { - toAdf: (md) => extended.markdownToAdf(md), - toMarkdown: (doc) => extended.adfToMarkdown(doc), - }, marklassian: { toAdf: (md) => marklassian.markdownToAdf(md) }, + "md-adf": { + toAdf: (md) => mdAdf.markdownToAdf(md).value, + toMarkdown: (doc) => mdAdf.adfToMarkdown(doc).value, + }, "md-to-adf": { toAdf: (md) => mdToAdf(md) }, + "mdast-util-from-adf": { toMarkdown: (doc) => mdastToMarkdown(fromADF(doc)) }, }; const show = (label, call) => { diff --git a/converter-comparison/run.sh b/converter-comparison/run.sh index 2c0ed8b..d4c1fd9 100755 --- a/converter-comparison/run.sh +++ b/converter-comparison/run.sh @@ -8,9 +8,12 @@ docker run --rm --memory 2g --memory-swap 2g --ulimit core=0 -u "$(id -u):$(id - npm install --before=2026-10-29 --ignore-scripts --no-audit --no-fund --silent \ @atlaskit/editor-json-transformer@10.0.0 \ @atlaskit/editor-markdown-transformer@6.3.1 \ + @complexthings/md-to-adf@1.0.0 \ @faulander/md2adf@1.0.0 \ @larvit/adf-codec@0.2.0 \ @nthings.dev/adf4j-wasm@1.0.2 \ + @wrtnlabs/markdown-to-adf@1.0.2 \ + adf-converter@0.1.0 \ adf-md@0.2.0 \ adf-to-markdown@1.0.1 \ adf-to-md@1.2.1 \ @@ -19,6 +22,7 @@ docker run --rm --memory 2g --memory-swap 2g --ulimit core=0 -u "$(id -u):$(id - github-markdown-adf@1.3.0 \ markdown-to-adf@0.2.20 \ marklassian@1.2.1 \ + md-adf@0.2.2 \ md-to-adf@0.6.4 \ mdast-util-from-adf@2.3.0 \ mdast-util-to-markdown@2.2.1 &&