From 302804cc4ec1c7f16972ffb176e3d8209e679624 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 16 Sep 2026 09:52:52 +0200 Subject: [PATCH 1/3] 12b: the inline directive forms read and spell the !adf: prefix --- corpus/errors/carry-inside-mark.md | 2 +- corpus/errors/carry-invalid-json-inline.md | 2 +- corpus/errors/carry-nesting-depth.md | 2 +- ...d.error => directive-carry-reserved.error} | 0 ...eserved.md => directive-carry-reserved.md} | 2 +- corpus/errors/directive-content-slot.md | 2 +- corpus/errors/directive-inline-unclosed.md | 2 +- corpus/errors/directive-mark-markdown.md | 2 +- corpus/errors/directive-prefix-alone.error | 1 + corpus/errors/directive-prefix-alone.md | 1 + corpus/errors/directive-slot-attributes.error | 1 + corpus/errors/directive-slot-attributes.md | 1 + corpus/errors/directive-text-plain.md | 2 +- corpus/errors/nesting-depth-composed.md | 2 +- corpus/round-trip/block-nodes/table-pipe.md | 4 +- .../combinations/attribute-escapes.md | 6 +- .../combinations/carry-attributes.md | 2 +- corpus/round-trip/combinations/carry-marks.md | 12 +- .../combinations/carry-text-attributes.md | 6 +- .../combinations/carve-out-directive.json | 13 +- .../combinations/carve-out-directive.md | 8 +- .../combinations/code-span-joined-run.md | 2 +- .../round-trip/combinations/combined-page.md | 4 +- .../directive-autolink-backtick.md | 4 +- .../combinations/directive-link-backticks.md | 6 +- .../combinations/directive-link-brackets.md | 8 +- .../combinations/directive-link-colon.md | 9 - ...-colon.json => directive-link-prefix.json} | 32 ++- .../combinations/directive-link-prefix.md | 11 ++ .../combinations/link-definition-carry.md | 4 +- .../combinations/link-definition-line.md | 4 +- .../combinations/mark-inside-word.md | 2 +- .../round-trip/combinations/mark-run-carry.md | 2 +- .../combinations/mark-run-matching.md | 6 +- corpus/round-trip/combinations/mark-run.md | 4 +- .../combinations/pipe-cell-whitespace.md | 4 +- .../commonmark-subset/hard-break.md | 4 +- .../round-trip/inline-nodes/content-slot.md | 6 +- corpus/round-trip/inline-nodes/date.md | 2 +- corpus/round-trip/inline-nodes/emoji.md | 2 +- .../inline-nodes/hard-break-attributes.md | 4 +- corpus/round-trip/inline-nodes/inline-card.md | 4 +- .../inline-nodes/mark-content-slot.md | 2 +- .../round-trip/inline-nodes/mark-nesting.md | 4 +- corpus/round-trip/inline-nodes/marks.md | 4 +- .../round-trip/inline-nodes/media-inline.md | 4 +- corpus/round-trip/inline-nodes/mention.md | 2 +- corpus/round-trip/inline-nodes/status.md | 2 +- .../inline-nodes/text-whitespace.md | 12 +- .../opaque-carry/unknown-inline.json | 2 +- .../round-trip/opaque-carry/unknown-inline.md | 8 +- spec/flavour.md | 8 +- src/markdown-property.test.ts | 28 +-- src/markdown/code-language.ts | 4 +- src/markdown/directive-syntax.test.ts | 58 +++--- src/markdown/directive-syntax.ts | 35 ++-- src/markdown/emit/adf-to-markdown.test.ts | 114 +++++------ src/markdown/emit/inline-line.ts | 8 +- src/markdown/emit/line-escaping.ts | 6 +- src/markdown/opaque-carry.ts | 9 +- src/markdown/parse/directive-marks.ts | 3 +- src/markdown/parse/directive-nodes.ts | 12 +- src/markdown/parse/inline-content.ts | 14 +- src/markdown/parse/markdown-to-adf.test.ts | 183 ++++++++++-------- src/markdown/parse/markdown-to-adf.ts | 4 +- src/property-harness.ts | 3 +- 66 files changed, 397 insertions(+), 329 deletions(-) rename corpus/errors/{directive-adf-reserved.error => directive-carry-reserved.error} (100%) rename corpus/errors/{directive-adf-reserved.md => directive-carry-reserved.md} (68%) create mode 100644 corpus/errors/directive-prefix-alone.error create mode 100644 corpus/errors/directive-prefix-alone.md create mode 100644 corpus/errors/directive-slot-attributes.error create mode 100644 corpus/errors/directive-slot-attributes.md delete mode 100644 corpus/round-trip/combinations/directive-link-colon.md rename corpus/round-trip/combinations/{directive-link-colon.json => directive-link-prefix.json} (73%) create mode 100644 corpus/round-trip/combinations/directive-link-prefix.md diff --git a/corpus/errors/carry-inside-mark.md b/corpus/errors/carry-inside-mark.md index 10bec4d..7da4509 100644 --- a/corpus/errors/carry-inside-mark.md +++ b/corpus/errors/carry-inside-mark.md @@ -1 +1 @@ -_a :adf{json="{\"type\":\"blockCard\"}"} b_ +_a !adf:carry{json="{\"type\":\"blockCard\"}"} b_ diff --git a/corpus/errors/carry-invalid-json-inline.md b/corpus/errors/carry-invalid-json-inline.md index db12419..5dacb6b 100644 --- a/corpus/errors/carry-invalid-json-inline.md +++ b/corpus/errors/carry-invalid-json-inline.md @@ -1 +1 @@ -:adf{json="{"} +!adf:carry{json="{"} diff --git a/corpus/errors/carry-nesting-depth.md b/corpus/errors/carry-nesting-depth.md index ec39271..5f5183e 100644 --- a/corpus/errors/carry-nesting-depth.md +++ b/corpus/errors/carry-nesting-depth.md @@ -1 +1 @@ -:adf{json="[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"} +!adf:carry{json="[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"} diff --git a/corpus/errors/directive-adf-reserved.error b/corpus/errors/directive-carry-reserved.error similarity index 100% rename from corpus/errors/directive-adf-reserved.error rename to corpus/errors/directive-carry-reserved.error diff --git a/corpus/errors/directive-adf-reserved.md b/corpus/errors/directive-carry-reserved.md similarity index 68% rename from corpus/errors/directive-adf-reserved.md rename to corpus/errors/directive-carry-reserved.md index 867275e..2ab969b 100644 --- a/corpus/errors/directive-adf-reserved.md +++ b/corpus/errors/directive-carry-reserved.md @@ -1,3 +1,3 @@ -:::adf +:::carry {"type":"rule"} ::: diff --git a/corpus/errors/directive-content-slot.md b/corpus/errors/directive-content-slot.md index 75bff4f..9b3be0a 100644 --- a/corpus/errors/directive-content-slot.md +++ b/corpus/errors/directive-content-slot.md @@ -1 +1 @@ -Part :mention[**@Mikael**]{id=b1c2}. +Part !adf:mention[**@Mikael**]{id=b1c2}. diff --git a/corpus/errors/directive-inline-unclosed.md b/corpus/errors/directive-inline-unclosed.md index e035c81..29d756b 100644 --- a/corpus/errors/directive-inline-unclosed.md +++ b/corpus/errors/directive-inline-unclosed.md @@ -1 +1 @@ -Part :mention[@A and more. +Part !adf:mention[@A and more. diff --git a/corpus/errors/directive-mark-markdown.md b/corpus/errors/directive-mark-markdown.md index 20221a3..7894675 100644 --- a/corpus/errors/directive-mark-markdown.md +++ b/corpus/errors/directive-mark-markdown.md @@ -1 +1 @@ -Part :em[emphasised] here. +Part !adf:em[emphasised] here. diff --git a/corpus/errors/directive-prefix-alone.error b/corpus/errors/directive-prefix-alone.error new file mode 100644 index 0000000..4f35411 --- /dev/null +++ b/corpus/errors/directive-prefix-alone.error @@ -0,0 +1 @@ +malformed-directive diff --git a/corpus/errors/directive-prefix-alone.md b/corpus/errors/directive-prefix-alone.md new file mode 100644 index 0000000..6079223 --- /dev/null +++ b/corpus/errors/directive-prefix-alone.md @@ -0,0 +1 @@ +Part !adf: here. diff --git a/corpus/errors/directive-slot-attributes.error b/corpus/errors/directive-slot-attributes.error new file mode 100644 index 0000000..a811539 --- /dev/null +++ b/corpus/errors/directive-slot-attributes.error @@ -0,0 +1 @@ +unsupported-node-shape diff --git a/corpus/errors/directive-slot-attributes.md b/corpus/errors/directive-slot-attributes.md new file mode 100644 index 0000000..3753dc7 --- /dev/null +++ b/corpus/errors/directive-slot-attributes.md @@ -0,0 +1 @@ +!adf:status[!adf:carry{json="{\"attrs\":{\"localId\":\"x\"},\"text\":\"ok\",\"type\":\"text\"}"}]{color=red} diff --git a/corpus/errors/directive-text-plain.md b/corpus/errors/directive-text-plain.md index b52b563..4de8110 100644 --- a/corpus/errors/directive-text-plain.md +++ b/corpus/errors/directive-text-plain.md @@ -1 +1 @@ -Part :text{text=plain} here. +Part !adf:text{text=plain} here. diff --git a/corpus/errors/nesting-depth-composed.md b/corpus/errors/nesting-depth-composed.md index dd0d6be..ccb645f 100644 --- a/corpus/errors/nesting-depth-composed.md +++ b/corpus/errors/nesting-depth-composed.md @@ -1 +1 @@ -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - :a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[:a[x]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - !adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[!adf:a[x]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] diff --git a/corpus/round-trip/block-nodes/table-pipe.md b/corpus/round-trip/block-nodes/table-pipe.md index 3935489..87217e6 100644 --- a/corpus/round-trip/block-nodes/table-pipe.md +++ b/corpus/round-trip/block-nodes/table-pipe.md @@ -2,5 +2,5 @@ | --- | --- | | Bolt M8 | Grade `8.8` | | Nut \| washer | Sold as a pair | -| Washer M8 | 100 pcs:hardBreak{}zinc-plated | -| :text{text=" "}Spare | | +| Washer M8 | 100 pcs!adf:hardBreak{}zinc-plated | +| !adf:text{text=" "}Spare | | diff --git a/corpus/round-trip/combinations/attribute-escapes.md b/corpus/round-trip/combinations/attribute-escapes.md index 534efe2..afc54de 100644 --- a/corpus/round-trip/combinations/attribute-escapes.md +++ b/corpus/round-trip/combinations/attribute-escapes.md @@ -1,7 +1,7 @@ -a `b :inlineCard{url="c\u0060d"} +a `b !adf:inlineCard{url="c\u0060d"} -:inlineCard{url="https://example.com/s?q=a\u0026amp;b\u003cc"} +!adf:inlineCard{url="https://example.com/s?q=a\u0026amp;b\u003cc"} ::extension {extensionKey=jira parameters="{\"jql\":\"a\u007cb \u0060c\u0060 \u0026 d \u003c e\"}"} -[a:inlineCard{url="b](c)"} +[a!adf:inlineCard{url="b](c)"} diff --git a/corpus/round-trip/combinations/carry-attributes.md b/corpus/round-trip/combinations/carry-attributes.md index 0f5a99b..981a7ab 100644 --- a/corpus/round-trip/combinations/carry-attributes.md +++ b/corpus/round-trip/combinations/carry-attributes.md @@ -38,4 +38,4 @@ } ``` -See :adf{json="{\"attrs\":{\"localId\":5,\"text\":\"In review\"},\"type\":\"status\"}"} now. +See !adf:carry{json="{\"attrs\":{\"localId\":5,\"text\":\"In review\"},\"type\":\"status\"}"} now. diff --git a/corpus/round-trip/combinations/carry-marks.md b/corpus/round-trip/combinations/carry-marks.md index bcd5077..3ce9c8c 100644 --- a/corpus/round-trip/combinations/carry-marks.md +++ b/corpus/round-trip/combinations/carry-marks.md @@ -1,11 +1,11 @@ -Reviewed :adf{json="{\"marks\":[{\"attrs\":{\"annotationType\":\"inlineComment\",\"id\":\"0198f3a2-7c41-7f2e-9b3a-4d8e2c1a6b90\"},\"type\":\"annotation\"}],\"text\":\"audited\",\"type\":\"text\"}"} twice. +Reviewed !adf:carry{json="{\"marks\":[{\"attrs\":{\"annotationType\":\"inlineComment\",\"id\":\"0198f3a2-7c41-7f2e-9b3a-4d8e2c1a6b90\"},\"type\":\"annotation\"}],\"text\":\"audited\",\"type\":\"text\"}"} twice. -:adf{json="{\"marks\":[{\"attrs\":{\"colour\":\"red\"},\"type\":\"em\"}],\"text\":\"urgent\",\"type\":\"text\"}"} +!adf:carry{json="{\"marks\":[{\"attrs\":{\"colour\":\"red\"},\"type\":\"em\"}],\"text\":\"urgent\",\"type\":\"text\"}"} -:adf{json="{\"marks\":[{\"type\":\"code\"},{\"type\":\"strong\"}],\"text\":\"flag\",\"type\":\"text\"}"} +!adf:carry{json="{\"marks\":[{\"type\":\"code\"},{\"type\":\"strong\"}],\"text\":\"flag\",\"type\":\"text\"}"} -:adf{json="{\"marks\":[{\"type\":\"code\"}],\"text\":\"a\\nb\",\"type\":\"text\"}"} +!adf:carry{json="{\"marks\":[{\"type\":\"code\"}],\"text\":\"a\\nb\",\"type\":\"text\"}"} -:adf{json="{\"marks\":[{\"type\":\"link\"}],\"text\":\"Release notes\",\"type\":\"text\"}"} +!adf:carry{json="{\"marks\":[{\"type\":\"link\"}],\"text\":\"Release notes\",\"type\":\"text\"}"} -a:adf{json="{\"marks\":[{\"type\":\"em\"}],\"text\":\".\",\"type\":\"text\"}"}b:adf{json="{\"marks\":[{\"type\":\"strong\"}],\"text\":\",\",\"type\":\"text\"}"}c +a!adf:carry{json="{\"marks\":[{\"type\":\"em\"}],\"text\":\".\",\"type\":\"text\"}"}b!adf:carry{json="{\"marks\":[{\"type\":\"strong\"}],\"text\":\",\",\"type\":\"text\"}"}c diff --git a/corpus/round-trip/combinations/carry-text-attributes.md b/corpus/round-trip/combinations/carry-text-attributes.md index dad9c07..2fd338d 100644 --- a/corpus/round-trip/combinations/carry-text-attributes.md +++ b/corpus/round-trip/combinations/carry-text-attributes.md @@ -1,5 +1,5 @@ -:adf{json="{\"attrs\":{\"localId\":\"01a0a067-68cf-78af-abd6-c660ec0d189b\"},\"text\":\"Owner\",\"type\":\"text\"}"} signs off. +!adf:carry{json="{\"attrs\":{\"localId\":\"01a0a067-68cf-78af-abd6-c660ec0d189b\"},\"text\":\"Owner\",\"type\":\"text\"}"} signs off. -Signed by :adf{json="{\"attrs\":{\"localId\":\"01a0a067-68d2-787e-afc5-2459776aa029\"},\"text\":\"the owner\",\"type\":\"text\"}"} +Signed by !adf:carry{json="{\"attrs\":{\"localId\":\"01a0a067-68d2-787e-afc5-2459776aa029\"},\"text\":\"the owner\",\"type\":\"text\"}"} -:adf{json="{\"attrs\":{\"localId\":\"01a0a067-68d5-7372-9962-29a039654056\"},\"text\":\"One \",\"type\":\"text\"}"}:adf{json="{\"attrs\":{\"localId\":\"01a0a067-68d5-7372-9962-29a039654056\"},\"text\":\"anchor\",\"type\":\"text\"}"} +!adf:carry{json="{\"attrs\":{\"localId\":\"01a0a067-68d5-7372-9962-29a039654056\"},\"text\":\"One \",\"type\":\"text\"}"}!adf:carry{json="{\"attrs\":{\"localId\":\"01a0a067-68d5-7372-9962-29a039654056\"},\"text\":\"anchor\",\"type\":\"text\"}"} diff --git a/corpus/round-trip/combinations/carve-out-directive.json b/corpus/round-trip/combinations/carve-out-directive.json index 55135a3..736b8b9 100644 --- a/corpus/round-trip/combinations/carve-out-directive.json +++ b/corpus/round-trip/combinations/carve-out-directive.json @@ -3,7 +3,16 @@ { "content": [ { - "text": "Write :mention[@Mikael]{id=5b10a2} to tag someone.", + "text": "Write !adf:mention[@Mikael]{id=5b10a2} to tag someone.", + "type": "text" + } + ], + "type": "paragraph" + }, + { + "content": [ + { + "text": "Namespaced under !adf: alone.", "type": "text" } ], @@ -54,7 +63,7 @@ { "content": [ { - "text": ":status[nope]{color=red} vs ", + "text": "!adf:status[nope]{color=red} vs ", "type": "text" }, { diff --git a/corpus/round-trip/combinations/carve-out-directive.md b/corpus/round-trip/combinations/carve-out-directive.md index e3c46cf..12a0e69 100644 --- a/corpus/round-trip/combinations/carve-out-directive.md +++ b/corpus/round-trip/combinations/carve-out-directive.md @@ -1,4 +1,6 @@ -Write \:mention[@Mikael]{id=5b10a2} to tag someone. +Write \!adf:mention[@Mikael]{id=5b10a2} to tag someone. + +Namespaced under \!adf: alone. \::syncBlock {localId=x} @@ -6,6 +8,6 @@ Write \:mention[@Mikael]{id=5b10a2} to tag someone. Stand-up at 10:30, shipped :tada:, {maxLevel} deep, a ::syncBlock mid-line. -:status[In review]\{color} decides. +!adf:status[In review]\{color} decides. -\:status[nope]{color=red} vs :status[yep]{color=red} +\!adf:status[nope]{color=red} vs !adf:status[yep]{color=red} diff --git a/corpus/round-trip/combinations/code-span-joined-run.md b/corpus/round-trip/combinations/code-span-joined-run.md index a89d1f6..092dc3c 100644 --- a/corpus/round-trip/combinations/code-span-joined-run.md +++ b/corpus/round-trip/combinations/code-span-joined-run.md @@ -1,5 +1,5 @@ \`\` \`\``a` -:underline[\`\`]\`\``a` +!adf:underline[\`\`]\`\``a` \`\`\\\``` ` ``\` diff --git a/corpus/round-trip/combinations/combined-page.md b/corpus/round-trip/combinations/combined-page.md index 17439fd..7a8ddd4 100644 --- a/corpus/round-trip/combinations/combined-page.md +++ b/corpus/round-trip/combinations/combined-page.md @@ -1,6 +1,6 @@ # Release 2.4 -Shipped :emoji[๐Ÿš€]{shortName=":rocket:"} on :date{timestamp=1756080000000} โ€” :mention[@Mikael]{id=01a032c3-7a7c-775f-a730-2d79351338b4} owns the rollout, status :status[In review]{color=yellow}. +Shipped !adf:emoji[๐Ÿš€]{shortName=":rocket:"} on !adf:date{timestamp=1756080000000} โ€” !adf:mention[@Mikael]{id=01a032c3-7a7c-775f-a730-2d79351338b4} owns the rollout, status !adf:status[In review]{color=yellow}. | Part | Qty | | --- | --- | @@ -11,7 +11,7 @@ Shipped :emoji[๐Ÿš€]{shortName=":rocket:"} on :date{timestamp=1756080000000} โ€” - Check the collation: - `mysqldump --default-character-set=utf8mb4` -> Rolled back once, see the :underline[postmortem]. +> Rolled back once, see the !adf:underline[postmortem]. --- diff --git a/corpus/round-trip/combinations/directive-autolink-backtick.md b/corpus/round-trip/combinations/directive-autolink-backtick.md index 163f313..4d744f7 100644 --- a/corpus/round-trip/combinations/directive-autolink-backtick.md +++ b/corpus/round-trip/combinations/directive-autolink-backtick.md @@ -1,3 +1,3 @@ -:underline[[http://a\`b](http://a\`b)]`c +!adf:underline[[http://a\`b](http://a\`b)]`c -:underline[[http://d\`e](http://d`e)] +!adf:underline[[http://d\`e](http://d`e)] diff --git a/corpus/round-trip/combinations/directive-link-backticks.md b/corpus/round-trip/combinations/directive-link-backticks.md index 85d19f6..7bdc3e3 100644 --- a/corpus/round-trip/combinations/directive-link-backticks.md +++ b/corpus/round-trip/combinations/directive-link-backticks.md @@ -1,5 +1,5 @@ -:textColor[[ ](\`)]{color=""}`a +!adf:textColor[[ ](\`)]{color=""}`a -:underline[[b](c "\`")]`c` +!adf:underline[[b](c "\`")]`c` -:underline[[d](e`f)] +!adf:underline[[d](e`f)] diff --git a/corpus/round-trip/combinations/directive-link-brackets.md b/corpus/round-trip/combinations/directive-link-brackets.md index e677943..3ef541c 100644 --- a/corpus/round-trip/combinations/directive-link-brackets.md +++ b/corpus/round-trip/combinations/directive-link-brackets.md @@ -1,7 +1,7 @@ -:subsup[[a](\[)] +!adf:subsup[[a](\[)] -:underline[[b](\]) [c](x "\[")] +!adf:underline[[b](\]) [c](x "\[")] -:underline[[d](e[f])] +!adf:underline[[d](e[f])] -:underline[[http://a\[b](http://a\[b)] +!adf:underline[[http://a\[b](http://a\[b)] diff --git a/corpus/round-trip/combinations/directive-link-colon.md b/corpus/round-trip/combinations/directive-link-colon.md deleted file mode 100644 index ecf5427..0000000 --- a/corpus/round-trip/combinations/directive-link-colon.md +++ /dev/null @@ -1,9 +0,0 @@ -:underline[[x](\:a{)] - -:underline[[x](y "\:a{")] - -:underline[[x](\:a[]{)] - -:underline[[x](https://example.com/?q=\:emoji{)] - -:underline[[ab\:c{](ab\:c{)] diff --git a/corpus/round-trip/combinations/directive-link-colon.json b/corpus/round-trip/combinations/directive-link-prefix.json similarity index 73% rename from corpus/round-trip/combinations/directive-link-colon.json rename to corpus/round-trip/combinations/directive-link-prefix.json index 2cc9c86..eb1c0ea 100644 --- a/corpus/round-trip/combinations/directive-link-colon.json +++ b/corpus/round-trip/combinations/directive-link-prefix.json @@ -9,7 +9,7 @@ }, { "attrs": { - "href": ":a{" + "href": "!adf:a{" }, "type": "link" } @@ -30,7 +30,7 @@ { "attrs": { "href": "y", - "title": ":a{" + "title": "!adf:a{" }, "type": "link" } @@ -50,7 +50,7 @@ }, { "attrs": { - "href": ":a[]{" + "href": "!adf:a[]{" }, "type": "link" } @@ -70,7 +70,7 @@ }, { "attrs": { - "href": "https://example.com/?q=:emoji{" + "href": "https://example.com/?q=!adf:emoji{" }, "type": "link" } @@ -90,12 +90,32 @@ }, { "attrs": { - "href": "ab:c{" + "href": "ab:!adf:c{" }, "type": "link" } ], - "text": "ab:c{", + "text": "ab:!adf:c{", + "type": "text" + } + ], + "type": "paragraph" + }, + { + "content": [ + { + "marks": [ + { + "type": "underline" + }, + { + "attrs": { + "href": "ab:!adf:d" + }, + "type": "link" + } + ], + "text": "ab:!adf:d", "type": "text" } ], diff --git a/corpus/round-trip/combinations/directive-link-prefix.md b/corpus/round-trip/combinations/directive-link-prefix.md new file mode 100644 index 0000000..d584e7d --- /dev/null +++ b/corpus/round-trip/combinations/directive-link-prefix.md @@ -0,0 +1,11 @@ +!adf:underline[[x](\!adf:a{)] + +!adf:underline[[x](y "\!adf:a{")] + +!adf:underline[[x](\!adf:a[]{)] + +!adf:underline[[x](https://example.com/?q=\!adf:emoji{)] + +!adf:underline[[ab:\!adf:c{](ab:\!adf:c{)] + +!adf:underline[[ab:\!adf:d](ab:\!adf:d)] diff --git a/corpus/round-trip/combinations/link-definition-carry.md b/corpus/round-trip/combinations/link-definition-carry.md index a06706f..66f8434 100644 --- a/corpus/round-trip/combinations/link-definition-carry.md +++ b/corpus/round-trip/combinations/link-definition-carry.md @@ -1,3 +1,3 @@ -:adf{json="{\"marks\":[{\"attrs\":{\"href\":\"/u\"},\"type\":\"link\"},{\"type\":\"code\"}],\"text\":\"]: a\",\"type\":\"text\"}"} +!adf:carry{json="{\"marks\":[{\"attrs\":{\"href\":\"/u\"},\"type\":\"link\"},{\"type\":\"code\"}],\"text\":\"]: a\",\"type\":\"text\"}"} -:adf{json="{\"attrs\":{\"timestamp\":\"]:a\"},\"marks\":[{\"attrs\":{\"href\":\"/u\"},\"type\":\"link\"}],\"type\":\"date\"}"} +!adf:carry{json="{\"attrs\":{\"timestamp\":\"]:a\"},\"marks\":[{\"attrs\":{\"href\":\"/u\"},\"type\":\"link\"}],\"type\":\"date\"}"} diff --git a/corpus/round-trip/combinations/link-definition-line.md b/corpus/round-trip/combinations/link-definition-line.md index b5c5a28..0f41766 100644 --- a/corpus/round-trip/combinations/link-definition-line.md +++ b/corpus/round-trip/combinations/link-definition-line.md @@ -1,6 +1,6 @@ -\[:date{timestamp="]:a"} +\[!adf:date{timestamp="]:a"} > \[`]: a` -- \[:date{timestamp="]:a"}\ +- \[!adf:date{timestamp="]:a"}\ b diff --git a/corpus/round-trip/combinations/mark-inside-word.md b/corpus/round-trip/combinations/mark-inside-word.md index 6d15c53..1224510 100644 --- a/corpus/round-trip/combinations/mark-inside-word.md +++ b/corpus/round-trip/combinations/mark-inside-word.md @@ -1 +1 @@ -un:adf{json="{\"marks\":[{\"type\":\"strong\"}],\"text\":\"-real\",\"type\":\"text\"}"}istic +un!adf:carry{json="{\"marks\":[{\"type\":\"strong\"}],\"text\":\"-real\",\"type\":\"text\"}"}istic diff --git a/corpus/round-trip/combinations/mark-run-carry.md b/corpus/round-trip/combinations/mark-run-carry.md index 9114578..be4accc 100644 --- a/corpus/round-trip/combinations/mark-run-carry.md +++ b/corpus/round-trip/combinations/mark-run-carry.md @@ -1 +1 @@ -_**Ready**_:adf{json="{\"attrs\":{\"extensionKey\":\"status-lozenge\",\"extensionType\":\"com.atlassian.confluence.macro.core\"},\"marks\":[{\"type\":\"em\"},{\"type\":\"strong\"}],\"type\":\"inlineExtension\"}"}_**to ship**_ +_**Ready**_!adf:carry{json="{\"attrs\":{\"extensionKey\":\"status-lozenge\",\"extensionType\":\"com.atlassian.confluence.macro.core\"},\"marks\":[{\"type\":\"em\"},{\"type\":\"strong\"}],\"type\":\"inlineExtension\"}"}_**to ship**_ diff --git a/corpus/round-trip/combinations/mark-run-matching.md b/corpus/round-trip/combinations/mark-run-matching.md index bc30a65..56c4fa7 100644 --- a/corpus/round-trip/combinations/mark-run-matching.md +++ b/corpus/round-trip/combinations/mark-run-matching.md @@ -1,5 +1,5 @@ -un:adf{json="{\"marks\":[{\"type\":\"em\"}],\"text\":\"a\",\"type\":\"text\"}"}:adf{json="{\"marks\":[{\"type\":\"em\"},{\"type\":\"strong\"}],\"text\":\"b\",\"type\":\"text\"}"}**c**istic +un!adf:carry{json="{\"marks\":[{\"type\":\"em\"}],\"text\":\"a\",\"type\":\"text\"}"}!adf:carry{json="{\"marks\":[{\"type\":\"em\"},{\"type\":\"strong\"}],\"text\":\"b\",\"type\":\"text\"}"}**c**istic -***a*b**:adf{json="{\"marks\":[{\"type\":\"strong\"},{\"type\":\"em\"}],\"text\":\"c\",\"type\":\"text\"}"} +***a*b**!adf:carry{json="{\"marks\":[{\"type\":\"strong\"},{\"type\":\"em\"}],\"text\":\"c\",\"type\":\"text\"}"} -~~un~~:adf{json="{\"marks\":[{\"type\":\"strike\"},{\"type\":\"em\"}],\"text\":\"a\",\"type\":\"text\"}"}:adf{json="{\"marks\":[{\"type\":\"strike\"},{\"type\":\"em\"},{\"type\":\"strong\"}],\"text\":\"b\",\"type\":\"text\"}"}~~**c**istic~~ +~~un~~!adf:carry{json="{\"marks\":[{\"type\":\"strike\"},{\"type\":\"em\"}],\"text\":\"a\",\"type\":\"text\"}"}!adf:carry{json="{\"marks\":[{\"type\":\"strike\"},{\"type\":\"em\"},{\"type\":\"strong\"}],\"text\":\"b\",\"type\":\"text\"}"}~~**c**istic~~ diff --git a/corpus/round-trip/combinations/mark-run.md b/corpus/round-trip/combinations/mark-run.md index 863dc71..051f819 100644 --- a/corpus/round-trip/combinations/mark-run.md +++ b/corpus/round-trip/combinations/mark-run.md @@ -1,5 +1,5 @@ -:textColor[Overdue since :status[Blocked]{color=red} on Tuesday]{color="#ae2e24"} +!adf:textColor[Overdue since !adf:status[Blocked]{color=red} on Tuesday]{color="#ae2e24"} _Torque the **M8 bolt** to **25 Nm**_ -[Release notes :emoji[๐Ÿš€]{shortName=":rocket:"}](https://example.com/release) +[Release notes !adf:emoji[๐Ÿš€]{shortName=":rocket:"}](https://example.com/release) diff --git a/corpus/round-trip/combinations/pipe-cell-whitespace.md b/corpus/round-trip/combinations/pipe-cell-whitespace.md index 05c77e5..6d01590 100644 --- a/corpus/round-trip/combinations/pipe-cell-whitespace.md +++ b/corpus/round-trip/combinations/pipe-cell-whitespace.md @@ -1,4 +1,4 @@ | Cell | Note | | --- | --- | -| :text{text=" "}lead | | -| :status[ok]{style="a\u007cb"} | trail:text{text=" "} | +| !adf:text{text=" "}lead | | +| !adf:status[ok]{style="a\u007cb"} | trail!adf:text{text=" "} | diff --git a/corpus/round-trip/commonmark-subset/hard-break.md b/corpus/round-trip/commonmark-subset/hard-break.md index ebede47..e7bdab5 100644 --- a/corpus/round-trip/commonmark-subset/hard-break.md +++ b/corpus/round-trip/commonmark-subset/hard-break.md @@ -1,6 +1,6 @@ Line one\ Line two -Trailing:hardBreak{} +Trailing!adf:hardBreak{} -## Two:hardBreak{}lines +## Two!adf:hardBreak{}lines diff --git a/corpus/round-trip/inline-nodes/content-slot.md b/corpus/round-trip/inline-nodes/content-slot.md index 2d247db..289f4e2 100644 --- a/corpus/round-trip/inline-nodes/content-slot.md +++ b/corpus/round-trip/inline-nodes/content-slot.md @@ -1,5 +1,5 @@ -:status[In review]{color=yellow} carries the text in the slot. +!adf:status[In review]{color=yellow} carries the text in the slot. -:status{color=neutral} carries no text attribute. +!adf:status{color=neutral} carries no text attribute. -:status[]{color=neutral} carries the empty string. +!adf:status[]{color=neutral} carries the empty string. diff --git a/corpus/round-trip/inline-nodes/date.md b/corpus/round-trip/inline-nodes/date.md index ef5707d..5d225ed 100644 --- a/corpus/round-trip/inline-nodes/date.md +++ b/corpus/round-trip/inline-nodes/date.md @@ -1 +1 @@ -Due :date{timestamp=1756080000000}, reviewed :date{localId=01a033ab-781c-7f79-9c8a-fb63356c1cad timestamp=1735689600000}. +Due !adf:date{timestamp=1756080000000}, reviewed !adf:date{localId=01a033ab-781c-7f79-9c8a-fb63356c1cad timestamp=1735689600000}. diff --git a/corpus/round-trip/inline-nodes/emoji.md b/corpus/round-trip/inline-nodes/emoji.md index d3f6533..3bc060d 100644 --- a/corpus/round-trip/inline-nodes/emoji.md +++ b/corpus/round-trip/inline-nodes/emoji.md @@ -1 +1 @@ -Shipped :emoji[๐ŸŽ‰]{id=1f389 localId=01a033ad-f00e-7acb-a009-37ddd25d7949 shortName=":tada:"} on Friday. +Shipped !adf:emoji[๐ŸŽ‰]{id=1f389 localId=01a033ad-f00e-7acb-a009-37ddd25d7949 shortName=":tada:"} on Friday. diff --git a/corpus/round-trip/inline-nodes/hard-break-attributes.md b/corpus/round-trip/inline-nodes/hard-break-attributes.md index 1bcc6f6..8fad5a1 100644 --- a/corpus/round-trip/inline-nodes/hard-break-attributes.md +++ b/corpus/round-trip/inline-nodes/hard-break-attributes.md @@ -1,3 +1,3 @@ -One:hardBreak{localId=01a0431d-201e-7400-a175-9c04d3628f95}Two +One!adf:hardBreak{localId=01a0431d-201e-7400-a175-9c04d3628f95}Two -Three:hardBreak{text="\n"}Four +Three!adf:hardBreak{text="\n"}Four diff --git a/corpus/round-trip/inline-nodes/inline-card.md b/corpus/round-trip/inline-nodes/inline-card.md index 2ef9f06..0da29fd 100644 --- a/corpus/round-trip/inline-nodes/inline-card.md +++ b/corpus/round-trip/inline-nodes/inline-card.md @@ -1,3 +1,3 @@ -Tracking :inlineCard{url="https://example.com/browse/ABC-123"} this week. +Tracking !adf:inlineCard{url="https://example.com/browse/ABC-123"} this week. -:inlineCard{data="{\"@type\":\"Link\",\"name\":\"Torque spec\",\"url\":\"https://example.com/torque\"}" localId=01a033ad-1fa5-76fa-aad0-2e56a17d76aa} +!adf:inlineCard{data="{\"@type\":\"Link\",\"name\":\"Torque spec\",\"url\":\"https://example.com/torque\"}" localId=01a033ad-1fa5-76fa-aad0-2e56a17d76aa} diff --git a/corpus/round-trip/inline-nodes/mark-content-slot.md b/corpus/round-trip/inline-nodes/mark-content-slot.md index 83ee860..d4c1882 100644 --- a/corpus/round-trip/inline-nodes/mark-content-slot.md +++ b/corpus/round-trip/inline-nodes/mark-content-slot.md @@ -1 +1 @@ -**:mention[@Mikael]{id=01a032c3-7a7c-775f-a730-2d79351338b4}** owns the rebuild. +**!adf:mention[@Mikael]{id=01a032c3-7a7c-775f-a730-2d79351338b4}** owns the rebuild. diff --git a/corpus/round-trip/inline-nodes/mark-nesting.md b/corpus/round-trip/inline-nodes/mark-nesting.md index e0a2959..41c2451 100644 --- a/corpus/round-trip/inline-nodes/mark-nesting.md +++ b/corpus/round-trip/inline-nodes/mark-nesting.md @@ -1,3 +1,3 @@ -_:underline[Rewired]_ at the mains. +_!adf:underline[Rewired]_ at the mains. -:underline[_Rewound_] by hand. +!adf:underline[_Rewound_] by hand. diff --git a/corpus/round-trip/inline-nodes/marks.md b/corpus/round-trip/inline-nodes/marks.md index c69b440..4a1c203 100644 --- a/corpus/round-trip/inline-nodes/marks.md +++ b/corpus/round-trip/inline-nodes/marks.md @@ -1,3 +1,3 @@ -:textColor[Over]{color="#ae2e24"}:textColor[due]{color="#a54800"} since the H:subsup[2]{type=sub}O test, :underline[signed by :mention[@Mikael]{id=01a032c3-7a7c-775f-a730-2d79351338b4}] on the 2:subsup[nd]{type=sup} of May. +!adf:textColor[Over]{color="#ae2e24"}!adf:textColor[due]{color="#a54800"} since the H!adf:subsup[2]{type=sub}O test, !adf:underline[signed by !adf:mention[@Mikael]{id=01a032c3-7a7c-775f-a730-2d79351338b4}] on the 2!adf:subsup[nd]{type=sup} of May. -:border[:mediaInline{collection=contentId-98237 id=01a032c3-7a90-70c9-88f6-c60f710eda07}]{color="#091e42" size=2} +!adf:border[!adf:mediaInline{collection=contentId-98237 id=01a032c3-7a90-70c9-88f6-c60f710eda07}]{color="#091e42" size=2} diff --git a/corpus/round-trip/inline-nodes/media-inline.md b/corpus/round-trip/inline-nodes/media-inline.md index 64cd79b..781d25d 100644 --- a/corpus/round-trip/inline-nodes/media-inline.md +++ b/corpus/round-trip/inline-nodes/media-inline.md @@ -1,3 +1,3 @@ -See :mediaInline{alt="Torque chart" collection=contentId-98237 height=24 id=01a033ae-69a3-7411-b388-a37f9d103ded type=file width=24} mid-sentence. +See !adf:mediaInline{alt="Torque chart" collection=contentId-98237 height=24 id=01a033ae-69a3-7411-b388-a37f9d103ded type=file width=24} mid-sentence. -Or :mediaInline{data="{\"fileName\":\"budget.pdf\",\"fileSize\":18432}" localId=01a033b1-b5a0-7a98-9e9c-48192bd30ac0 occurrenceKey=01a033ab-bab8-74b0-9ec8-b3419b000189 type=image} beside the text. +Or !adf:mediaInline{data="{\"fileName\":\"budget.pdf\",\"fileSize\":18432}" localId=01a033b1-b5a0-7a98-9e9c-48192bd30ac0 occurrenceKey=01a033ab-bab8-74b0-9ec8-b3419b000189 type=image} beside the text. diff --git a/corpus/round-trip/inline-nodes/mention.md b/corpus/round-trip/inline-nodes/mention.md index a39976c..973db6e 100644 --- a/corpus/round-trip/inline-nodes/mention.md +++ b/corpus/round-trip/inline-nodes/mention.md @@ -1 +1 @@ -Ping :mention[@Mikael]{accessLevel=CONTAINER id=01a032c3-7a7c-775f-a730-2d79351338b4 localId=01a033ad-ecb7-7530-aa1c-ff25031e96e2 userType=DEFAULT} before merging. +Ping !adf:mention[@Mikael]{accessLevel=CONTAINER id=01a032c3-7a7c-775f-a730-2d79351338b4 localId=01a033ad-ecb7-7530-aa1c-ff25031e96e2 userType=DEFAULT} before merging. diff --git a/corpus/round-trip/inline-nodes/status.md b/corpus/round-trip/inline-nodes/status.md index ed01471..ab8f278 100644 --- a/corpus/round-trip/inline-nodes/status.md +++ b/corpus/round-trip/inline-nodes/status.md @@ -1 +1 @@ -The rebuild is :status[In review]{color=yellow localId=01a033a7-d68a-71a9-a58a-edf5757ad176 style=bold} until Friday. +The rebuild is !adf:status[In review]{color=yellow localId=01a033a7-d68a-71a9-a58a-edf5757ad176 style=bold} until Friday. diff --git a/corpus/round-trip/inline-nodes/text-whitespace.md b/corpus/round-trip/inline-nodes/text-whitespace.md index 89a815f..ecdf6b9 100644 --- a/corpus/round-trip/inline-nodes/text-whitespace.md +++ b/corpus/round-trip/inline-nodes/text-whitespace.md @@ -1,15 +1,15 @@ -:text{text=" "}Two leading spaces held, and one text node split:text{text="\n"}over two lines. +!adf:text{text=" "}Two leading spaces held, and one text node split!adf:text{text="\n"}over two lines. -Trailing space held:text{text=" "} +Trailing space held!adf:text{text=" "} -:text{text="\t"}Tabbed at the block edge. +!adf:text{text="\t"}Tabbed at the block edge. -Space before the break:text{text=" "}\ +Space before the break!adf:text{text=" "}\ survives. Space after the break\ -:text{text=" "}survives too. +!adf:text{text=" "}survives too. -Kept _:text{text=" "}inside:text{text=" "}_, exactly. +Kept _!adf:text{text=" "}inside!adf:text{text=" "}_, exactly. Edged with the whitespace CommonMark keeps diff --git a/corpus/round-trip/opaque-carry/unknown-inline.json b/corpus/round-trip/opaque-carry/unknown-inline.json index 0dd91b4..280d7bc 100644 --- a/corpus/round-trip/opaque-carry/unknown-inline.json +++ b/corpus/round-trip/opaque-carry/unknown-inline.json @@ -56,7 +56,7 @@ { "content": [ { - "text": ":", + "text": "!adf:", "type": "text" }, { diff --git a/corpus/round-trip/opaque-carry/unknown-inline.md b/corpus/round-trip/opaque-carry/unknown-inline.md index 22343ff..a5d8b95 100644 --- a/corpus/round-trip/opaque-carry/unknown-inline.md +++ b/corpus/round-trip/opaque-carry/unknown-inline.md @@ -1,7 +1,7 @@ -Draft: :adf{json="{\"attrs\":{\"text\":\"Add a summary\"},\"type\":\"placeholder\"}"} โ€” expected Friday. +Draft: !adf:carry{json="{\"attrs\":{\"text\":\"Add a summary\"},\"type\":\"placeholder\"}"} โ€” expected Friday. -**Blocked** on :adf{json="{\"attrs\":{\"extensionKey\":\"jira-issue\",\"extensionType\":\"com.atlassian.jira.macro\"},\"marks\":[{\"type\":\"strong\"}],\"type\":\"inlineExtension\"}"} until the rebuild lands. +**Blocked** on !adf:carry{json="{\"attrs\":{\"extensionKey\":\"jira-issue\",\"extensionType\":\"com.atlassian.jira.macro\"},\"marks\":[{\"type\":\"strong\"}],\"type\":\"inlineExtension\"}"} until the rebuild lands. -\::adf{json="{\"type\":\"blockCard\"}"} +\!adf:!adf:carry{json="{\"type\":\"blockCard\"}"} -**a**:adf{json="{\"marks\":[{\"type\":\"strong\"}],\"type\":\"placeholder\"}"}**b** +**a**!adf:carry{json="{\"marks\":[{\"type\":\"strong\"}],\"type\":\"placeholder\"}"}**b** diff --git a/spec/flavour.md b/spec/flavour.md index 4ced431..d38046b 100644 --- a/spec/flavour.md +++ b/spec/flavour.md @@ -418,10 +418,10 @@ Right. Attributes and the carry fallback read as in the block sections, the carry in its inline form. Of the nodes below, `emoji`, `mention` and `status` spell their `text` attribute in the content slot as plain text: `[]` is the empty string, absent content is the absent attribute, non-empty content -parsing to anything but one unmarked text node โ€” adjacent text nodes with identical marks and no -attributes merged first โ€” is a named error, and so is a `text` key in `{attrs}`. An enclosing mark -spelling does not reach into the slot. The rest take no content, `!adf:text` included; content on a -node that takes none is a named error. +parsing to anything but one text node carrying neither marks nor attributes โ€” adjacent text nodes +with identical marks and no attributes merged first โ€” is a named error, and so is a `text` key in +`{attrs}`. An enclosing mark spelling does not reach into the slot. The rest take no content, +`!adf:text` included; content on a node that takes none is a named error. - `date` โ€” Attributes: `localId` (string), `timestamp` (string, epoch milliseconds). - `emoji` โ€” Attributes: `id` (string), `localId` (string), `shortName` (string, `:name:`), `text` diff --git a/src/markdown-property.test.ts b/src/markdown-property.test.ts index 1778594..914f121 100644 --- a/src/markdown-property.test.ts +++ b/src/markdown-property.test.ts @@ -11,7 +11,8 @@ import { adfDocument, attributes, jsonKey, jsonValue, markdownPieces, propertyRu import { adfToMarkdown } from './markdown/emit/adf-to-markdown.ts' import { blockArgument } from './markdown/block-directive-arguments.ts' import { blockDirectives } from './adf/block-directives.ts' -import { carryName } from './markdown/opaque-carry.ts' +import { carryFence, carryName } from './markdown/opaque-carry.ts' +import { directivePrefix, spellAttributes, spellDirectiveOpener, spellJsonAttribute, spellLeafDirective, spellStringAttribute, spellVocabulary } from './markdown/directive-syntax.ts' import { fencedCodeBlock } from './markdown/backtick-runs.ts' import { inlineDirectives } from './adf/inline-directives.ts' import { listBreakName } from './markdown/list-break.ts' @@ -21,7 +22,6 @@ import { markdownToAdf } from './markdown/parse/markdown-to-adf.ts' import { marksAttribute } from './markdown/block-directive-marks.ts' import { nodeContent, nodeMarks } from './adf/document.ts' import { serializeCanonicalJson } from './canonical-json.ts' -import { spellAttributes, spellJsonAttribute, spellLeafDirective, spellStringAttribute, spellVocabulary } from './markdown/directive-syntax.ts' import { textDirectiveName } from './markdown/text-directive.ts' import { toEditorNormal } from './adf/editor-normal.ts' import { vocabularyPairs } from './adf/attribute-vocabulary.ts' @@ -107,9 +107,10 @@ const autolink = fc.oneof( ) const hostileAutolink = fc.tuple(fc.constantFrom('http://', 'ab:', 'a:'), textOf(0)).map(([scheme, rest]) => `<${scheme}${rest}>`) const inlineHtml = fc.constantFrom('', '', '', "", '', '', '', '', '', '
', '') -const hardBreak = fc.constantFrom('\\\n', ' \n', '\n', ':hardBreak{}') -const textDirective = fc.constantFrom(' ', ' ', '\t', '\n', '\n\n').map((held) => `:${textDirectiveName}{${textDirectiveName}=${spellStringAttribute(held)}}`) -const hostileTextDirective = fc.constantFrom(' \n', 'a', '').map((held) => `:${textDirectiveName}{${textDirectiveName}=${spellStringAttribute(held)}}`) +const hardBreak = fc.constantFrom('\\\n', ' \n', '\n', spellLeafDirective('hardBreak', '')) +const spellTextDirective = (held: string) => spellLeafDirective(textDirectiveName, `{${textDirectiveName}=${spellStringAttribute(held)}}`) +const textDirective = fc.constantFrom(' ', ' ', '\t', '\n', '\n\n').map(spellTextDirective) +const hostileTextDirective = fc.constantFrom(' \n', 'a', '').map(spellTextDirective) const url = fc.stringMatching(/^https?:\/\/[a-z]{1,6}\.[a-z]{2,3}(?:\/[a-z0-9()]{0,5})?$/) const title = (text: Arbitrary) => fc.oneof(fc.constant(''), text.map((held) => ` "${held}"`), text.map((held) => ` '${held}'`), text.map((held) => ` (${held})`)) @@ -163,10 +164,11 @@ const carriedNode = fc.oneof( fc.tuple(fc.constantFrom('mention', 'paragraph', 'status', 'widget'), fc.dictionary(jsonKey, jsonValue, { maxKeys: 2, noNullPrototype: true })).map(([type, attrs]): JsonValue => ({ attrs, type })), textOf(1).map((held): JsonValue => ({ text: held, type: 'text' })), ) -const inlineCarry = carriedNode.map((node) => `:${carryName}{json=${spellStringAttribute(serializeCanonicalJson(node, 'compact'))}}`) -const hostileInlineCarry = fc.oneof(carriedNode, jsonValue).map((node) => `:${carryName}{json=${spellStringAttribute(JSON.stringify(node, null, 1))}}`) -const blockCarry = carriedNode.map((node) => fencedCodeBlock(carryName, serializeCanonicalJson(node, 'two-space'))) -const hostileBlockCarry = fc.oneof(carriedNode, jsonValue).map((node) => fencedCodeBlock(carryName, JSON.stringify(node))) +const spellCarry = (json: string) => spellLeafDirective(carryName, `{json=${spellStringAttribute(json)}}`) +const inlineCarry = carriedNode.map((node) => spellCarry(serializeCanonicalJson(node, 'compact'))) +const hostileInlineCarry = fc.oneof(carriedNode, jsonValue).map((node) => spellCarry(JSON.stringify(node, null, 1))) +const blockCarry = carriedNode.map((node) => fencedCodeBlock(carryFence, serializeCanonicalJson(node, 'two-space'))) +const hostileBlockCarry = fc.oneof(carriedNode, jsonValue).map((node) => fencedCodeBlock(carryFence, JSON.stringify(node))) function prefixLines(body: string, first: string, rest: (index: number) => string): string { return body @@ -225,16 +227,18 @@ function inlineMarkdown(hostile: boolean): InlineMarkdown { ...Object.entries(inlineDirectives).map(([name, directive]) => fc .tuple(directive.textAttribute === undefined ? fc.constant(null) : fc.option(hostile ? word : prose), tableAttributes(directive.attributes, directive.textAttribute)) - .map(([slot, attrs]) => (slot === null ? spellLeafDirective(name, attrs) : `:${name}[${slot}]${attrs}`)), + .map(([slot, attrs]) => (slot === null ? spellLeafDirective(name, attrs) : `${spellDirectiveOpener(name)}${slot}]${attrs}`)), ), ...Object.entries(markAttributes) .filter(([name]) => hostile || markSpelling(name)?.kind === 'directive') - .map(([name, vocabulary]) => fc.tuple(tie('inlines'), tableAttributes(vocabulary)).map(([content, attrs]) => `:${name}[${content}]${attrs}`)), + .map(([name, vocabulary]) => fc.tuple(tie('inlines'), tableAttributes(vocabulary)).map(([content, attrs]) => `${spellDirectiveOpener(name)}${content}]${attrs}`)), ), weight: 2, }, { - arbitrary: fc.tuple(directiveName, fc.option(tie('inlines'), { freq: 3 }), hostileAttributes).map(([name, content, attrs]) => `:${name}${content === null ? '' : `[${content}]`}${attrs}`), + arbitrary: fc + .tuple(directiveName, fc.option(tie('inlines'), { freq: 3 }), hostileAttributes) + .map(([name, content, attrs]) => `${directivePrefix}${name}${content === null ? '' : `[${content}]`}${attrs}`), hostile: true, weight: 2, }, diff --git a/src/markdown/code-language.ts b/src/markdown/code-language.ts index 27f3a07..6d4ef4e 100644 --- a/src/markdown/code-language.ts +++ b/src/markdown/code-language.ts @@ -1,5 +1,5 @@ import type { JsonValue } from '../json-value.ts' -import { carryName } from './opaque-carry.ts' +import { carryFence } from './opaque-carry.ts' import { holdsControlCharacter } from './commonmark-grammar.ts' import { holdsEntityReference } from './entity-references.ts' @@ -8,7 +8,7 @@ export type LanguageSlot = { info: string; kind: 'fence' } | { kind: 'attribute' // spec/flavour.md, The CommonMark blocks: the one slot a codeBlock's language rides. export function languageSlot(language: JsonValue | undefined): LanguageSlot { if (language === undefined) return { kind: 'none' } - if (typeof language !== 'string' || language === '' || language === carryName) return { kind: 'attribute' } + if (typeof language !== 'string' || language === '' || language === carryFence) return { kind: 'attribute' } if (/[`\\]/.test(language) || holdsControlCharacter(language) || language !== language.trim() || holdsEntityReference(language)) return { kind: 'attribute' } return { info: language, kind: 'fence' } } diff --git a/src/markdown/directive-syntax.test.ts b/src/markdown/directive-syntax.test.ts index ae9f20f..b005f83 100644 --- a/src/markdown/directive-syntax.test.ts +++ b/src/markdown/directive-syntax.test.ts @@ -73,8 +73,8 @@ test('spells an empty {attrs} only where the brace itself claims the directive', const omitted = 'an empty {attrs} is omitted unless the { itself claims the directive: this one spells {}' assert.equal(fault('::rule {}'), omitted) assert.equal(fault(':::panel info {}'), omitted) - assert.equal(inline(':underline[a]{}'), omitted) - spans(':hardBreak{}', 'hardBreak', undefined) + assert.equal(inline('!adf:underline[a]{}'), omitted) + spans('!adf:hardBreak{}', 'hardBreak', undefined) }) test('names the directive line no spelling reads', () => { @@ -98,7 +98,7 @@ test('names the attributes no spelling reads', () => { assert.equal(fault('::panel { a=1}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\::: keeps the line literal text') assert.equal(fault('::panel {a=1 }'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\::: keeps the line literal text') assert.equal(fault('::panel {a=1 a=2}'), 'the attribute key a is spelled twice') - assert.equal(inline(':mention[@A]{id}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\: keeps the colon literal') + assert.equal(inline('!adf:mention[@A]{id}'), 'an attribute reads key=value, the value bare or double-quoted: this one does not; \\!adf: keeps the prefix literal') }) test('breaks the directive on the raw characters a quoted value spells as escapes', () => { @@ -108,41 +108,43 @@ test('breaks the directive on the raw characters a quoted value spells as escape assert.equal(fault('::panel {a="x|y"}'), 'a raw | inside {attrs} breaks the directive: spell it \\u007c') }) -test('reads an inline directive only where a bracket or a brace follows the name', () => { +test('claims at the prefix, and reads a directive only where a bracket or a brace follows the name', () => { + const completes = 'an unescaped !adf: completes no directive; \\!adf: keeps the prefix literal' assert.equal(inline('Part.'), 'unclaimed') - assert.equal(inline(':10:30'), 'unclaimed') - assert.equal(inline(':smile:'), 'unclaimed') - assert.equal(inline(':Mention[@A]'), 'unclaimed') - assert.equal(inline(':mention @A'), 'unclaimed') - spans(':mention[@A]', 'mention', '@A') - spans(':date{timestamp=1756080000000}', 'date', undefined, ['timestamp', '1756080000000']) - spans(':emoji[]{shortName=":tada:"}', 'emoji', '', ['shortName', ':tada:', '":tada:"']) - spans(':underline[ a ]', 'underline', ' a ') + assert.equal(inline(':mention[@A]'), 'unclaimed') + assert.equal(inline('!adfx:mention[@A]'), 'unclaimed') + assert.equal(inline('!adf:'), completes) + assert.equal(inline('!adf:Mention[@A]'), completes) + assert.equal(inline('!adf:mention @A'), completes) + spans('!adf:mention[@A]', 'mention', '@A') + spans('!adf:date{timestamp=1756080000000}', 'date', undefined, ['timestamp', '1756080000000']) + spans('!adf:emoji[]{shortName=":tada:"}', 'emoji', '', ['shortName', ':tada:', '":tada:"']) + spans('!adf:underline[ a ]', 'underline', ' a ') }) test('binds an inline directive as a unit, its content balancing brackets like link text', () => { - spans(':underline[a [b] c]', 'underline', 'a [b] c') - spans(':underline[a \\] b]', 'underline', 'a \\] b') - spans(':underline[a `]` b]', 'underline', 'a `]` b') - spans(':underline[a `b c]', 'underline', 'a `b c') - spans(':underline[:status[x]{color=red}]', 'underline', ':status[x]{color=red}') - spans(':status[x]{color=red style="bold "}', 'status', 'x', ['color', 'red'], ['style', 'bold ', '"bold "']) - assert.deepEqual(inline(':underline[a] {}'), { attributes: attributes(), content: 'a', length: 13, name: 'underline' }) - assert.deepEqual(inline(':text{text=" "} and more'), { attributes: attributes(['text', ' ', '" "']), content: undefined, length: 15, name: 'text' }) + spans('!adf:underline[a [b] c]', 'underline', 'a [b] c') + spans('!adf:underline[a \\] b]', 'underline', 'a \\] b') + spans('!adf:underline[a `]` b]', 'underline', 'a `]` b') + spans('!adf:underline[a `b c]', 'underline', 'a `b c') + spans('!adf:underline[!adf:status[x]{color=red}]', 'underline', '!adf:status[x]{color=red}') + spans('!adf:status[x]{color=red style="bold "}', 'status', 'x', ['color', 'red'], ['style', 'bold ', '"bold "']) + assert.deepEqual(inline('!adf:underline[a] {}'), { attributes: attributes(), content: 'a', length: 17, name: 'underline' }) + assert.deepEqual(inline('!adf:text{text=" "} and more'), { attributes: attributes(['text', ' ', '" "']), content: undefined, length: 19, name: 'text' }) }) test('names the inline directive left unclosed at the end of its line', () => { - assert.equal(inline(':mention[@A'), 'an inline directive [content] is unclosed; \\: keeps the colon literal') - assert.equal(inline(':mention[@A\nB]'), 'an inline directive [content] is unclosed; \\: keeps the colon literal') - assert.equal(inline(':mention[a `b\nc` d]'), 'an inline directive [content] is unclosed; \\: keeps the colon literal') - assert.equal(inline(':underline[:status[x'), 'an inline directive [content] is unclosed; \\: keeps the colon literal') - assert.equal(inline(':mention[@A]{id=1'), 'the {attrs} closing brace is missing') - assert.equal(inline(':mention{id=1'), 'the {attrs} closing brace is missing') - assert.equal(inline(':text{text="a\nb"}'), 'the {attrs} quoted value is not a JSON string') + assert.equal(inline('!adf:mention[@A'), 'an inline directive [content] is unclosed; \\!adf: keeps the prefix literal') + assert.equal(inline('!adf:mention[@A\nB]'), 'an inline directive [content] is unclosed; \\!adf: keeps the prefix literal') + assert.equal(inline('!adf:mention[a `b\nc` d]'), 'an inline directive [content] is unclosed; \\!adf: keeps the prefix literal') + assert.equal(inline('!adf:underline[!adf:status[x'), 'an inline directive [content] is unclosed; \\!adf: keeps the prefix literal') + assert.equal(inline('!adf:mention[@A]{id=1'), 'the {attrs} closing brace is missing') + assert.equal(inline('!adf:mention{id=1'), 'the {attrs} closing brace is missing') + assert.equal(inline('!adf:text{text="a\nb"}'), 'the {attrs} quoted value is not a JSON string') }) test('refuses inline directives nested deeper than the parser carries', () => { - const nest = (depth: number): string => `${':underline['.repeat(depth)}x${']'.repeat(depth)}` + const nest = (depth: number): string => `${'!adf:underline['.repeat(depth)}x${']'.repeat(depth)}` spans(nest(largestNesting), 'underline', nest(largestNesting - 1)) assert.equal(inline(nest(largestNesting + 1)), `the input nests inline directives deeper than the ${largestNesting} levels the parser carries`) }) diff --git a/src/markdown/directive-syntax.ts b/src/markdown/directive-syntax.ts index 3c0ec9e..f4d5fbb 100644 --- a/src/markdown/directive-syntax.ts +++ b/src/markdown/directive-syntax.ts @@ -26,11 +26,13 @@ type Attributes = { attributes: DirectiveAttributes; length: number } type AttributePair = { end: number; key: string; value: DirectiveValue } +export const directivePrefix = '!adf:' + const bareTokenSource = '[A-Za-z0-9_-]+' const bareRun = new RegExp(bareTokenSource, 'y') const bareToken = new RegExp(`^${bareTokenSource}$`) const directiveName = /[a-z][A-Za-z0-9]*/y -const inlineDirectiveOpener = /:[a-z][A-Za-z0-9]*[[{]/y +const inlineDirectiveOpener = new RegExp(`${directivePrefix}[a-z][A-Za-z0-9]*[[{]`, 'y') const lineEnd = /^[ \t]*$/ // spec/flavour.md, Attributes. const reservedSource = '[&<`|]' @@ -39,12 +41,13 @@ const rawReserved = new RegExp(reservedSource) const noAttributes: DirectiveAttributes = new Map() export const directiveLineEscape = '\\::: keeps the line literal text' -export const inlineDirectiveEscape = '\\: keeps the colon literal' +export const inlineDirectiveEscape = `\\${directivePrefix} keeps the prefix literal` const emptyFault = 'an empty {attrs} is omitted unless the { itself claims the directive: this one spells {}' const nameFault = `a directive name reads [a-z][A-Za-z0-9]*: this one does not; ${directiveLineEscape}` const orderFault = 'the {attrs} keys read in alphabetical order' const pairFault = 'an attribute reads key=value, the value bare or double-quoted: this one does not' +const prefixFault = `an unescaped ${directivePrefix} completes no directive; ${inlineDirectiveEscape}` const shapeFault = `a directive line reads a name, one bare argument and {attrs}, one space apart: this one does not; ${directiveLineEscape}` export function attributeValue(text: string, kind: AttributeKind): AttributeReading { @@ -56,9 +59,12 @@ export function attributeValue(text: string, kind: AttributeKind): AttributeRead return overNested(parsed) ? { refusal: 'nesting' } : { value: { kind, value: parsed } } } -export function holdsInlineDirectiveOpener(text: string): boolean { - for (let index = text.indexOf(':'); index !== -1; index = text.indexOf(':', index + 1)) if (opensInlineDirective(text, index)) return true - return false +export function claimsDirectivePrefix(text: string, index: number): boolean { + return text.startsWith(directivePrefix, index) +} + +export function holdsDirectivePrefix(text: string): boolean { + return text.includes(directivePrefix) } export function isBareToken(text: string): boolean { @@ -69,10 +75,6 @@ export function malformedDirective(message: string): ConvertFault { return { code: 'malformed-directive', message } } -export function opensInlineDirective(text: string, index: number): boolean { - return inlineDirectiveName(text, index) !== undefined -} - export function readDirectiveLine(line: string): Read | undefined { if (!claimsDirectiveLine(line)) return undefined const colons = runLength(line, 0) @@ -112,8 +114,12 @@ export function spellJsonAttribute(value: JsonValue): string { return quote(serializeCanonicalJson(value, 'compact')) } +export function spellDirectiveOpener(name: string): string { + return `${directivePrefix}${name}[` +} + export function spellLeafDirective(name: string, attributes: string): string { - return `:${name}${attributes === '' ? '{}' : attributes}` + return `${directivePrefix}${name}${attributes === '' ? '{}' : attributes}` } export function spellStringAttribute(text: string): string { @@ -159,7 +165,7 @@ function escapeDigits(character: string): string { function inlineDirectiveName(text: string, index: number): string | undefined { inlineDirectiveOpener.lastIndex = index const opened = inlineDirectiveOpener.exec(text)?.[0] - return opened === undefined ? undefined : opened.slice(1, -1) + return opened === undefined ? undefined : opened.slice(directivePrefix.length, -1) } function readDirectiveHeader(rest: string): Read<{ argument: string | undefined; attributes: DirectiveAttributes; name: string }> { @@ -187,12 +193,13 @@ function readDirectiveHeader(rest: string): Read<{ argument: string | undefined; } function readNestedDirective(text: string, index: number, depth: number): Read | undefined { + if (!claimsDirectivePrefix(text, index)) return undefined const name = inlineDirectiveName(text, index) - if (name === undefined) return undefined + if (name === undefined) return { fault: malformedDirective(prefixFault) } if (depth > largestNesting) { return { fault: { code: 'unsupported-nesting-depth', message: `the input nests inline directives deeper than the ${largestNesting} levels the parser carries` } } } - let cursor = index + 1 + name.length + let cursor = index + directivePrefix.length + name.length let content: string | undefined if (text.charAt(cursor) === '[') { const end = readDirectiveContent(text, cursor + 1, depth) @@ -227,7 +234,7 @@ function readDirectiveContent(text: string, start: number, depth: number): Read< cursor = span continue } - const nested = character === ':' ? readNestedDirective(text, cursor, depth + 1) : undefined + const nested = character === '!' ? readNestedDirective(text, cursor, depth + 1) : undefined if (nested !== undefined) { if (nested.fault !== undefined) return { fault: nested.fault } cursor += nested.value.length diff --git a/src/markdown/emit/adf-to-markdown.test.ts b/src/markdown/emit/adf-to-markdown.test.ts index 5995088..69d5c0d 100644 --- a/src/markdown/emit/adf-to-markdown.test.ts +++ b/src/markdown/emit/adf-to-markdown.test.ts @@ -52,14 +52,14 @@ test('refuses a document version the markdown cannot carry', () => { test('carries a text node attribute no spelling holds', () => { assert.equal( markdown(adfToMarkdown(document(paragraph({ attrs: { localId: 'a' }, text: 'x', type: 'text' })))), - ':adf{json="{\\"attrs\\":{\\"localId\\":\\"a\\"},\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n', + '!adf:carry{json="{\\"attrs\\":{\\"localId\\":\\"a\\"},\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n', ) }) test('spells a CommonMark block as a directive where its own spelling holds neither attribute nor mark', () => { assert.equal(markdown(adfToMarkdown(document({ attrs: { localId: 'a' }, type: 'paragraph' }))), '::paragraph {localId=a}\n') assert.equal(markdown(adfToMarkdown(document({ attrs: { wrap: true }, type: 'codeBlock' }))), ':::codeBlock {wrap=true}\n```\n```\n:::\n') - assert.equal(markdown(adfToMarkdown(document(paragraph({ attrs: { localId: 'a' }, type: 'hardBreak' }, { text: 'x', type: 'text' })))), ':hardBreak{localId=a}x\n') + assert.equal(markdown(adfToMarkdown(document(paragraph({ attrs: { localId: 'a' }, type: 'hardBreak' }, { text: 'x', type: 'text' })))), '!adf:hardBreak{localId=a}x\n') assert.equal(markdown(adfToMarkdown(document({ marks: [{ type: 'border' }], type: 'blockquote' }))), ':::blockquote {marks="[{\\"type\\":\\"border\\"}]"}\n:::\n') assert.equal(markdown(adfToMarkdown(document({ type: 'listItem' }))), ':::listItem\n:::\n') }) @@ -114,26 +114,26 @@ test('carries a link mark the link spelling cannot write', () => { const carried = (mark: AdfMark): string => markdown(adfToMarkdown(document(paragraph({ marks: [mark], text: 't', type: 'text' })))) assert.equal( carried({ attrs: { href: 'x', id: 'y' }, type: 'link' }), - ':adf{json="{\\"marks\\":[{\\"attrs\\":{\\"href\\":\\"x\\",\\"id\\":\\"y\\"},\\"type\\":\\"link\\"}],\\"text\\":\\"t\\",\\"type\\":\\"text\\"}"}\n', + '!adf:carry{json="{\\"marks\\":[{\\"attrs\\":{\\"href\\":\\"x\\",\\"id\\":\\"y\\"},\\"type\\":\\"link\\"}],\\"text\\":\\"t\\",\\"type\\":\\"text\\"}"}\n', ) - assert.equal(carried({ attrs: { href: 4 }, type: 'link' }), ':adf{json="{\\"marks\\":[{\\"attrs\\":{\\"href\\":4},\\"type\\":\\"link\\"}],\\"text\\":\\"t\\",\\"type\\":\\"text\\"}"}\n') - assert.equal(carried({ type: 'link' }), ':adf{json="{\\"marks\\":[{\\"type\\":\\"link\\"}],\\"text\\":\\"t\\",\\"type\\":\\"text\\"}"}\n') + assert.equal(carried({ attrs: { href: 4 }, type: 'link' }), '!adf:carry{json="{\\"marks\\":[{\\"attrs\\":{\\"href\\":4},\\"type\\":\\"link\\"}],\\"text\\":\\"t\\",\\"type\\":\\"text\\"}"}\n') + assert.equal(carried({ type: 'link' }), '!adf:carry{json="{\\"marks\\":[{\\"type\\":\\"link\\"}],\\"text\\":\\"t\\",\\"type\\":\\"text\\"}"}\n') }) test('carries a mark the canonical spellings cannot nest', () => { const carried = (...marks: AdfMark[]): string => markdown(adfToMarkdown(document(paragraph({ marks, text: 'x', type: 'text' })))) - assert.equal(carried({ type: 'annotation' }), ':adf{json="{\\"marks\\":[{\\"type\\":\\"annotation\\"}],\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n') + assert.equal(carried({ type: 'annotation' }), '!adf:carry{json="{\\"marks\\":[{\\"type\\":\\"annotation\\"}],\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n') assert.equal( carried({ type: 'code' }, { type: 'strong' }), - ':adf{json="{\\"marks\\":[{\\"type\\":\\"code\\"},{\\"type\\":\\"strong\\"}],\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n', + '!adf:carry{json="{\\"marks\\":[{\\"type\\":\\"code\\"},{\\"type\\":\\"strong\\"}],\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n', ) assert.equal( carried({ attrs: { colour: 'red' }, type: 'em' }), - ':adf{json="{\\"marks\\":[{\\"attrs\\":{\\"colour\\":\\"red\\"},\\"type\\":\\"em\\"}],\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n', + '!adf:carry{json="{\\"marks\\":[{\\"attrs\\":{\\"colour\\":\\"red\\"},\\"type\\":\\"em\\"}],\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n', ) assert.equal( carried({ attrs: { localId: 'x' }, type: 'code' }), - ':adf{json="{\\"marks\\":[{\\"attrs\\":{\\"localId\\":\\"x\\"},\\"type\\":\\"code\\"}],\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n', + '!adf:carry{json="{\\"marks\\":[{\\"attrs\\":{\\"localId\\":\\"x\\"},\\"type\\":\\"code\\"}],\\"text\\":\\"x\\",\\"type\\":\\"text\\"}"}\n', ) }) @@ -165,7 +165,7 @@ test('parts two adjacent lists of the same kind, the marker spelling being what test('carries a node type no section spells', () => { assert.equal(markdown(adfToMarkdown(document({ type: 'blockCard' }))), '```adf\n{\n "type": "blockCard"\n}\n```\n') assert.equal(markdown(adfToMarkdown(document({ type: 'toString' }))), '```adf\n{\n "type": "toString"\n}\n```\n') - assert.equal(markdown(adfToMarkdown(document(paragraph({ type: 'blockCard' })))), ':adf{json="{\\"type\\":\\"blockCard\\"}"}\n') + assert.equal(markdown(adfToMarkdown(document(paragraph({ type: 'blockCard' })))), '!adf:carry{json="{\\"type\\":\\"blockCard\\"}"}\n') assert.equal(markdown(adfToMarkdown(document({ text: 'x', type: 'text' }))), '```adf\n{\n "text": "x",\n "type": "text"\n}\n```\n') assert.equal(markdown(adfToMarkdown(document({ type: 'hardBreak' }))), '```adf\n{\n "type": "hardBreak"\n}\n```\n') }) @@ -179,7 +179,7 @@ test('breaks a mark run at the node it carries', () => { const carried: AdfNode = { marks: [strong], type: 'placeholder' } assert.equal( markdown(adfToMarkdown(document(paragraph({ marks: [strong], text: 'a', type: 'text' }, carried, { marks: [strong], text: 'b', type: 'text' })))), - '**a**:adf{json="{\\"marks\\":[{\\"type\\":\\"strong\\"}],\\"type\\":\\"placeholder\\"}"}**b**\n', + '**a**!adf:carry{json="{\\"marks\\":[{\\"type\\":\\"strong\\"}],\\"type\\":\\"placeholder\\"}"}**b**\n', ) }) @@ -225,7 +225,7 @@ test('spells an ordered list no marker fits as a directive', () => { test('carries a code mark over anything but text', () => { assert.equal( markdown(adfToMarkdown(document(paragraph({ marks: [{ type: 'code' }], type: 'hardBreak' })))), - ':adf{json="{\\"marks\\":[{\\"type\\":\\"code\\"}],\\"type\\":\\"hardBreak\\"}"}\n', + '!adf:carry{json="{\\"marks\\":[{\\"type\\":\\"code\\"}],\\"type\\":\\"hardBreak\\"}"}\n', ) }) @@ -257,7 +257,9 @@ test('escapes only text that would otherwise open a construct', () => { assert.equal(markdown(adfToMarkdown(document(later))), 'a\\\n\\ \\