Skip to content

Commit

Permalink
fix(pte): insert empty text block after removing void block (#6552)
Browse files Browse the repository at this point in the history
* fix(pte): insert empty text block after removing void block at position 0

* fix(pte): refactor createWithPlaceholderBlock
  • Loading branch information
pedrobonamin committed May 7, 2024
1 parent fada62f commit 379510f
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function SlateContainer(props: SlateContainerProps) {
}, [keyGenerator, portableTextEditor, maxBlocks, readOnly, patches$, slateEditor])

const initialValue = useMemo(() => {
return [slateEditor.createPlaceholderBlock()]
return [slateEditor.pteCreateEmptyBlock()]
}, [slateEditor])

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function useSyncValue(
at: [childrenLength - 1 - index],
})
})
Transforms.insertNodes(slateEditor, slateEditor.createPlaceholderBlock(), {at: [0]})
Transforms.insertNodes(slateEditor, slateEditor.pteCreateEmptyBlock(), {at: [0]})
// Add a new selection in the top of the document
if (hadSelection) {
Transforms.select(slateEditor, [0, 0])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {describe, expect, it, jest} from '@jest/globals'
/* eslint-disable max-nested-callbacks */
import {render, waitFor} from '@testing-library/react'
import {createRef, type RefObject} from 'react'

import {PortableTextEditorTester, schemaType} from '../../__tests__/PortableTextEditorTester'
import {PortableTextEditor} from '../../PortableTextEditor'

describe('plugin:withPlaceholderBlock', () => {
describe('removing nodes', () => {
it("should insert an empty text block if it's removing the only block", async () => {
const editorRef: RefObject<PortableTextEditor> = createRef()
const initialValue = [
{
_key: '5fc57af23597',
_type: 'someObject',
},
]
const onChange = jest.fn()
await waitFor(() => {
render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={initialValue}
/>,
)
})

await waitFor(() => {
if (editorRef.current) {
PortableTextEditor.focus(editorRef.current)

PortableTextEditor.delete(
editorRef.current,
{
focus: {path: [{_key: '5fc57af23597'}], offset: 0},
anchor: {path: [{_key: '5fc57af23597'}], offset: 0},
},
{mode: 'blocks'},
)

const value = PortableTextEditor.getValue(editorRef.current)

expect(value).toEqual([
{
_type: 'myTestBlockType',
_key: '3',
style: 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: '2',
text: '',
marks: [],
},
],
},
])
}
})
})
it('should not insert a new block if we have more blocks available', async () => {
const editorRef: RefObject<PortableTextEditor> = createRef()
const initialValue = [
{
_key: '5fc57af23597',
_type: 'someObject',
},
{
_type: 'myTestBlockType',
_key: 'existingBlock',
style: 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: '2',
text: '',
marks: [],
},
],
},
]
const onChange = jest.fn()
await waitFor(() => {
render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={initialValue}
/>,
)
})

await waitFor(() => {
if (editorRef.current) {
PortableTextEditor.focus(editorRef.current)

PortableTextEditor.delete(
editorRef.current,
{
focus: {path: [{_key: '5fc57af23597'}], offset: 0},
anchor: {path: [{_key: '5fc57af23597'}], offset: 0},
},
{mode: 'blocks'},
)

const value = PortableTextEditor.getValue(editorRef.current)
expect(value).toEqual([
{
_type: 'myTestBlockType',
_key: 'existingBlock',
style: 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: '2',
text: '',
marks: [],
},
],
},
])
}
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -1250,4 +1250,128 @@ Array [
})
})
})

describe('removing nodes', () => {
it("should insert an empty text block if it's removing the only block", async () => {
const editorRef: RefObject<PortableTextEditor> = createRef()
const initialValue = [
{
_key: '5fc57af23597',
_type: 'someObject',
},
]
const onChange = jest.fn()
await waitFor(() => {
render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={initialValue}
/>,
)
})

await waitFor(() => {
if (editorRef.current) {
PortableTextEditor.focus(editorRef.current)

PortableTextEditor.delete(
editorRef.current,
{
focus: {path: [{_key: '5fc57af23597'}], offset: 0},
anchor: {path: [{_key: '5fc57af23597'}], offset: 0},
},
{mode: 'blocks'},
)

const value = PortableTextEditor.getValue(editorRef.current)

expect(value).toEqual([
{
_type: 'myTestBlockType',
_key: '3',
style: 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: '2',
text: '',
marks: [],
},
],
},
])
}
})
})
it('should not insert a new block if we have more blocks available', async () => {
const editorRef: RefObject<PortableTextEditor> = createRef()
const initialValue = [
{
_key: '5fc57af23597',
_type: 'someObject',
},
{
_type: 'myTestBlockType',
_key: 'existingBlock',
style: 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: '2',
text: '',
marks: [],
},
],
},
]
const onChange = jest.fn()
await waitFor(() => {
render(
<PortableTextEditorTester
onChange={onChange}
ref={editorRef}
schemaType={schemaType}
value={initialValue}
/>,
)
})

