Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/yellow-stamps-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@portabletext/editor': patch
---

fix(`patches`): mitigate error when span without `text` is inserted
11 changes: 11 additions & 0 deletions packages/editor/src/internal-utils/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -62,6 +63,15 @@ export function toSlateBlock(
}
}

if (typeof child.text !== 'string') {
hasSpansWithMissingText = true

return {
...child,
text: '',
}
}

// Original child object (span)
return child
})
Expand All @@ -71,6 +81,7 @@ export function toSlateBlock(
!hasMissingMarkDefs &&
!hasMissingChildren &&
!hasInlines &&
!hasSpansWithMissingText &&
Element.isElement(block)
) {
// Original object
Expand Down
61 changes: 60 additions & 1 deletion packages/editor/tests/event.patches.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down