Hold the editor's join rule in plain/editor-normal.ts as the reader's over editor-normal forms
This commit is contained in:
@@ -11,7 +11,7 @@ import { blockNodes } from '../adf/block-nodes.ts'
|
||||
import { directivePrefix } from '../markdown/directive-syntax.ts'
|
||||
import { emptyKeys, mergeAdjacentText } from '../adf/document.ts'
|
||||
import { inlineNodes } from '../adf/inline-nodes.ts'
|
||||
import { joinsWhenEditorNormal } from '../markdown/adjacent-text.ts'
|
||||
import { joinsWhenEditorNormal } from '../markdown/plain/editor-normal.ts'
|
||||
import { markAttributes } from '../adf/mark-attributes.ts'
|
||||
|
||||
type Positions = { block: AdfNode; inline: AdfNode }
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { AdfAttributes, AdfMark, AdfNode } from '../adf/document.ts'
|
||||
import type { JsonValue } from '../json-value.ts'
|
||||
import { identicalMark, identicalMarks, isBareText, nodeAttrs, nodeMarks } from '../adf/document.ts'
|
||||
import type { AdfNode } from '../adf/document.ts'
|
||||
import { identicalMarks, isBareText, nodeMarks } from '../adf/document.ts'
|
||||
import { spellInlineLeafDirective } from './directive-syntax.ts'
|
||||
|
||||
type JsonContainer = JsonValue[] | { [key: string]: JsonValue }
|
||||
|
||||
export const textBreakName = 'textBreak'
|
||||
|
||||
export const textBreakSpelling = spellInlineLeafDirective(textBreakName, '')
|
||||
@@ -13,41 +10,3 @@ export const textBreakSpelling = spellInlineLeafDirective(textBreakName, '')
|
||||
export function joinsWhenRead(previous: AdfNode, node: AdfNode): boolean {
|
||||
return isBareText(previous) && isBareText(node) && identicalMarks(nodeMarks(previous), nodeMarks(node))
|
||||
}
|
||||
|
||||
// The editor's rule, differing from the reader's only on shapes editor-normal ADF erases: an empty attrs, content or marks, and -0 in a mark.
|
||||
export function joinsWhenEditorNormal(previous: AdfNode, node: AdfNode): boolean {
|
||||
if (!joinsAsEditorText(previous) || !joinsAsEditorText(node)) return false
|
||||
return identicalMarks(nodeMarks(previous).map(normalMark), nodeMarks(node).map(normalMark))
|
||||
}
|
||||
|
||||
export function sameMarkWhenEditorNormal(left: AdfMark, right: AdfMark): boolean {
|
||||
return identicalMark(normalMark(left), normalMark(right))
|
||||
}
|
||||
|
||||
export function normalMark(mark: AdfMark): AdfMark {
|
||||
const attrs = normalAttributes(nodeAttrs(mark))
|
||||
return attrs === undefined ? { type: mark.type } : { attrs, type: mark.type }
|
||||
}
|
||||
|
||||
export 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 joinsAsEditorText(node: AdfNode): boolean {
|
||||
return node.type === 'text' && Object.keys(nodeAttrs(node)).length === 0
|
||||
}
|
||||
|
||||
@@ -75,3 +75,8 @@ test('normalizes blocks and mark attributes nesting far past the levels a recurs
|
||||
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'])
|
||||
})
|
||||
|
||||
test('joins a text node holding content to its neighbour, as editor-normal forms hold no content to part them', () => {
|
||||
const paragraph: AdfNode = { content: [{ content: [{ text: 'lost', type: 'text' }], text: 'a', type: 'text' }, { text: 'b', type: 'text' }], type: 'paragraph' }
|
||||
assert.deepEqual(toEditorNormal({ content: [paragraph], type: 'doc', version: 1 }).content, [{ content: [{ content: [{ text: 'lost', type: 'text' }], text: 'ab', type: 'text' }], type: 'paragraph' }])
|
||||
})
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||
import { joinsWhenEditorNormal, normalAttributes, normalMark } from '../adjacent-text.ts'
|
||||
import { mergeAdjacentText, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
|
||||
import type { AdfAttributes, AdfDocument, AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import type { JsonValue } from '../../json-value.ts'
|
||||
import { identicalMark, mergeAdjacentText, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
|
||||
import { joinsWhenRead } from '../adjacent-text.ts'
|
||||
|
||||
type JsonContainer = JsonValue[] | { [key: string]: JsonValue }
|
||||
|
||||
type NodeHolder = { content?: AdfNode[] }
|
||||
|
||||
// The editor's rule is the reader's over the pair's editor-normal forms, which hold no content: a text node's content never parts it.
|
||||
export function joinsWhenEditorNormal(previous: AdfNode, node: AdfNode): boolean {
|
||||
return joinsWhenRead(normalNode(previous), normalNode(node))
|
||||
}
|
||||
|
||||
export function sameMarkWhenEditorNormal(left: AdfMark, right: AdfMark): boolean {
|
||||
return identicalMark(normalMark(left), normalMark(right))
|
||||
}
|
||||
|
||||
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 }]
|
||||
@@ -28,3 +40,27 @@ function normalNode(node: AdfNode): AdfNode {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { LineContainer } from '../line-container.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 { joinsWhenEditorNormal, sameMarkWhenEditorNormal } from '../adjacent-text.ts'
|
||||
import { joinsWhenEditorNormal, sameMarkWhenEditorNormal } from './editor-normal.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { mergeAdjacentText, nodeAttrs, nodeContent, nodeMarks } from '../../adf/document.ts'
|
||||
import { plainLineFallback, type PlainLineFallback } from '../emit/inline-line.ts'
|
||||
|
||||
Reference in New Issue
Block a user