diff --git a/CHANGELOG.md b/CHANGELOG.md index 4acd89a..9053afa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,32 @@ ## Unreleased -- `docs/feature-table.md` sets real documents beside what `adfToLosslessMarkdown`, +- **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. -- **Breaking:** `adfToHtml`, `adfToPortableMarkdown`, `htmlToAdf` and `losslessMarkdownToAdf` refuse - a block or non-text inline node holding `text`, and a leaf block or non-text inline node holding - `content`, as `adfToLosslessMarkdown` already did outside a carried node, which it now also - refuses. `MIGRATION.md` says how to convert a stored 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 @@ -25,31 +44,16 @@ 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. -- **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. `adfToHtml` and `htmlToAdf` do - the same. `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. + 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 diff --git a/README.md b/README.md index fd17b30..7cfdb4e 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,7 @@ documentation says, on 2026-10-28. Serves [Goals](#goals) 1, 2 and 8. Pure functions, each taking a whole document and returning a whole result; no I/O, no configuration. [What each call writes](https://github.com/larvit/adf-codec/blob/main/docs/feature-table.md) -sets real documents beside each writer's output and says whether that output reads back the same. +sets example documents beside each writer's output and says whether that output reads back the same. ```ts adfToLosslessMarkdown(doc: AdfDocument): Result @@ -528,7 +528,7 @@ content. No stylesheet ships. The complete dialect: | `table`, `tableRow`, `tableCell`, `tableHeader` | ``, `` for a first row of `tableHeader` cells alone, ``, ``, `` and the rest in ``, a row to a line, over a bare `
` and ``; a cell's first paragraph stands as an item's does, and `` writes its attributes | | `text` marked `code`, `em`, `strong` | ``, ``, ``, the first mark outermost | | an attribute its type holds that its element does not write, such as `localId`, on a paragraph, heading, quote, list, list item, table, row, cell, code block, rule, emoji, mention, status, date or card | `data-*` named as HTML's `dataset` names it: `

` | -| any other node, or a node holding what its element cannot, except an inline card a link marks, which is refused ([The errors](#the-errors)) | `

its content

`, a `` inline | +| any other node, or a node holding what its element cannot, except a shape Atlassian's schema forbids, which is refused ([The errors](#the-errors)) | `

its content

