mirror of
https://github.com/strapi/strapi.git
synced 2025-11-03 11:25:17 +00:00
[Blocks Editor] Fix empty list backspace issue (#18260)
* fixed backspace key for empty list * fixed backspace key for empty list * added a check for handleBackspaceKey * test case updated for ordered-list, colocated code for list block
This commit is contained in:
parent
9b2cfe1ae1
commit
502bd29738
@ -62,11 +62,23 @@ const BlocksInput = ({ disabled }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackspaceEvent = (event) => {
|
||||
const selectedNode = editor.children[editor.selection.anchor.path[0]];
|
||||
const selectedBlock = Object.values(blocks).find((block) => block.matchNode(selectedNode));
|
||||
|
||||
if (selectedBlock.handleBackspaceKey) {
|
||||
selectedBlock.handleBackspaceKey(editor, event);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
handleEnter();
|
||||
}
|
||||
if (event.key === 'Backspace') {
|
||||
handleBackspaceEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -18,6 +18,12 @@ const initialValue = [
|
||||
},
|
||||
];
|
||||
|
||||
const mockEvent = {
|
||||
preventDefault: jest.fn(),
|
||||
target: {
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
const user = userEvent.setup();
|
||||
|
||||
const baseEditor = createEditor();
|
||||
@ -608,6 +614,103 @@ describe('useBlocksStore', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles the backspace key on a very first list with single empty list item', () => {
|
||||
const { result } = renderHook(useBlocksStore);
|
||||
|
||||
baseEditor.children = [
|
||||
{
|
||||
type: 'list',
|
||||
format: 'unordered',
|
||||
children: [
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Set the cursor on the first list item
|
||||
Transforms.select(baseEditor, {
|
||||
anchor: { path: [0, 0, 0], offset: 0 },
|
||||
focus: { path: [0, 0, 0], offset: 0 },
|
||||
});
|
||||
|
||||
// Simulate the backspace key
|
||||
result.current['list-unordered'].handleBackspaceKey(baseEditor, mockEvent);
|
||||
|
||||
// Should remove the empty list item and replace with empty paragraph
|
||||
expect(baseEditor.children).toEqual([
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles the backspace key on a list with single empty list item', () => {
|
||||
const { result } = renderHook(useBlocksStore);
|
||||
|
||||
baseEditor.children = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'some text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
format: 'ordered',
|
||||
children: [
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Set the cursor on the first list item
|
||||
Transforms.select(baseEditor, {
|
||||
anchor: { path: [1, 0, 0], offset: 0 },
|
||||
focus: { path: [1, 0, 0], offset: 0 },
|
||||
});
|
||||
|
||||
// Simulate the backspace key
|
||||
result.current['list-ordered'].handleBackspaceKey(baseEditor, mockEvent);
|
||||
|
||||
// Should remove the empty list item
|
||||
expect(baseEditor.children).toEqual([
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'some text',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles enter key on a quote', () => {
|
||||
const { result } = renderHook(useBlocksStore);
|
||||
|
||||
|
||||
@ -151,35 +151,34 @@ List.propTypes = {
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
const Img = styled.img`
|
||||
max-width: 100%;
|
||||
`;
|
||||
/**
|
||||
* Common handler for the backspace event on ordered and unordered lists
|
||||
* @param {import('slate').Editor} editor
|
||||
* @param {Event} event
|
||||
*/
|
||||
const handleBackspaceKeyOnList = (editor, event) => {
|
||||
const [currentListItem, currentListItemPath] = Editor.parent(editor, editor.selection.anchor);
|
||||
const [currentList, currentListPath] = Editor.parent(editor, currentListItemPath);
|
||||
const isListEmpty = currentList.children.length === 1 && currentListItem.children[0].text === '';
|
||||
|
||||
const Image = ({ attributes, children, element }) => {
|
||||
if (!element.image) return null;
|
||||
const { url, alternativeText, width, height } = element.image;
|
||||
if (isListEmpty) {
|
||||
event.preventDefault();
|
||||
// Delete the empty list
|
||||
Transforms.removeNodes(editor, { at: currentListPath });
|
||||
|
||||
return (
|
||||
<Box {...attributes}>
|
||||
{children}
|
||||
<Box contentEditable={false}>
|
||||
<Img src={url} alt={alternativeText} width={width} height={height} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Image.propTypes = {
|
||||
attributes: PropTypes.object.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
element: PropTypes.shape({
|
||||
image: PropTypes.shape({
|
||||
url: PropTypes.string.isRequired,
|
||||
alternativeText: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
}),
|
||||
}).isRequired,
|
||||
if (currentListPath[0] === 0) {
|
||||
// If the list was the only(or first) block element then insert empty paragraph as editor needs default value
|
||||
Transforms.insertNodes(
|
||||
editor,
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ type: 'text', text: '' }],
|
||||
},
|
||||
{ at: currentListPath }
|
||||
);
|
||||
Transforms.select(editor, currentListPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -218,6 +217,37 @@ const handleEnterKeyOnList = (editor) => {
|
||||
}
|
||||
};
|
||||
|
||||
const Img = styled.img`
|
||||
max-width: 100%;
|
||||
`;
|
||||
|
||||
const Image = ({ attributes, children, element }) => {
|
||||
if (!element.image) return null;
|
||||
const { url, alternativeText, width, height } = element.image;
|
||||
|
||||
return (
|
||||
<Box {...attributes}>
|
||||
{children}
|
||||
<Box contentEditable={false}>
|
||||
<Img src={url} alt={alternativeText} width={width} height={height} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Image.propTypes = {
|
||||
attributes: PropTypes.object.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
element: PropTypes.shape({
|
||||
image: PropTypes.shape({
|
||||
url: PropTypes.string.isRequired,
|
||||
alternativeText: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
}),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
const Link = React.forwardRef(({ element, children, ...attributes }, forwardedRef) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const editor = useSlate();
|
||||
@ -381,6 +411,7 @@ Link.propTypes = {
|
||||
* matchNode: (node: Object) => boolean,
|
||||
* isInBlocksSelector: true,
|
||||
* handleEnterKey: (editor: import('slate').Editor) => void,
|
||||
* handleBackspaceKey?:(editor: import('slate').Editor, event: Event) => void,
|
||||
* }
|
||||
* }} an object containing rendering functions and metadata for different blocks, indexed by name.
|
||||
*/
|
||||
@ -603,6 +634,7 @@ export function useBlocksStore() {
|
||||
// TODO add icon and label and set isInBlocksEditor to true
|
||||
isInBlocksSelector: false,
|
||||
handleEnterKey: handleEnterKeyOnList,
|
||||
handleBackspaceKey: handleBackspaceKeyOnList,
|
||||
},
|
||||
'list-unordered': {
|
||||
renderElement: (props) => <List {...props} />,
|
||||
@ -614,6 +646,7 @@ export function useBlocksStore() {
|
||||
// TODO add icon and label and set isInBlocksEditor to true
|
||||
isInBlocksSelector: false,
|
||||
handleEnterKey: handleEnterKeyOnList,
|
||||
handleBackspaceKey: handleBackspaceKeyOnList,
|
||||
},
|
||||
'list-item': {
|
||||
renderElement: (props) => (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user