Gather the plain flavour's code in markdown/plain/
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
|
||||
import type { Flavour } from '../plain-conventions.ts'
|
||||
import type { Flavour } from '../plain/conventions.ts'
|
||||
import { adfDocumentFault, carriesOnly, isPlainText, nodeAttrs, nodeContent } from '../../adf/document.ts'
|
||||
import { alertMarker, foldedAlertMarker, leadingMarker, readAlertMarker, readTaskMarker, taskMarker } from '../plain-conventions.ts'
|
||||
import { alertMarker, foldedAlertMarker, leadingMarker, readAlertMarker, readTaskMarker, taskMarker } from '../plain/conventions.ts'
|
||||
import { blockDirectiveForm, documentSpelling, listBreakSpelling } from '../block-directive.ts'
|
||||
import { blockNodeModel, blockNodes } from '../../adf/block-nodes.ts'
|
||||
import { carriedBlock } from '../opaque-carry.ts'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import type { Flavour } from '../plain-conventions.ts'
|
||||
import type { Flavour } from '../plain/conventions.ts'
|
||||
import type { InlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||
import type { LineContainer } from '../line-container.ts'
|
||||
import { assembleInlineLine, isSyntax, type InlineEscaping, type InlineSegment, type MarkRun, type NodeRange } from './line-escaping.ts'
|
||||
@@ -9,7 +9,7 @@ import { commonMarkLink, linkHref, markSpelling, spellMarkAttributes } from '../
|
||||
import { identicalMark, isPlainText, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
|
||||
import { escapeUnbalanced, spellDestination } from '../commonmark/link-syntax.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { highlightDelimiter } from '../plain-conventions.ts'
|
||||
import { highlightDelimiter } from '../plain/conventions.ts'
|
||||
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { longestBacktickRun } from '../commonmark/backtick-runs.ts'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Flavour } from '../plain-conventions.ts'
|
||||
import type { Flavour } from '../plain/conventions.ts'
|
||||
import type { LineContainer } from '../line-container.ts'
|
||||
import { backslashEscape, escapesLineClaim, inlineHtmlConstruct, opensBracketedAutolink, opensEmailAutolink, type LinePosition } from '../commonmark/grammar.ts'
|
||||
import { backtickRun, closingBacktickRun } from '../commonmark/backtick-runs.ts'
|
||||
import { claimsDirectivePrefix } from '../directive-syntax.ts'
|
||||
import { delimiterFlags, isWordCharacter, matchEmphasis, runLength } from '../commonmark/emphasis-matching.ts'
|
||||
import { highlightDelimiter, highlightFlanking } from '../plain-conventions.ts'
|
||||
import { highlightDelimiter, highlightFlanking } from '../plain/conventions.ts'
|
||||
import { isBareDelimiterRow } from '../pipe-table-syntax.ts'
|
||||
import { opensLinkDefinition } from '../commonmark/link-reference-definitions.ts'
|
||||
import { readEntityReference } from '../commonmark/entity-references.ts'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AdfNode } from '../../adf/document.ts'
|
||||
import type { ConvertErrorPath } from '../../result.ts'
|
||||
import type { Flavour } from '../plain-conventions.ts'
|
||||
import type { Flavour } from '../plain/conventions.ts'
|
||||
import { carriesOnly, nodeContent } from '../../adf/document.ts'
|
||||
import { spellPipeDelimiter, spellPipeRow } from '../pipe-table-syntax.ts'
|
||||
import { tryPipeCell } from './inline-line.ts'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import type { DirectiveSpan, NestedSpans } from '../directive-syntax.ts'
|
||||
import type { EmphasisPairing } from '../commonmark/emphasis-matching.ts'
|
||||
import type { Flavour } from '../plain-conventions.ts'
|
||||
import type { Flavour } from '../plain/conventions.ts'
|
||||
import type { LineContainer } from '../line-container.ts'
|
||||
import type { LinkDefinition } from '../commonmark/link-syntax.ts'
|
||||
import { backslashEscape, decodeTextEscapes, inlineHtmlConstruct, readBracketedAutolink, readEmailAutolink, trimTrailingSpace } from '../commonmark/grammar.ts'
|
||||
@@ -9,7 +9,7 @@ import { backtickRun, closingBacktickRun } from '../commonmark/backtick-runs.ts'
|
||||
import { commonMarkLink, linkHref } from '../mark-spellings.ts'
|
||||
import { delimiterFlags, matchEmphasis, runLength } from '../commonmark/emphasis-matching.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { highlightDelimiter, highlightFlanking } from '../plain-conventions.ts'
|
||||
import { highlightDelimiter, highlightFlanking } from '../plain/conventions.ts'
|
||||
import { identicalMarks, mergeAdjacentText, nodeAttrs, nodeMarks } from '../../adf/document.ts'
|
||||
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||
import { noSpans, readInlineDirective } from '../directive-syntax.ts'
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||
import type { Block, DirectiveBlock } from './blocks.ts'
|
||||
import type { BlockDirectiveNode } from './directive-nodes.ts'
|
||||
import type { ConvertFault } from '../../result.ts'
|
||||
import type { Flavour } from '../plain-conventions.ts'
|
||||
import type { Flavour } from '../plain/conventions.ts'
|
||||
import type { LineContainer } from '../line-container.ts'
|
||||
import type { LinkDefinitions } from './inline-content.ts'
|
||||
import { carryFenceType, readCarriedBlock } from '../opaque-carry.ts'
|
||||
@@ -10,11 +10,11 @@ import { commonMarkSpelling, type SpellingMemo } from '../emit/adf-to-markdown.t
|
||||
import { documentAttribute, documentName, documentSpelling, listBreakName, listBreakSpelling } from '../block-directive.ts'
|
||||
import { emptyKeys, nodeAttrs, nodeContent } from '../../adf/document.ts'
|
||||
import { failure, faulted, positioned, success, type ConvertErrorPath, type ParseError, type Result, type SourcePosition } from '../../result.ts'
|
||||
import { inlineLeaves } from '../emit/plain-inline.ts'
|
||||
import { inlineLeaves } from '../plain/inline-reduction.ts'
|
||||
import { languageSlot } from '../code-language.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { leadingMarker, readAlertMarker, readTaskMarker } from '../plain-conventions.ts'
|
||||
import { mintTaskIds } from './task-ids.ts'
|
||||
import { leadingMarker, readAlertMarker, readTaskMarker } from '../plain/conventions.ts'
|
||||
import { mintTaskIds } from '../plain/task-ids.ts'
|
||||
import { parseBlocks } from './blocks.ts'
|
||||
import { parseInlineContent } from './inline-content.ts'
|
||||
import { readBlockDirectiveNode } from './directive-nodes.ts'
|
||||
|
||||
@@ -3,7 +3,7 @@ import test from 'node:test'
|
||||
|
||||
import type { AdfAttributes, AdfDocument, AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import { adfToMarkdown } from '../emit/adf-to-markdown.ts'
|
||||
import { adfToPlainMarkdown } from '../emit/plain-reduction.ts'
|
||||
import { adfToPlainMarkdown } from '../plain/adf-to-plain-markdown.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { markdownToAdf, plainMarkdownToAdf } from './markdown-to-adf.ts'
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import type { AdfAttributes, AdfDocument, AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import { adfToPlainMarkdown, reduceToPlain } from './plain-reduction.ts'
|
||||
import { adfToPlainMarkdown, reduceToPlain } from './adf-to-plain-markdown.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
|
||||
const code: AdfMark = { type: 'code' }
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||
import { adfDocumentFault, nodeAttrs, nodeContent } from '../../adf/document.ts'
|
||||
import { commonMarkSpelling, largestListMarker, writeMarkdown, type SpellingMemo } from './adf-to-markdown.ts'
|
||||
import { commonMarkSpelling, largestListMarker, writeMarkdown, type SpellingMemo } from '../emit/adf-to-markdown.ts'
|
||||
import { blockNodeModel } from '../../adf/block-nodes.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { inlineLeaves, isBlockNodeType, oneLine, reduceInline, writableHref } from './plain-inline.ts'
|
||||
import { inlineLeaves, isBlockNodeType, oneLine, reduceInline, writableHref } from './inline-reduction.ts'
|
||||
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||
import { languageSlot } from '../code-language.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { taskMarker } from '../plain-conventions.ts'
|
||||
import { toEditorNormal } from '../../adf/editor-normal.ts'
|
||||
import { taskMarker } from './conventions.ts'
|
||||
import { toEditorNormal } from './editor-normal.ts'
|
||||
|
||||
// depth: the level the node reduced stands at, counted as the emitter counts it.
|
||||
type Reduction = { depth: number; memo: SpellingMemo; path: ConvertErrorPath }
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isWordCharacter } from './commonmark/emphasis-matching.ts'
|
||||
import { isWordCharacter } from '../commonmark/emphasis-matching.ts'
|
||||
|
||||
export type Flavour = 'lossless' | 'plain'
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import type { AdfNode } from '../../adf/document.ts'
|
||||
import type { JsonValue } from '../../json-value.ts'
|
||||
import { toEditorNormal } from './editor-normal.ts'
|
||||
|
||||
test('merges adjacent text nodes carrying identical marks, at every level', () => {
|
||||
const content: AdfNode[] = [
|
||||
{ text: 'a', type: 'text' },
|
||||
{ text: 'b', type: 'text' },
|
||||
{ marks: [{ type: 'strong' }], text: 'c', type: 'text' },
|
||||
{ marks: [{ attrs: {}, type: 'strong' }], text: 'd', type: 'text' },
|
||||
{ type: 'hardBreak' },
|
||||
{ text: 'e', type: 'text' },
|
||||
{ attrs: { localId: '01a0a06b-5281-7f27-9022-8d3a74b0ab0d' }, text: 'f', type: 'text' },
|
||||
{ text: 'g', type: 'text' },
|
||||
{ attrs: {}, text: 'h', type: 'text' },
|
||||
]
|
||||
assert.deepEqual(toEditorNormal({ content: [{ attrs: { panelType: 'info' }, content: [{ content, type: 'paragraph' }], type: 'panel' }], type: 'doc', version: 1 }), {
|
||||
content: [
|
||||
{
|
||||
attrs: { panelType: 'info' },
|
||||
content: [
|
||||
{
|
||||
content: [
|
||||
{ text: 'ab', type: 'text' },
|
||||
{ marks: [{ type: 'strong' }], text: 'cd', type: 'text' },
|
||||
{ type: 'hardBreak' },
|
||||
{ text: 'e', type: 'text' },
|
||||
{ attrs: { localId: '01a0a06b-5281-7f27-9022-8d3a74b0ab0d' }, text: 'f', type: 'text' },
|
||||
{ text: 'gh', type: 'text' },
|
||||
],
|
||||
type: 'paragraph',
|
||||
},
|
||||
],
|
||||
type: 'panel',
|
||||
},
|
||||
],
|
||||
type: 'doc',
|
||||
version: 1,
|
||||
})
|
||||
})
|
||||
|
||||
test('reads negative zero as zero, as JSON does', () => {
|
||||
const marks = [{ attrs: { size: -0 }, type: 'border' }]
|
||||
assert.deepEqual(
|
||||
toEditorNormal({ content: [{ attrs: { a: -0, b: [{ c: -0 }, null, 'd'] }, content: [{ marks, text: 'x', type: 'text' }], type: 'paragraph' }], type: 'doc', version: -0 }),
|
||||
{ content: [{ attrs: { a: 0, b: [{ c: 0 }, null, 'd'] }, content: [{ marks: [{ attrs: { size: 0 }, type: 'border' }], text: 'x', type: 'text' }], type: 'paragraph' }], type: 'doc', version: 0 },
|
||||
)
|
||||
})
|
||||
|
||||
test('reads an empty attrs object, marks array or content array as the absent key', () => {
|
||||
const paragraph: AdfNode = { attrs: {}, content: [{ attrs: {}, marks: [], text: 'a', type: 'text' }, { marks: [{ attrs: {}, type: 'em' }], text: 'b', type: 'text' }], marks: [], type: 'paragraph' }
|
||||
assert.deepEqual(toEditorNormal({ content: [paragraph, { content: [], type: 'rule' }], type: 'doc', version: 1 }), {
|
||||
content: [{ content: [{ text: 'a', type: 'text' }, { marks: [{ type: 'em' }], text: 'b', type: 'text' }], type: 'paragraph' }, { type: 'rule' }],
|
||||
type: 'doc',
|
||||
version: 1,
|
||||
})
|
||||
assert.deepEqual(toEditorNormal({ content: [], type: 'doc', version: 1 }), { type: 'doc', version: 1 })
|
||||
})
|
||||
|
||||
test('normalizes blocks and mark attributes nesting far past the levels a recursive walk survives', () => {
|
||||
const levels = 100000
|
||||
let node: AdfNode = { content: [], type: 'paragraph' }
|
||||
for (let level = 0; level < levels; level += 1) node = { content: [node], type: 'blockquote' }
|
||||
let normal = toEditorNormal({ content: [node], type: 'doc', version: 1 }).content?.[0]
|
||||
let depth = 0
|
||||
for (; normal?.content !== undefined; depth += 1) normal = normal.content[0]
|
||||
assert.equal(depth, levels)
|
||||
assert.deepEqual(normal, { type: 'paragraph' })
|
||||
let deep: JsonValue = 1
|
||||
for (let level = 0; level < 2 * levels; level += 1) deep = [deep]
|
||||
const marks = [{ attrs: { deep }, type: 'textColor' }]
|
||||
const merged = toEditorNormal({ content: [{ content: [{ marks, text: 'a', type: 'text' }, { marks, text: 'b', type: 'text' }], type: 'paragraph' }], type: 'doc', version: 1 })
|
||||
assert.deepEqual(merged.content?.[0]?.content?.map((text) => text.text), ['ab'])
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { AdfAttributes, AdfDocument, AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import type { JsonValue } from '../../json-value.ts'
|
||||
import { identicalMark, identicalMarks, mergeAdjacentText, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
|
||||
|
||||
type JsonContainer = JsonValue[] | { [key: string]: JsonValue }
|
||||
|
||||
type NodeHolder = { content?: AdfNode[] }
|
||||
|
||||
export function sameMark(left: AdfMark, right: AdfMark): boolean {
|
||||
return identicalMark(normalMark(left), normalMark(right))
|
||||
}
|
||||
|
||||
export function joinsNormally(previous: AdfNode, node: AdfNode): boolean {
|
||||
if (!mergesText(previous) || !mergesText(node)) return false
|
||||
return identicalMarks(nodeMarks(previous).map(normalMark), nodeMarks(node).map(normalMark))
|
||||
}
|
||||
|
||||
export function toEditorNormal(document: AdfDocument): AdfDocument {
|
||||
const normal: AdfDocument = { type: document.type, version: Object.is(document.version, -0) ? 0 : document.version }
|
||||
const pending: { holder: NodeHolder; source: NodeHolder }[] = [{ holder: normal, source: document }]
|
||||
for (let entry = pending.pop(); entry !== undefined; entry = pending.pop()) {
|
||||
const content = mergeAdjacentText(nodeContent(entry.source), joinsNormally)
|
||||
if (content.length === 0) continue
|
||||
entry.holder.content = content.map((source) => {
|
||||
const holder = normalNode(source)
|
||||
pending.push({ holder, source })
|
||||
return holder
|
||||
})
|
||||
}
|
||||
return normal
|
||||
}
|
||||
|
||||
function normalNode(node: AdfNode): AdfNode {
|
||||
const normal: AdfNode = { type: node.type }
|
||||
const attrs = normalAttributes(nodeAttrs(node))
|
||||
if (attrs !== undefined) normal.attrs = attrs
|
||||
const marks = nodeMarks(node).map(normalMark)
|
||||
if (marks.length > 0) normal.marks = marks
|
||||
if (node.text !== undefined) normal.text = node.text
|
||||
return normal
|
||||
}
|
||||
|
||||
function normalMark(mark: AdfMark): AdfMark {
|
||||
const attrs = normalAttributes(nodeAttrs(mark))
|
||||
return attrs === undefined ? { type: mark.type } : { attrs, type: mark.type }
|
||||
}
|
||||
|
||||
function normalAttributes(attrs: AdfAttributes): AdfAttributes | undefined {
|
||||
if (Object.keys(attrs).length === 0) return undefined
|
||||
const normal = { ...attrs }
|
||||
const pending: JsonContainer[] = [normal]
|
||||
for (let held = pending.pop(); held !== undefined; held = pending.pop()) {
|
||||
if (Array.isArray(held)) for (const [index, value] of held.entries()) held[index] = normalValue(value, pending)
|
||||
else for (const [key, value] of Object.entries(held)) held[key] = normalValue(value, pending)
|
||||
}
|
||||
return normal
|
||||
}
|
||||
|
||||
function normalValue(value: JsonValue, pending: JsonContainer[]): JsonValue {
|
||||
if (Object.is(value, -0)) return 0
|
||||
if (value === null || typeof value !== 'object') return value
|
||||
const copy = Array.isArray(value) ? [...value] : { ...value }
|
||||
pending.push(copy)
|
||||
return copy
|
||||
}
|
||||
|
||||
function mergesText(node: AdfNode): boolean {
|
||||
return node.type === 'text' && Object.keys(nodeAttrs(node)).length === 0
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { AdfAttributes, AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import type { LineContainer } from '../line-container.ts'
|
||||
import type { MarkRun } from './line-escaping.ts'
|
||||
import type { MarkRun } from '../emit/line-escaping.ts'
|
||||
import { blockNodeModel } from '../../adf/block-nodes.ts'
|
||||
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { joinsNormally, sameMark } from '../../adf/editor-normal.ts'
|
||||
import { joinsNormally, sameMark } from './editor-normal.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { mergeAdjacentText, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
|
||||
import { plainLineFallback, type PlainLineFallback } from './inline-line.ts'
|
||||
import { plainLineFallback, type PlainLineFallback } from '../emit/inline-line.ts'
|
||||
import { spellDestination, spellLinkTarget } from '../commonmark/link-syntax.ts'
|
||||
|
||||
const highlight = 'backgroundColor'
|
||||
Reference in New Issue
Block a user