From ee9a7e7562fe21d3db5f239662b95822aec0fece Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 3 Oct 2026 17:52:48 +0200 Subject: [PATCH] =?UTF-8?q?Join=20only=20content-free=20text=20nodes=20in?= =?UTF-8?q?=20editor-normal=20ADF,=20type=20checked=20first:=20adfToPlainM?= =?UTF-8?q?arkdown=20on=203,000=20extension=20blocks=20of=20200-key=20para?= =?UTF-8?q?meters=201,752-1,941=20ms=20=E2=86=92=201,034-1,240=20ms=20(1,0?= =?UTF-8?q?18-1,283=20ms=20at=20608ef26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/markdown/plain/adf-to-plain-markdown.test.ts | 6 ++++++ src/markdown/plain/editor-normal.test.ts | 10 ++++++---- src/markdown/plain/editor-normal.ts | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/markdown/plain/adf-to-plain-markdown.test.ts b/src/markdown/plain/adf-to-plain-markdown.test.ts index 28db1b7..d634fe0 100644 --- a/src/markdown/plain/adf-to-plain-markdown.test.ts +++ b/src/markdown/plain/adf-to-plain-markdown.test.ts @@ -344,3 +344,9 @@ test('writes what the editor-normal form of the document writes', () => { assert.equal(plain({ content: [], text: 'hi', type: 'futureInline' }), 'hi\n') assert.equal(plain(paragraph({ marks: [{ attrs: {}, type: 'code' }], text: 'a', type: 'text' }, { marks: [{ type: 'code' }], text: '|b', type: 'text' })), '`a|b`\n') }) + +test('writes a text node holding content the same in either order beside its neighbour', () => { + const holding: AdfNode = { content: [text('inner')], text: 'a', type: 'text' } + assert.equal(plain(paragraph(holding, text('b'))), 'ab\n') + assert.equal(plain(paragraph(text('b'), holding)), 'ba\n') +}) diff --git a/src/markdown/plain/editor-normal.test.ts b/src/markdown/plain/editor-normal.test.ts index 3eb32e7..851c660 100644 --- a/src/markdown/plain/editor-normal.test.ts +++ b/src/markdown/plain/editor-normal.test.ts @@ -50,7 +50,7 @@ test('reads negative zero as zero, as JSON does', () => { ) }) -test('reads an empty attrs object, marks array or content array as the absent key, but on doc', () => { +test('reads an empty attrs object, marks array or content array as the absent key, except on doc', () => { 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' }], @@ -77,7 +77,9 @@ test('normalizes blocks and mark attributes nesting far past the levels a recurs 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' }]) +test('joins a text node holding content to no neighbour, so its content is kept', () => { + const holding: AdfNode = { content: [{ text: 'kept', type: 'text' }], text: 'a', type: 'text' } + for (const content of [[holding, { text: 'b', type: 'text' }], [{ text: 'b', type: 'text' }, holding]]) { + assert.deepEqual(toEditorNormal({ content: [{ content, type: 'paragraph' }], type: 'doc', version: 1 }).content, [{ content, type: 'paragraph' }]) + } }) diff --git a/src/markdown/plain/editor-normal.ts b/src/markdown/plain/editor-normal.ts index 74b3a74..3877967 100644 --- a/src/markdown/plain/editor-normal.ts +++ b/src/markdown/plain/editor-normal.ts @@ -7,8 +7,9 @@ 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. +// The editor's rule is the reader's over the pair's editor-normal forms; a text node holding content never joins, so its content is kept. export function joinsWhenEditorNormal(previous: AdfNode, node: AdfNode): boolean { + if (previous.type !== 'text' || node.type !== 'text' || nodeContent(previous).length > 0 || nodeContent(node).length > 0) return false return joinsWhenRead(normalNode(previous), normalNode(node)) }