await waitFor(() => {
if (editorRef.current) {
PortableTextEditor.focus(editorRef.current)

PortableTextEditor.delete(
editorRef.current,
{
focus: {path: [{_key: '5fc57af23597'}], offset: 0},
anchor: {path: [{_key: '5fc57af23597'}], offset: 0},
},
{mode: 'blocks'},
)

const value = PortableTextEditor.getValue(editorRef.current)
expect(value).toEqual([
{
_type: 'myTestBlockType',
_key: 'existingBlock',
style: 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: '2',
text: '',
marks: [],
},
],
},
])
}
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ export function createWithEditableAPI(
// that would insert the placeholder into the actual value
// which should remain empty)
if (editor.children.length === 0) {
editor.children = [editor.createPlaceholderBlock()]
editor.children = [editor.pteCreateEmptyBlock()]
}
editor.onChange()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
import {type Descendant} from 'slate'
import {Editor, Path} from 'slate'

import {type PortableTextMemberSchemaTypes, type PortableTextSlateEditor} from '../../types/editor'
import {type PortableTextSlateEditor} from '../../types/editor'
import {type SlateTextBlock, type VoidElement} from '../../types/slate'
import {debugWithName} from '../../utils/debug'

const debug = debugWithName('plugin:withPlaceholderBlock')

interface Options {
schemaTypes: PortableTextMemberSchemaTypes
keyGenerator: () => string
}
/**
* Keep a "placeholder" block present when the editor is empty
*
*/
export function createWithPlaceholderBlock({
schemaTypes,
keyGenerator,
}: Options): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
export function createWithPlaceholderBlock(): (
editor: PortableTextSlateEditor,
) => PortableTextSlateEditor {
return function withPlaceholderBlock(editor: PortableTextSlateEditor): PortableTextSlateEditor {
editor.createPlaceholderBlock = (): Descendant => {
debug('Creating placeholder block')
return {
_type: schemaTypes.block.name,
_key: keyGenerator(),
style: schemaTypes.styles[0].value || 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: keyGenerator(),
text: '',
marks: [],
},
],
const {apply} = editor

editor.apply = (op) => {
if (op.type === 'remove_node') {
const node = op.node as SlateTextBlock | VoidElement
if (op.path[0] === 0 && Editor.isVoid(editor, node)) {
// Check next path, if it exists, do nothing
const nextPath = Path.next(op.path)
// Is removing the first block which is a void (not a text block), add a new empty text block in it, if there is no other element in the next path
if (!editor.children[nextPath[0]]) {
debug('Adding placeholder block')
Editor.insertNode(editor, editor.pteCreateEmptyBlock())
}
}
}
apply(op)
}
return editor
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function createWithUtils({schemaTypes, keyGenerator, portableTextEditor}:
{
_type: schemaTypes.block.name,
_key: keyGenerator(),
style: 'normal',
style: schemaTypes.styles[0].value || 'normal',
markDefs: [],
children: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,7 @@ export const withPlugins = <T extends Editor>(
const withPortableTextMarkModel = createWithPortableTextMarkModel(schemaTypes, change$)
const withPortableTextBlockStyle = createWithPortableTextBlockStyle(schemaTypes)

const withPlaceholderBlock = createWithPlaceholderBlock({
keyGenerator,
schemaTypes,
})
const withPlaceholderBlock = createWithPlaceholderBlock()

const withInsertBreak = createWithInsertBreak()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch, previous
editor.children.forEach((c, i) => {
Transforms.removeNodes(editor, {at: [i]})
})
Transforms.insertNodes(editor, editor.createPlaceholderBlock())
Transforms.insertNodes(editor, editor.pteCreateEmptyBlock())
if (previousSelection) {
Transforms.select(editor, {
anchor: {path: [0, 0], offset: 0},
Expand Down

0 comments on commit 379510f

Please sign in to comment.