mirror of
https://github.com/strapi/strapi.git
synced 2025-12-17 02:03:18 +00:00
fix(content-manager): dont support list selector when multiple blocks are selected
This commit is contained in:
parent
c58fb0f790
commit
3b379e30a6
@ -34,7 +34,7 @@ const LinkContent = React.forwardRef<HTMLAnchorElement, LinkContentProps>(
|
|||||||
const [linkText, setLinkText] = React.useState(elementText);
|
const [linkText, setLinkText] = React.useState(elementText);
|
||||||
const [linkUrl, setLinkUrl] = React.useState(link.url);
|
const [linkUrl, setLinkUrl] = React.useState(link.url);
|
||||||
const linkInputRef = React.useRef<HTMLInputElement>(null);
|
const linkInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
const showRemoveButton = editor.lastInsertedLinkPath
|
const isLastInsertedLink = editor.lastInsertedLinkPath
|
||||||
? !Path.equals(path, editor.lastInsertedLinkPath)
|
? !Path.equals(path, editor.lastInsertedLinkPath)
|
||||||
: true;
|
: true;
|
||||||
const [isSaveDisabled, setIsSaveDisabled] = React.useState(false);
|
const [isSaveDisabled, setIsSaveDisabled] = React.useState(false);
|
||||||
@ -147,7 +147,7 @@ const LinkContent = React.forwardRef<HTMLAnchorElement, LinkContentProps>(
|
|||||||
<RemoveButton
|
<RemoveButton
|
||||||
variant="danger-light"
|
variant="danger-light"
|
||||||
onClick={() => removeLink(editor)}
|
onClick={() => removeLink(editor)}
|
||||||
$visible={showRemoveButton}
|
$visible={isLastInsertedLink}
|
||||||
>
|
>
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'components.Blocks.popover.remove',
|
id: 'components.Blocks.popover.remove',
|
||||||
|
|||||||
@ -272,9 +272,11 @@ const handleEnterKeyOnList = (editor: Editor) => {
|
|||||||
* Common handler for converting a node to a list
|
* Common handler for converting a node to a list
|
||||||
*/
|
*/
|
||||||
const handleConvertToList = (editor: Editor, format: Block<'list'>['format']) => {
|
const handleConvertToList = (editor: Editor, format: Block<'list'>['format']) => {
|
||||||
baseHandleConvert<Block<'list-item'>>(editor, { type: 'list-item' });
|
const convertedPath = baseHandleConvert<Block<'list-item'>>(editor, { type: 'list-item' });
|
||||||
|
|
||||||
Transforms.wrapNodes(editor, { type: 'list', format, children: [] });
|
if (!convertedPath) return;
|
||||||
|
|
||||||
|
Transforms.wrapNodes(editor, { type: 'list', format, children: [] }, { at: convertedPath });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1419,38 +1419,29 @@ describe('List', () => {
|
|||||||
|
|
||||||
it('converts a heading with a link to a list', () => {
|
it('converts a heading with a link to a list', () => {
|
||||||
const baseEditor = createEditor();
|
const baseEditor = createEditor();
|
||||||
|
|
||||||
baseEditor.children = [
|
baseEditor.children = [
|
||||||
{
|
{
|
||||||
type: 'heading',
|
type: 'heading',
|
||||||
|
level: 1,
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
text: '',
|
|
||||||
type: 'text',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
url: 'https://strapi.io',
|
url: 'https://strapi.io',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
text: 'Heading link',
|
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
text: 'Heading link',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: '',
|
|
||||||
type: 'text',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
level: 1,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Set the cursor on the heading
|
// Set the cursor on the heading
|
||||||
Transforms.select(baseEditor, {
|
Transforms.select(baseEditor, {
|
||||||
anchor: { path: [0, 1, 0], offset: 0 },
|
anchor: { path: [0, 0, 0], offset: 0 },
|
||||||
focus: { path: [0, 1, 0], offset: 0 },
|
focus: { path: [0, 0, 0], offset: 0 },
|
||||||
});
|
});
|
||||||
|
|
||||||
listBlocks['list-ordered'].handleConvert!(baseEditor);
|
listBlocks['list-ordered'].handleConvert!(baseEditor);
|
||||||
|
|||||||
@ -362,6 +362,39 @@ const ListButton = ({ block, format }: ListButtonProps) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @TODO: Currently, applying list while multiple blocks are selected is not supported.
|
||||||
|
* We should implement this feature in the future.
|
||||||
|
*/
|
||||||
|
const isListDisabled = () => {
|
||||||
|
// Always disabled when the whole editor is disabled
|
||||||
|
if (disabled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always enabled when there's no selection
|
||||||
|
if (!editor.selection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the block node closest to the anchor and focus
|
||||||
|
const anchorNodeEntry = Editor.above(editor, {
|
||||||
|
at: editor.selection.anchor,
|
||||||
|
match: (node) => !Editor.isEditor(node) && node.type !== 'text',
|
||||||
|
});
|
||||||
|
const focusNodeEntry = Editor.above(editor, {
|
||||||
|
at: editor.selection.focus,
|
||||||
|
match: (node) => !Editor.isEditor(node) && node.type !== 'text',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!anchorNodeEntry || !focusNodeEntry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled if the anchor and focus are not in the same block
|
||||||
|
return anchorNodeEntry[0] !== focusNodeEntry[0];
|
||||||
|
};
|
||||||
|
|
||||||
const toggleList = (format: Block<'list'>['format']) => {
|
const toggleList = (format: Block<'list'>['format']) => {
|
||||||
let currentListEntry;
|
let currentListEntry;
|
||||||
if (editor.selection) {
|
if (editor.selection) {
|
||||||
@ -403,7 +436,7 @@ const ListButton = ({ block, format }: ListButtonProps) => {
|
|||||||
name={format}
|
name={format}
|
||||||
label={block.label}
|
label={block.label}
|
||||||
isActive={isListActive()}
|
isActive={isListActive()}
|
||||||
disabled={disabled}
|
disabled={isListDisabled()}
|
||||||
handleClick={() => toggleList(format)}
|
handleClick={() => toggleList(format)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type Element, Editor, Transforms } from 'slate';
|
import { type Element, type Path, Editor, Transforms } from 'slate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts some logic that is common to most blocks' handleConvert functions.
|
* Extracts some logic that is common to most blocks' handleConvert functions.
|
||||||
@ -7,7 +7,7 @@ import { type Element, Editor, Transforms } from 'slate';
|
|||||||
const baseHandleConvert = <T extends Element>(
|
const baseHandleConvert = <T extends Element>(
|
||||||
editor: Editor,
|
editor: Editor,
|
||||||
attributesToSet: Partial<T> & { type: T['type'] }
|
attributesToSet: Partial<T> & { type: T['type'] }
|
||||||
): void => {
|
): void | Path => {
|
||||||
// If there is no selection, convert last inserted node
|
// If there is no selection, convert last inserted node
|
||||||
const [_, lastNodePath] = Editor.last(editor, []);
|
const [_, lastNodePath] = Editor.last(editor, []);
|
||||||
|
|
||||||
@ -18,27 +18,29 @@ const baseHandleConvert = <T extends Element>(
|
|||||||
at: editor.selection ?? lastNodePath,
|
at: editor.selection ?? lastNodePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure we get block nodes (elements), not an inline node
|
// Make sure we get a block node, not an inline node
|
||||||
const [, updatedLastNodePath] = Editor.last(editor, []);
|
const [, updatedLastNodePath] = Editor.last(editor, []);
|
||||||
const nodes = Editor.nodes(editor, {
|
const entry = Editor.above(editor, {
|
||||||
match: (node) => !Editor.isEditor(node) && node.type !== 'text' && node.type !== 'link',
|
match: (node) => !Editor.isEditor(node) && node.type !== 'text' && node.type !== 'link',
|
||||||
at: editor.selection ?? updatedLastNodePath,
|
at: editor.selection ?? updatedLastNodePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!nodes) {
|
if (!entry || Editor.isEditor(entry[0])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [element, elementPath] of nodes) {
|
const [element, elementPath] = entry;
|
||||||
Transforms.setNodes(
|
|
||||||
editor,
|
Transforms.setNodes(
|
||||||
{
|
editor,
|
||||||
...getAttributesToClear(element as Element), // because of the match we can safely assume it's a Node of type Element
|
{
|
||||||
...attributesToSet,
|
...getAttributesToClear(element),
|
||||||
} as Partial<Element>,
|
...attributesToSet,
|
||||||
{ at: elementPath }
|
} as Partial<Element>,
|
||||||
);
|
{ at: elementPath }
|
||||||
}
|
);
|
||||||
|
|
||||||
|
return elementPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user