12b - The inline directive forms read and spell the !adf: prefix #84
+10
-7
@@ -87,8 +87,11 @@ info string, and `listBreak` for the leaf that parts two adjacent lists (Canonic
|
||||
**Claiming**: an unescaped `!adf:` claims wherever it stands. What follows picks the form: `/name`
|
||||
closes a container, and a name picks by what follows it in turn — a space or the line's end a block
|
||||
line, `[` or `{` an inline directive. A `!adf:` completing none of the three is a named error, and
|
||||
`\!adf:` is the literal, block and inline alike. A claimed block line also ends a lazy continuation:
|
||||
the blockquote or list item whose paragraph CommonMark would fold it into closes instead.
|
||||
`\!adf:` is the literal, block and inline alike. A construct CommonMark binds first — a code span,
|
||||
an autolink URI, a link destination or title — holds its `!adf:` as content; a directive's
|
||||
`[content]`, which binds ahead of bracket matching, does not. A claimed block line also ends a lazy
|
||||
continuation: the blockquote or list item whose paragraph CommonMark would fold it into closes
|
||||
instead.
|
||||
|
||||
**Inline**: `!adf:name[content]{attrs}`, on one line — an inline directive never spans lines.
|
||||
`[content]` is inline markdown; brackets inside balance as in CommonMark link text, `\]` for a
|
||||
@@ -416,11 +419,11 @@ Right.
|
||||
## Inline nodes
|
||||
|
||||
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 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,
|
||||
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 text node carrying neither marks, attributes nor content — 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).
|
||||
|
||||
@@ -3,11 +3,11 @@ import type { BlockDirective } from '../../adf/block-directives.ts'
|
||||
import type { ConvertFault } from '../../result.ts'
|
||||
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
|
||||
import type { Elsewhere } from './directive-attributes.ts'
|
||||
import { attributeNestingMessage, nodeAttrs, nodeMarks } from '../../adf/document.ts'
|
||||
import { attributeNestingMessage, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
|
||||
import { attributeValue, directiveLineEscape, directivePrefix, inlineDirectiveEscape, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
|
||||
import { blockArgument } from '../block-directive-arguments.ts'
|
||||
import { blockDirective } from '../../adf/block-directives.ts'
|
||||
import { carryName } from '../opaque-carry.ts'
|
||||
import { carryFence, carryName } from '../opaque-carry.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { inlineDirective } from '../../adf/inline-directives.ts'
|
||||
import { inlineMarkSpellingFault } from './directive-marks.ts'
|
||||
@@ -26,7 +26,7 @@ export function readBlockDirectiveNode(
|
||||
path: ConvertErrorPath,
|
||||
): Result<BlockDirectiveNode> {
|
||||
if (name === carryName) {
|
||||
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the fence`, path)
|
||||
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the ${carryFence} fence`, path)
|
||||
}
|
||||
const directive = blockDirective(name)
|
||||
if (directive === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name, directiveLineEscape), path)
|
||||
@@ -62,7 +62,7 @@ export function readInlineDirectiveNode(
|
||||
if (slot !== undefined && content !== undefined) {
|
||||
const text = slotText(content)
|
||||
if (text === undefined) {
|
||||
return failure('unsupported-node-shape', `the ${name} content slot holds one text node carrying neither marks nor attributes: this one holds something else`, path)
|
||||
return failure('unsupported-node-shape', `the ${name} content slot holds one text node carrying neither marks, attributes nor content: this one holds something else`, path)
|
||||
}
|
||||
const spans = slotLineEndingFault(name, text)
|
||||
if (spans !== undefined) return faulted(spans, path)
|
||||
@@ -91,7 +91,7 @@ function blockSpellingFault(name: string): ConvertFault | undefined {
|
||||
function slotText(content: readonly AdfNode[]): string | undefined {
|
||||
if (content.length === 0) return ''
|
||||
const only = content.length === 1 ? content[0] : undefined
|
||||
if (only?.type !== 'text' || nodeMarks(only).length > 0 || Object.keys(nodeAttrs(only)).length > 0 || typeof only.text !== 'string') return undefined
|
||||
if (only?.type !== 'text' || nodeMarks(only).length > 0 || Object.keys(nodeAttrs(only)).length > 0 || nodeContent(only).length > 0 || typeof only.text !== 'string') return undefined
|
||||
return only.text
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ test('names the position a directive name the other one spells belongs to', () =
|
||||
})
|
||||
|
||||
test('names the reserved carry name a block directive spells', () => {
|
||||
const reserved = 'malformed-directive: the name carry is reserved for the opaque carry, whose block form is the fence'
|
||||
const reserved = 'malformed-directive: the name carry is reserved for the opaque carry, whose block form is the adf fence'
|
||||
assert.equal(content(markdownToAdf('::carry\n')), reserved)
|
||||
assert.equal(content(markdownToAdf(':::carry\nx\n:::\n')), reserved)
|
||||
assert.deepEqual(content(markdownToAdf('```adfx\nx\n```\n')), [{ attrs: { language: 'adfx' }, content: [text('x')], type: 'codeBlock' }])
|
||||
@@ -876,9 +876,10 @@ test('reads the content slot as the text attribute the node spells there', () =>
|
||||
})
|
||||
|
||||
test('names the content slot no lone plain text node reads back from', () => {
|
||||
const named = 'unsupported-node-shape: the status content slot holds one text node carrying neither marks nor attributes: this one holds something else'
|
||||
const named = 'unsupported-node-shape: the status content slot holds one text node carrying neither marks, attributes nor content: this one holds something else'
|
||||
assert.equal(content(markdownToAdf('!adf:status[**A**]{color=yellow}\n')), named)
|
||||
assert.equal(content(markdownToAdf('!adf:status[!adf:carry{json="{\\"attrs\\":{\\"localId\\":\\"a\\"},\\"text\\":\\"A\\",\\"type\\":\\"text\\"}"}]{color=yellow}\n')), named)
|
||||
assert.equal(content(markdownToAdf('!adf:status[!adf:carry{json="{\\"content\\":[{\\"text\\":\\"B\\",\\"type\\":\\"text\\"}],\\"text\\":\\"A\\",\\"type\\":\\"text\\"}"}]{color=yellow}\n')), named)
|
||||
assert.equal(code(markdownToAdf('!adf:status[a`b`]{color=yellow}\n')), 'unsupported-node-shape')
|
||||
assert.equal(code(markdownToAdf('!adf:status[!adf:date{timestamp=1}]{color=yellow}\n')), 'unsupported-node-shape')
|
||||
assert.equal(content(markdownToAdf('!adf:status[]{color=yellow}\n')), 'unmappable-image: an image fits only as a paragraph of its own: this one sits inside other content')
|
||||
|
||||
Reference in New Issue
Block a user