Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc11daa540 | |||
| 7b814e02dc | |||
| 5e55ce2a8e | |||
| cf9a737e71 |
@@ -49,7 +49,7 @@ function nodeLeaves(node: AdfNode, container: LineContainer, path: ConvertErrorP
|
|||||||
if (node.type === 'placeholder') return success([])
|
if (node.type === 'placeholder') return success([])
|
||||||
if (node.type === 'mention') return success(textLeaves(nonEmpty(attrs['text']) ?? idMention(attrs['id']), marks, container))
|
if (node.type === 'mention') return success(textLeaves(nonEmpty(attrs['text']) ?? idMention(attrs['id']), marks, container))
|
||||||
if (node.type === 'status') return success(textLeaves(attrs['text'], marks, container))
|
if (node.type === 'status') return success(textLeaves(attrs['text'], marks, container))
|
||||||
if (['extension', 'inlineExtension'].includes(node.type)) return success(textLeaves(nonEmpty(attrs['text']), marks, container, nonEmpty(attrs['extensionKey']) ?? 'extension'))
|
if (['extension', 'inlineExtension'].includes(node.type)) return success(textLeaves(nonEmpty(attrs['text']), marks, container, noteName(attrs['extensionKey']) ?? 'extension'))
|
||||||
if (node.type === 'syncBlock') return success(noteLeaves('synced block'))
|
if (node.type === 'syncBlock') return success(noteLeaves('synced block'))
|
||||||
if (['media', 'mediaInline'].includes(node.type)) return success(mediaLeaves(attrs, marks, container))
|
if (['media', 'mediaInline'].includes(node.type)) return success(mediaLeaves(attrs, marks, container))
|
||||||
if (['blockCard', 'embedCard', 'inlineCard'].includes(node.type)) return success(cardLeaves(attrs, marks, container))
|
if (['blockCard', 'embedCard', 'inlineCard'].includes(node.type)) return success(cardLeaves(attrs, marks, container))
|
||||||
@@ -87,7 +87,15 @@ function mediaLeaves(attrs: Readonly<AdfAttributes>, marks: readonly AdfMark[],
|
|||||||
}
|
}
|
||||||
|
|
||||||
function noteLeaves(name: string): AdfNode[] {
|
function noteLeaves(name: string): AdfNode[] {
|
||||||
return [textLeaf(`(${name.replace(/[\r\u0000]/g, '').replace(/\n/g, ' ')} not included)`, [{ type: 'em' }])]
|
return [textLeaf(`(${name} not included)`, [{ type: 'em' }])]
|
||||||
|
}
|
||||||
|
|
||||||
|
function noteName(value: unknown): string | undefined {
|
||||||
|
return typeof value === 'string' ? nonEmpty(oneLine(value).trim()) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function oneLine(text: string): string {
|
||||||
|
return text.replace(/[\r\u0000]/g, '').replace(/\n/g, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
function nonEmpty(value: unknown): string | undefined {
|
function nonEmpty(value: unknown): string | undefined {
|
||||||
|
|||||||
@@ -166,6 +166,12 @@ test('keeps the CommonMark blocks in their spelling and drops their attributes a
|
|||||||
assert.equal(plain(node('orderedList', { order: -1 }, item(said('a')))), '1. a\n')
|
assert.equal(plain(node('orderedList', { order: -1 }, item(said('a')))), '1. a\n')
|
||||||
const code: AdfNode = { content: [text('x')], type: 'codeBlock' }
|
const code: AdfNode = { content: [text('x')], type: 'codeBlock' }
|
||||||
assert.equal(plain(node('orderedList', { order: 1e10 }, item(said('Alpha')), item(code), item())), '- 10000000000. Alpha\n- 10000000001.\n\n ```\n x\n ```\n- 10000000002.\n')
|
assert.equal(plain(node('orderedList', { order: 1e10 }, item(said('Alpha')), item(code), item())), '- 10000000000. Alpha\n- 10000000001.\n\n ```\n x\n ```\n- 10000000002.\n')
|
||||||
|
const long = node('orderedList', { order: 1e10 }, item(said('y')))
|
||||||
|
assert.equal(plain(bulletList(item(said('x'))), long, bulletList(item(said('z')))), '- x\n- 10000000000. y\n- z\n')
|
||||||
|
assert.equal(plain(node('taskList', {}, node('taskItem', { state: 'DONE' }, text('t'))), long), '- [x] t\n- 10000000000. y\n')
|
||||||
|
assert.equal(plain(bulletList(item(said('a'), bulletList(item(said('x'))), long))), '- a\n - x\n - 10000000000. y\n')
|
||||||
|
const givesWay = node('orderedList', { order: 1e10 }, item(node('rule', {})), item(bulletList(item(bulletList(item())))))
|
||||||
|
assert.equal(plain(givesWay, bulletList(item(said('z')))), '- 10000000000.\n- 10000000001.\n - -\n- z\n')
|
||||||
assert.equal(plain(node('orderedList', { order: 999999999 }, item(said('a'))), node('orderedList', { order: 5 }, item(said('b')))), '- 999999999\\. a\n- 1000000000. b\n')
|
assert.equal(plain(node('orderedList', { order: 999999999 }, item(said('a'))), node('orderedList', { order: 5 }, item(said('b')))), '- 999999999\\. a\n- 1000000000. b\n')
|
||||||
assert.equal(plain(node('heading', { level: 7 }, text('h'))), 'h\n')
|
assert.equal(plain(node('heading', { level: 7 }, text('h'))), 'h\n')
|
||||||
})
|
})
|
||||||
@@ -209,6 +215,7 @@ test('spells an extension as its text attribute, else a note naming it, and a pl
|
|||||||
assert.equal(plain(node('extension', { extensionKey: 'toc', text: 'Contents' }), node('extension', { extensionKey: 'jira-issues-table' })), 'Contents\n\n_(jira-issues-table not included)_\n')
|
assert.equal(plain(node('extension', { extensionKey: 'toc', text: 'Contents' }), node('extension', { extensionKey: 'jira-issues-table' })), 'Contents\n\n_(jira-issues-table not included)_\n')
|
||||||
assert.equal(plain(node('syncBlock', { resourceId: 'r' })), '_(synced block not included)_\n')
|
assert.equal(plain(node('syncBlock', { resourceId: 'r' })), '_(synced block not included)_\n')
|
||||||
assert.equal(plain(node('extension', { extensionKey: 'jira\r\nissues\u0000' })), '_(jira issues not included)_\n')
|
assert.equal(plain(node('extension', { extensionKey: 'jira\r\nissues\u0000' })), '_(jira issues not included)_\n')
|
||||||
|
assert.equal(plain(node('extension', { extensionKey: '\r\u0000' }), node('extension', { extensionKey: '\n' })), '_(extension not included)_\n\n_(extension not included)_\n')
|
||||||
assert.equal(plain(paragraph(text('a '), node('inlineExtension', { text: 'macro' }), text(' '), node('inlineExtension', {}), node('placeholder', { text: 'Type here' }))), 'a macro _(extension not included)_\n')
|
assert.equal(plain(paragraph(text('a '), node('inlineExtension', { text: 'macro' }), text(' '), node('inlineExtension', {}), node('placeholder', { text: 'Type here' }))), 'a macro _(extension not included)_\n')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { adfDocumentFault, nodeAttrs, nodeContent } from '../../adf/document.ts'
|
|||||||
import { blockNodeModel } from '../../adf/block-nodes.ts'
|
import { blockNodeModel } from '../../adf/block-nodes.ts'
|
||||||
import { commonMarkSpelling, largestListMarker, type SpellingMemo } from './adf-to-markdown.ts'
|
import { commonMarkSpelling, largestListMarker, type SpellingMemo } from './adf-to-markdown.ts'
|
||||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||||
import { inlineLeaves, isBlockNodeType, reduceInline, writableHref } from './plain-inline.ts'
|
import { inlineLeaves, isBlockNodeType, oneLine, reduceInline, writableHref } from './plain-inline.ts'
|
||||||
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
|
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||||
import { languageSlot } from '../code-language.ts'
|
import { languageSlot } from '../code-language.ts'
|
||||||
import { largestNesting } from '../../nesting.ts'
|
import { largestNesting } from '../../nesting.ts'
|
||||||
@@ -114,16 +114,20 @@ function plainSequence(blocks: readonly AdfNode[], reduction: Reduction): Result
|
|||||||
const listed = sequence[index]
|
const listed = sequence[index]
|
||||||
if (listed === undefined || (listed.type !== 'bulletList' && listed.type !== 'orderedList')) continue
|
if (listed === undefined || (listed.type !== 'bulletList' && listed.type !== 'orderedList')) continue
|
||||||
const block = numberedPastMarkers(listed)
|
const block = numberedPastMarkers(listed)
|
||||||
sequence[index] = block
|
const spelled = block === listed && commonMarkSpelling(block, reduction.path, reduction.depth, reduction.memo)?.ok === true
|
||||||
if (commonMarkSpelling(block, reduction.path, reduction.depth, reduction.memo)?.ok === true) continue
|
if (spelled) continue
|
||||||
const held = nodeContent(block).flatMap(nodeContent)
|
sequence = spliced(sequence, index, block === listed ? nodeContent(block).flatMap(nodeContent) : [block])
|
||||||
const from = Math.max(0, index - 1)
|
index = Math.max(0, index - 1) - 1
|
||||||
sequence = [...sequence.slice(0, from), ...mergedLists([...sequence.slice(from, index), ...held, ...sequence.slice(index + 1, index + 2)]), ...sequence.slice(index + 2)]
|
|
||||||
index = from - 1
|
|
||||||
}
|
}
|
||||||
return success(sequence)
|
return success(sequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The replacement merges with the lists beside it, so no two lists of one type stand adjacent.
|
||||||
|
function spliced(sequence: readonly AdfNode[], index: number, replacement: readonly AdfNode[]): AdfNode[] {
|
||||||
|
const from = Math.max(0, index - 1)
|
||||||
|
return [...sequence.slice(0, from), ...mergedLists([...sequence.slice(from, index), ...replacement, ...sequence.slice(index + 1, index + 2)]), ...sequence.slice(index + 2)]
|
||||||
|
}
|
||||||
|
|
||||||
// A numbered list whose markers run past CommonMark's keeps its numbers as text in a bullet list.
|
// A numbered list whose markers run past CommonMark's keeps its numbers as text in a bullet list.
|
||||||
function numberedPastMarkers(list: AdfNode): AdfNode {
|
function numberedPastMarkers(list: AdfNode): AdfNode {
|
||||||
const order = nodeAttrs(list)['order']
|
const order = nodeAttrs(list)['order']
|
||||||
@@ -319,7 +323,7 @@ function reduceMedia(media: AdfNode, reduction: Reduction): Result<AdfNode[]> {
|
|||||||
const url = attrs['url']
|
const url = attrs['url']
|
||||||
if (attrs['type'] !== 'external' || typeof url !== 'string') return paragraphOfNode(media, reduction)
|
if (attrs['type'] !== 'external' || typeof url !== 'string') return paragraphOfNode(media, reduction)
|
||||||
const held = attrs['alt']
|
const held = attrs['alt']
|
||||||
const alt = typeof held === 'string' ? held.replace(/[\r\u0000]/g, '').replace(/\n/g, ' ').trim() : ''
|
const alt = typeof held === 'string' ? oneLine(held).trim() : ''
|
||||||
const external: AdfNode = { attrs: alt === '' ? { type: 'external', url: writableHref(url) } : { alt, type: 'external', url: writableHref(url) }, type: 'media' }
|
const external: AdfNode = { attrs: alt === '' ? { type: 'external', url: writableHref(url) } : { alt, type: 'external', url: writableHref(url) }, type: 'media' }
|
||||||
const image: AdfNode = { attrs: { layout: 'center' }, content: [external], type: 'mediaSingle' }
|
const image: AdfNode = { attrs: { layout: 'center' }, content: [external], type: 'mediaSingle' }
|
||||||
return success([image])
|
return success([image])
|
||||||
|
|||||||
@@ -254,8 +254,8 @@ chunk clearing a §11 seam.
|
|||||||
- [ ] **10b — The lift.** `plainMarkdownToAdf`'s ADF→ADF lift, tests first, a test per row it reads,
|
- [ ] **10b — The lift.** `plainMarkdownToAdf`'s ADF→ADF lift, tests first, a test per row it reads,
|
||||||
other tools' spellings included; the editor's default highlight colour looked up and cited.
|
other tools' spellings included; the editor's default highlight colour looked up and cited.
|
||||||
- [ ] **10c — The exports.** `adfToPlainMarkdown` and `plainMarkdownToAdf` exported with their README
|
- [ ] **10c — The exports.** `adfToPlainMarkdown` and `plainMarkdownToAdf` exported with their README
|
||||||
sections, and two properties over 4.2's generators: writing refuses only the guard's codes,
|
sections, and two properties over 4.2's generators: writing refuses only the guard's codes
|
||||||
and markdown `adfToPlainMarkdown` wrote reads back through `plainMarkdownToAdf` and writes
|
and writes no `!adf:`, and markdown `adfToPlainMarkdown` wrote reads back through `plainMarkdownToAdf` and writes
|
||||||
again byte for byte. AGENTS.md §1 records the pair as composed around the lossless one.
|
again byte for byte. AGENTS.md §1 records the pair as composed around the lossless one.
|
||||||
- [x] **11 — Atlassian's ADF schema as the tables' truth.**
|
- [x] **11 — Atlassian's ADF schema as the tables' truth.**
|
||||||
- [x] **11a — The vendored schema.**
|
- [x] **11a — The vendored schema.**
|
||||||
|
|||||||
Reference in New Issue
Block a user