`, a `` inline | - Every URL is written as the ADF holds it, `javascript:` included: a link's, an image's, a card's, and a carried link's in the carry element. Sanitize the HTML before showing a document from @@ -649,11 +649,12 @@ Every call can raise these: - **Writing, in `adfToLosslessMarkdown`**: a node not riding the carry that carries one mark type twice. Keep the ADF and show the document read-only; where you built the ADF, fix what the message names. -- **Writing, in `adfToHtml`, `adfToLosslessMarkdown` and `adfToPortableMarkdown`**: what Atlassian's - schema gives no node, even inside a node that rides the carry: an inline card marked by a link, a - block or a non-text inline node holding `text`, or a leaf block or a non-text inline node holding - `content`. Keep the ADF and pass the document over or show it read-only; where you built the ADF, - remove what the error's `path` and message name. +- **Writing, in `adfToHtml`, `adfToLosslessMarkdown` and `adfToPortableMarkdown`**: a shape + Atlassian's schema forbids, even inside a node that rides the carry: an inline card a link marks, + a known block holding `text`, a known non-text inline node holding `text` or non-empty `content`, + or a known block that takes no content, such as `rule`, holding non-empty `content`. Keep the ADF + and pass the document over or show it read-only; where you built the ADF, remove what the error's + `path` and message name. - **Writing, in `adfToPortableMarkdown`**: a line or task list the portable writer cannot spell. This is a bug in this library: report it. - **Past the engine's longest string**: diff --git a/docs/decisions.md b/docs/decisions.md index 9b0f6b3..0daef26 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -463,8 +463,7 @@ and `` 2. The maintainer took the leading spelling of each, - a mention is `@Mikael`, its `text` the content; - a card holding `data` and no URL is `Release plan`, `href` the URL its `data` holds and the text its name, else that URL. A card holding both `data` and a `url` rides the carry: its `href`, read back, could not say which of the two URLs it spells. -A sixth panel (2026-11-05, three readers in the same roles) picked each of these 3 of 3 for a table, -which until then rode the carry: +A sixth panel (2026-11-05, three readers in the same roles) picked each of these 3 of 3 for a table: - a first row of header cells alone in `
` of `` rows and over a line per cell; @@ -472,9 +471,6 @@ which until then rode the carry: - a cell's attributes as `
`, over `data-*` for all four. -A `background` holding anything but letters and digits after an optional `#` rides the carry, so no -value can end the `style` declaration. - ## `htmlToAdf` also reads the dialect as a browser serializes it 2026-10-28, the maintainer. Goal 6. Valid while a viewer/editor app may show `adfToHtml`'s HTML in a @@ -773,8 +769,9 @@ writer refuses alike, with one code, message and path, wherever the shape stands too, and a carry reader refuses it with the same code and the writers' message after its holder's name. The first step refuses an `inlineCard` with a `link` mark as `unsupported-node-shape`: the schema gives a card the `annotation` mark alone (§The node tables answer to Atlassian's schema), -and HTML nests no `` in an ``. It also refuses a block or non-text inline node holding `text`, -and a leaf block or non-text inline node holding a non-empty `content`. +and HTML nests no `` in an ``. The first step also refuses 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 holding non-empty `content`. Nothing else joins the first step. Nothing refuses a node type, mark or attribute the schema does not name (§Unknown nodes ride the carry), a known node where no section spells it, or a mark a node diff --git a/docs/feature-table.md b/docs/feature-table.md index cfe8aa0..89f3f6a 100644 --- a/docs/feature-table.md +++ b/docs/feature-table.md @@ -1,8 +1,10 @@ # What each call writes -`feature-table/build.mjs` writes this page from the corpus and the built package, and `./ci.sh` fails where the page differs. +Each section sets a sample ADF document beside what each call writes for it, then reads that output back: `adfToLosslessMarkdown` with `losslessMarkdownToAdf`, `adfToPortableMarkdown` with `portableMarkdownToAdf`, `adfToHtml` with `htmlToAdf`. `holds` means the reader gives the same document, `loses` a different one, and `refused` that the writer or reader returned an error, whose code the section names. -Each output is read back by its format's reader. `holds` means that gives the same document, `loses` a different one, and `refused` an error whose code the example names. +A result covers that example alone, and the examples are plain. Portable markdown drops every attribute, so a document holding one loses it even where its row says `holds`: to edit and save back, use `adfToLosslessMarkdown` and `losslessMarkdownToAdf`. An image holding an attribute, as editor-written ones do, rides the carry element in HTML, where this page shows ``. + +`./feature-table/write.sh` writes this page. Don't edit it by hand. | Example | Lossless | Portable | HTML | | --- | --- | --- | --- | diff --git a/feature-table/render.mjs b/feature-table/render.mjs index 7c762cb..4d90f12 100644 --- a/feature-table/render.mjs +++ b/feature-table/render.mjs @@ -8,16 +8,18 @@ const writers = [ const intro = `# What each call writes -\`feature-table/build.mjs\` writes this page from the corpus and the built package, and \`./ci.sh\` fails where the page differs. +Each section sets a sample ADF document beside what each call writes for it, then reads that output back: \`adfToLosslessMarkdown\` with \`losslessMarkdownToAdf\`, \`adfToPortableMarkdown\` with \`portableMarkdownToAdf\`, \`adfToHtml\` with \`htmlToAdf\`. \`holds\` means the reader gives the same document, \`loses\` a different one, and \`refused\` that the writer or reader returned an error, whose code the section names. -Each output is read back by its format's reader. \`holds\` means that gives the same document, \`loses\` a different one, and \`refused\` an error whose code the example names. +A result covers that example alone, and the examples are plain. Portable markdown drops every attribute, so a document holding one loses it even where its row says \`holds\`: to edit and save back, use \`adfToLosslessMarkdown\` and \`losslessMarkdownToAdf\`. An image holding an attribute, as editor-written ones do, rides the carry element in HTML, where this page shows \`\`. + +\`./feature-table/write.sh\` writes this page. Don't edit it by hand. ` -// `library` holds the package's calls, so a test can hand in what a real document cannot make them do. +// `library` is a parameter, so a test can hand in calls that refuse, which no corpus example makes them do. export function renderFeatureTable(examples, library) { const written = examples.map(({ doc, name }) => ({ doc, name, outcomes: writers.map((writer) => outcome(doc, writer, library)) })) const rows = written.map(({ name, outcomes }) => `| [${name}](#${name.toLowerCase().replaceAll(' ', '-')}) | ${outcomes.map(({ result }) => result).join(' | ')} |`) - const head = `| Example | ${writers.map(({ column }) => column).join(' | ')} |\n| --- | --- | --- | --- |` + const head = `| Example | ${writers.map(({ column }) => column).join(' | ')} |\n| --- | ${writers.map(() => '---').join(' | ')} |` return [intro, [head, ...rows].join('\n') + '\n', ...written.map(section)].join('\n') }