mirror of
https://github.com/strapi/strapi.git
synced 2025-11-06 21:29:24 +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) => {
|
const handleKeyDown = (event) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleEnter();
|
handleEnter();
|
||||||
}
|
}
|
||||||
|
if (event.key === 'Backspace') {
|
||||||
|
handleBackspaceEvent(event);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -18,6 +18,12 @@ const initialValue = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mockEvent = {
|
||||||
|
preventDefault: jest.fn(),
|
||||||
|
target: {
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|
||||||
const baseEditor = createEditor();
|
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', () => {
|
it('handles enter key on a quote', () => {
|
||||||
const { result } = renderHook(useBlocksStore);
|
const { result } = renderHook(useBlocksStore);
|
||||||
|
|
||||||
|
|||||||
@ -151,35 +151,34 @@ List.propTypes = {
|
|||||||
}).isRequired,
|
}).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 (isListEmpty) {
|
||||||
if (!element.image) return null;
|
event.preventDefault();
|
||||||
const { url, alternativeText, width, height } = element.image;
|
// Delete the empty list
|
||||||
|
Transforms.removeNodes(editor, { at: currentListPath });
|
||||||
|
|
||||||
return (
|
if (currentListPath[0] === 0) {
|
||||||
<Box {...attributes}>
|
// If the list was the only(or first) block element then insert empty paragraph as editor needs default value
|
||||||
{children}
|
Transforms.insertNodes(
|
||||||
<Box contentEditable={false}>
|
editor,
|
||||||
<Img src={url} alt={alternativeText} width={width} height={height} />
|
{
|
||||||
</Box>
|
type: 'paragraph',
|
||||||
</Box>
|
children: [{ type: 'text', text: '' }],
|
||||||
|
},
|
||||||
|
{ at: currentListPath }
|
||||||
);
|
);
|
||||||
};
|
Transforms.select(editor, currentListPath);
|
||||||
|
}
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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 Link = React.forwardRef(({ element, children, ...attributes }, forwardedRef) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const editor = useSlate();
|
const editor = useSlate();
|
||||||
@ -381,6 +411,7 @@ Link.propTypes = {
|
|||||||
* matchNode: (node: Object) => boolean,
|
* matchNode: (node: Object) => boolean,
|
||||||
* isInBlocksSelector: true,
|
* isInBlocksSelector: true,
|
||||||
* handleEnterKey: (editor: import('slate').Editor) => void,
|
* 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.
|
* }} 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
|
// TODO add icon and label and set isInBlocksEditor to true
|
||||||
isInBlocksSelector: false,
|
isInBlocksSelector: false,
|
||||||
handleEnterKey: handleEnterKeyOnList,
|
handleEnterKey: handleEnterKeyOnList,
|
||||||
|
handleBackspaceKey: handleBackspaceKeyOnList,
|
||||||
},
|
},
|
||||||
'list-unordered': {
|
'list-unordered': {
|
||||||
renderElement: (props) => <List {...props} />,
|
renderElement: (props) => <List {...props} />,
|
||||||
@ -614,6 +646,7 @@ export function useBlocksStore() {
|
|||||||
// TODO add icon and label and set isInBlocksEditor to true
|
// TODO add icon and label and set isInBlocksEditor to true
|
||||||
isInBlocksSelector: false,
|
isInBlocksSelector: false,
|
||||||
handleEnterKey: handleEnterKeyOnList,
|
handleEnterKey: handleEnterKeyOnList,
|
||||||
|
handleBackspaceKey: handleBackspaceKeyOnList,
|
||||||
},
|
},
|
||||||
'list-item': {
|
'list-item': {
|
||||||
renderElement: (props) => (
|
renderElement: (props) => (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user