35a - the parser reads the plain flavour and the lift goes #144
@@ -118,9 +118,9 @@ Plain markdown is a second flavour of the same grammar. `adfToPlainMarkdown` wri
|
|||||||
tools render — GitHub, GitLab, Obsidian and the like — keeping the content and dropping the rest:
|
tools render — GitHub, GitLab, Obsidian and the like — keeping the content and dropping the rest:
|
||||||
attributes, colours, layout, identity. It refuses only
|
attributes, colours, layout, identity. It refuses only
|
||||||
`not-an-adf-document`, `unsupported-document-version` and `unsupported-nesting-depth`, and writes
|
`not-an-adf-document`, `unsupported-document-version` and `unsupported-nesting-depth`, and writes
|
||||||
no directive. `plainMarkdownToAdf` reads what `markdownToAdf` reads, refusing what it refuses, and
|
no directive. `plainMarkdownToAdf` reads what `markdownToAdf` reads and refuses what it refuses,
|
||||||
the conventions below as nodes, taking other tools' spellings too; a backslash keeps a marker text:
|
and reads the conventions below as nodes, taking other tools' spellings too; a backslash keeps a
|
||||||
`\==x==`, `> \[!NOTE]`, `- \[x]`. Markdown
|
marker as text: `\==x==`, `> \[!NOTE]`, `- \[x]`. Markdown
|
||||||
`adfToPlainMarkdown` wrote reads back and writes again byte for byte; the document it came from
|
`adfToPlainMarkdown` wrote reads back and writes again byte for byte; the document it came from
|
||||||
does not come back. To edit a document and save it back, use `adfToMarkdown` and `markdownToAdf`:
|
does not come back. To edit a document and save it back, use `adfToMarkdown` and `markdownToAdf`:
|
||||||
saving what this pair read replaces mentions, attachments and macros with text.
|
saving what this pair read replaces mentions, attachments and macros with text.
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ type Paragraph = Extract<Block, { kind: 'paragraph' }>
|
|||||||
type Reading = { definitions: LinkDefinitions; flavour: Flavour; inExpand: boolean; memo: SpellingMemo }
|
type Reading = { definitions: LinkDefinitions; flavour: Flavour; inExpand: boolean; memo: SpellingMemo }
|
||||||
|
|
||||||
const documentStart: SourcePosition = { line: 1, offset: 0 }
|
const documentStart: SourcePosition = { line: 1, offset: 0 }
|
||||||
const imageBesideMarker = 'an image fits only as a paragraph of its own: this one shares its paragraph with a marker'
|
const imageAfterMarker = 'an image fits only as a paragraph of its own: this one continues the paragraph a marker opens, which a blank line before it ends'
|
||||||
|
const imageOnMarkerLine = 'an image fits only as a paragraph of its own: this one shares a line with a marker'
|
||||||
|
|
||||||
export function markdownToAdf(markdown: string): Result<AdfDocument, ParseError> {
|
export function markdownToAdf(markdown: string): Result<AdfDocument, ParseError> {
|
||||||
return readDocument(markdown, 'lossless')
|
return readDocument(markdown, 'lossless')
|
||||||
@@ -124,13 +125,13 @@ function quoteNode(blocks: readonly Block[], reading: Reading, path: ConvertErro
|
|||||||
if (led === undefined) return containerNode({ type: 'blockquote' }, blocks, reading, path, depth)
|
if (led === undefined) return containerNode({ type: 'blockquote' }, blocks, reading, path, depth)
|
||||||
const { folded, panelType } = led.marker
|
const { folded, panelType } = led.marker
|
||||||
const { line, rest } = markerLine(led.text)
|
const { line, rest } = markerLine(led.text)
|
||||||
if (!folded) return filledNode({ attrs: { panelType }, type: 'panel' }, readMarked(paragraphsOf(led.position, line, rest), body, reading, path, depth))
|
if (!folded) return filledNode({ attrs: { panelType }, type: 'panel' }, readMarked(paragraphsOf(led.position, line, rest), line !== '', body, reading, path, depth))
|
||||||
const title = parseInlineContent(line, reading.definitions, path, 'paragraph', 'lossless')
|
const title = parseInlineContent(line, reading.definitions, path, 'paragraph', 'lossless')
|
||||||
if (!title.ok) return title
|
if (!title.ok) return title
|
||||||
if (title.value.image !== undefined) return failure('unmappable-image', imageBesideMarker, path)
|
if (title.value.image !== undefined) return failure('unmappable-image', imageOnMarkerLine, path)
|
||||||
const text = title.value.nodes.map((node) => node.text ?? '').join('')
|
const text = title.value.nodes.map((node) => node.text ?? '').join('')
|
||||||
const type = reading.inExpand ? 'nestedExpand' : 'expand'
|
const type = reading.inExpand ? 'nestedExpand' : 'expand'
|
||||||
return filledNode(text === '' ? { type } : { attrs: { title: text }, type }, readMarked(paragraphsOf(led.position, rest), body, { ...reading, inExpand: true }, path, depth))
|
return filledNode(text === '' ? { type } : { attrs: { title: text }, type }, readMarked(paragraphsOf(led.position, rest), false, body, { ...reading, inExpand: true }, path, depth))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atlassian's schema requires a panel and an expand to hold a block.
|
// Atlassian's schema requires a panel and an expand to hold a block.
|
||||||
@@ -139,12 +140,13 @@ function filledNode(node: AdfNode, content: Result<AdfNode[]>): Result<AdfNode>
|
|||||||
return success({ ...node, content: content.value.length === 0 ? [{ type: 'paragraph' }] : content.value })
|
return success({ ...node, content: content.value.length === 0 ? [{ type: 'paragraph' }] : content.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
// A paragraph split off a marker still refuses the image it held beside it.
|
// A paragraph split off a marker still refuses the image it held beside it; `onMarkerLine` is whether the first one opens on the marker's line.
|
||||||
function readMarked(marked: readonly Paragraph[], others: readonly Block[], reading: Reading, path: ConvertErrorPath, depth: number): Result<AdfNode[]> {
|
function readMarked(marked: readonly Paragraph[], onMarkerLine: boolean, others: readonly Block[], reading: Reading, path: ConvertErrorPath, depth: number): Result<AdfNode[]> {
|
||||||
const read = readBlocks([...marked, ...others], reading, path, depth + 1)
|
const read = readBlocks([...marked, ...others], reading, path, depth + 1)
|
||||||
if (!read.ok) return read
|
if (!read.ok) return read
|
||||||
const image = read.value.slice(0, marked.length).findIndex((node) => node.type === 'mediaSingle')
|
const image = read.value.slice(0, marked.length).findIndex((node) => node.type === 'mediaSingle')
|
||||||
return image === -1 ? read : failure('unmappable-image', imageBesideMarker, [...path, 'content', image])
|
if (image === -1) return read
|
||||||
|
return failure('unmappable-image', image === 0 && onMarkerLine ? imageOnMarkerLine : imageAfterMarker, [...path, 'content', image])
|
||||||
}
|
}
|
||||||
|
|
||||||
// A task list trailing an item's blocks stands beside it, as ADF nests one.
|
// A task list trailing an item's blocks stands beside it, as ADF nests one.
|
||||||
@@ -157,7 +159,7 @@ function bulletNode(items: readonly Block[][], reading: Reading, path: ConvertEr
|
|||||||
}
|
}
|
||||||
const tasks: AdfNode[] = []
|
const tasks: AdfNode[] = []
|
||||||
for (const [index, { marker, others, position, text }] of led.entries()) {
|
for (const [index, { marker, others, position, text }] of led.entries()) {
|
||||||
const read = readMarked(paragraphsOf(position, text.replace(/^(?:[ \t\n]|\\\n)+/, '')), others, reading, [...path, 'content', index], depth)
|
const read = readMarked(paragraphsOf(position, text.replace(/^(?:[ \t\n]|\\\n)+/, '')), markerLine(text).line !== '', others, reading, [...path, 'content', index], depth)
|
||||||
if (!read.ok) return read
|
if (!read.ok) return read
|
||||||
let beside = read.value.length
|
let beside = read.value.length
|
||||||
while (read.value[beside - 1]?.type === 'taskList') beside -= 1
|
while (read.value[beside - 1]?.type === 'taskList') beside -= 1
|
||||||
|
|||||||
@@ -12,11 +12,16 @@
|
|||||||
and a list whose items all open `[x] ` as text. A highlighted `=` (today `=====`) and `a==b`
|
and a list whose items all open `[x] ` as text. A highlighted `=` (today `=====`) and `a==b`
|
||||||
(today `==a==b==`, highlighting `a` alone) come back highlighted whole, or lose the highlight
|
(today `==a==b==`, highlighting `a` alone) come back highlighted whole, or lose the highlight
|
||||||
where no spelling holds them; the plain pair's byte-for-byte property misses both, since the
|
where no spelling holds them; the plain pair's byte-for-byte property misses both, since the
|
||||||
wrong document re-spells to the same bytes. The reduction keeps only degrading what the flavour
|
wrong document re-spells to the same bytes. An expand titled `**x** [y](z)` comes back an
|
||||||
cannot spell.
|
expand, where today its escaped marker reads back as a quote. The reduction keeps only
|
||||||
|
degrading what the flavour cannot spell.
|
||||||
- **10f — Give task nodes read from plain markdown position ids.** Per `docs/decisions.md` §Plain
|
- **10f — Give task nodes read from plain markdown position ids.** Per `docs/decisions.md` §Plain
|
||||||
task ids come from position, README §Plain markdown's `localId` bullet saying so. The id spelling
|
task ids come from position, README §Plain markdown's `localId` bullet saying so. The id spelling
|
||||||
is part of the chunk.
|
is part of the chunk.
|
||||||
|
- **41 — Keep a link's target when `plainMarkdownToAdf` reads a callout title.** Per Goal 5 a link
|
||||||
|
target is content, but `> [!faq]- See [x](http://y)` reads to an expand titled `See x`, the
|
||||||
|
target gone. Where the target goes is a gap in `docs/decisions.md`; candidate: the title keeps
|
||||||
|
it as text, `See x (http://y)`.
|
||||||
- **40 — Make `markdownToAdf(adfToMarkdown(doc))` deep-equal `doc` for every document it takes.**
|
- **40 — Make `markdownToAdf(adfToMarkdown(doc))` deep-equal `doc` for every document it takes.**
|
||||||
Today it holds for editor-normal documents only: two adjacent text nodes with the same marks
|
Today it holds for editor-normal documents only: two adjacent text nodes with the same marks
|
||||||
merge, an empty `attrs`, `marks` or `content` drops, and `-0` reads back `0` — shapes pipelines
|
merge, an empty `attrs`, `marks` or `content` drops, and `-0` reads back `0` — shapes pipelines
|
||||||
|
|||||||
Reference in New Issue
Block a user