diff --git a/.changeset/yellow-stamps-wave.md b/.changeset/yellow-stamps-wave.md new file mode 100644 index 000000000..221cf035b --- /dev/null +++ b/.changeset/yellow-stamps-wave.md @@ -0,0 +1,5 @@ +--- +'@portabletext/editor': patch +--- + +fix(`patches`): mitigate error when span without `text` is inserted diff --git a/packages/editor/src/internal-utils/values.ts b/packages/editor/src/internal-utils/values.ts index adce19d93..3950ffc67 100644 --- a/packages/editor/src/internal-utils/values.ts +++ b/packages/editor/src/internal-utils/values.ts @@ -23,6 +23,7 @@ export function toSlateBlock( let hasInlines = false const hasMissingMarkDefs = typeof textBlock.markDefs === 'undefined' const hasMissingChildren = typeof textBlock.children === 'undefined' + let hasSpansWithMissingText = false const children = (textBlock.children || []).map((child) => { const {_type: childType, _key: childKey, ...childProps} = child @@ -62,6 +63,15 @@ export function toSlateBlock( } } + if (typeof child.text !== 'string') { + hasSpansWithMissingText = true + + return { + ...child, + text: '', + } + } + // Original child object (span) return child }) @@ -71,6 +81,7 @@ export function toSlateBlock( !hasMissingMarkDefs && !hasMissingChildren && !hasInlines && + !hasSpansWithMissingText && Element.isElement(block) ) { // Original object diff --git a/packages/editor/tests/event.patches.test.tsx b/packages/editor/tests/event.patches.test.tsx index edd9e8d4f..b38f139a3 100644 --- a/packages/editor/tests/event.patches.test.tsx +++ b/packages/editor/tests/event.patches.test.tsx @@ -3352,7 +3352,7 @@ describe('event.patches', () => { expect(editor.getSnapshot().context.value).toEqual([block]) }) - test('Scenario: Setting span text is a noop', async () => { + test('Scenario: Setting span text for empty text property is a noop', async () => { const keyGenerator = createTestKeyGenerator() const blockKey = keyGenerator() const spanKey = keyGenerator() @@ -3385,6 +3385,65 @@ describe('event.patches', () => { expect(editor.getSnapshot().context.value).toEqual([block]) }) + test('Scenario: Setting span text is not possible', async () => { + const keyGenerator = createTestKeyGenerator() + const blockKey = keyGenerator() + const spanKey = keyGenerator() + const block = { + _key: blockKey, + _type: 'block', + children: [ + {_key: spanKey, _type: 'span', text: 'foo', marks: ['strong']}, + ], + markDefs: [], + style: 'normal', + } + + const {editor} = await createTestEditor({ + keyGenerator, + initialValue: [block], + schemaDefinition: defineSchema({ + decorators: [{name: 'strong'}], + }), + }) + + const newSpanKey = keyGenerator() + + editor.send({ + type: 'patches', + patches: [ + { + type: 'insert', + origin: 'remote', + path: [{_key: blockKey}, 'children', {_key: spanKey}], + items: [ + // Spans without text are not allowed. This span will therefore + // be given an empty text prop upon insertion + { + _key: newSpanKey, + _type: 'span', + marks: [], + }, + ], + position: 'after', + }, + // This patch turns into a noop because the span was given an empty + // text prop upon insertion + { + type: 'setIfMissing', + origin: 'remote', + path: [{_key: blockKey}, 'children', {_key: newSpanKey}, 'text'], + value: 'bar', + }, + ], + snapshot: undefined, + }) + + await vi.waitFor(() => { + expect(editor.getSnapshot().context.value).toEqual([block]) + }) + }) + test('Scenario: Setting nested span property', async () => { const keyGenerator = createTestKeyGenerator() const blockKey = keyGenerator()