fix: #551 - rich text nested list structure
This commit is contained in:
@@ -32,7 +32,7 @@ const defaultLeaves: RichTextLeaf[] = ['bold', 'italic', 'underline', 'strikethr
|
||||
const baseClass = 'rich-text';
|
||||
type CustomText = { text: string;[x: string]: unknown }
|
||||
|
||||
type CustomElement = { type?: string; children: CustomText[] }
|
||||
type CustomElement = { type?: string; children: (CustomText | CustomElement)[] }
|
||||
|
||||
declare module 'slate' {
|
||||
interface CustomTypes {
|
||||
@@ -349,13 +349,13 @@ const RichText: React.FC<Props> = (props) => {
|
||||
|
||||
if (SlateElement.isElement(selectedElement) && selectedElement.type === 'li') {
|
||||
const selectedLeaf = Node.descendant(editor, editor.selection.anchor.path);
|
||||
if (Text.isText(selectedLeaf) && String(selectedLeaf.text).length === 1) {
|
||||
if (Text.isText(selectedLeaf) && String(selectedLeaf.text).length === 0) {
|
||||
Transforms.unwrapNodes(editor, {
|
||||
match: (n) => SlateElement.isElement(n) && listTypes.includes(n.type),
|
||||
split: true,
|
||||
});
|
||||
|
||||
Transforms.setNodes(editor, {});
|
||||
Transforms.setNodes(editor, { type: undefined });
|
||||
}
|
||||
} else if (editor.isVoid(selectedElement)) {
|
||||
Transforms.removeNodes(editor);
|
||||
@@ -374,6 +374,12 @@ const RichText: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</Slate>
|
||||
<pre>
|
||||
{JSON.stringify(editor.selection)}
|
||||
</pre>
|
||||
<pre>
|
||||
{JSON.stringify(value, null, 2)}
|
||||
</pre>
|
||||
<FieldDescription
|
||||
value={value}
|
||||
description={description}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useSlate } from 'slate-react';
|
||||
import isListActive from './isListActive';
|
||||
import toggleElement from './toggle';
|
||||
import toggleList from './toggleList';
|
||||
import { ButtonProps } from './types';
|
||||
|
||||
import '../buttons.scss';
|
||||
@@ -13,7 +13,7 @@ const ListButton: React.FC<ButtonProps> = ({ format, children, onClick, classNam
|
||||
|
||||
const defaultOnClick = useCallback((event) => {
|
||||
event.preventDefault();
|
||||
toggleElement(editor, format);
|
||||
toggleList(editor, format);
|
||||
}, [editor, format]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -52,12 +52,57 @@ const indent = {
|
||||
const isCurrentlyUL = isElementActive(editor, 'ul');
|
||||
|
||||
if (isCurrentlyOL || isCurrentlyUL) {
|
||||
Transforms.wrapNodes(editor, {
|
||||
type: 'li',
|
||||
children: [],
|
||||
});
|
||||
Transforms.wrapNodes(editor, { type: isCurrentlyOL ? 'ol' : 'ul', children: [{ text: ' ' }] });
|
||||
Transforms.setNodes(editor, { type: 'li' });
|
||||
let hasText = false;
|
||||
|
||||
if (editor.selection) {
|
||||
const leafNode = Editor.leaf(editor, editor.selection.focus);
|
||||
if (leafNode) {
|
||||
const [leaf] = leafNode;
|
||||
hasText = leaf.text.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasText) {
|
||||
Transforms.wrapNodes(editor, {
|
||||
type: 'li',
|
||||
children: [],
|
||||
});
|
||||
Transforms.wrapNodes(editor, { type: isCurrentlyOL ? 'ol' : 'ul', children: [{ text: ' ' }] });
|
||||
Transforms.setNodes(editor, { type: 'li' });
|
||||
} else {
|
||||
const [previousNode, previousNodePath] = Editor.previous(editor, {
|
||||
at: editor.selection.focus,
|
||||
});
|
||||
|
||||
Transforms.removeNodes(editor);
|
||||
|
||||
Transforms.insertNodes(
|
||||
editor,
|
||||
[
|
||||
{
|
||||
children: [
|
||||
previousNode,
|
||||
],
|
||||
},
|
||||
{
|
||||
type: isCurrentlyOL ? 'ol' : 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
{
|
||||
at: previousNodePath,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Transforms.wrapNodes(editor, { type: indentType, children: [] });
|
||||
}
|
||||
|
||||
@@ -1,33 +1,17 @@
|
||||
import { Element, Transforms } from 'slate';
|
||||
import { Transforms } from 'slate';
|
||||
import { ReactEditor } from 'slate-react';
|
||||
import isElementActive from './isActive';
|
||||
import listTypes from './listTypes';
|
||||
|
||||
const toggleElement = (editor, format) => {
|
||||
const isActive = isElementActive(editor, format);
|
||||
const isList = listTypes.includes(format);
|
||||
|
||||
let type = format;
|
||||
|
||||
if (isActive) {
|
||||
type = undefined;
|
||||
} else if (isList) {
|
||||
type = 'li';
|
||||
}
|
||||
|
||||
Transforms.unwrapNodes(editor, {
|
||||
match: (n) => Element.isElement(n) && listTypes.includes(n.type as string),
|
||||
split: true,
|
||||
mode: 'lowest',
|
||||
});
|
||||
|
||||
Transforms.setNodes(editor, { type });
|
||||
|
||||
if (!isActive && isList) {
|
||||
const block = { type: format, children: [] };
|
||||
Transforms.wrapNodes(editor, block);
|
||||
}
|
||||
|
||||
ReactEditor.focus(editor);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Element, Transforms } from 'slate';
|
||||
import { ReactEditor } from 'slate-react';
|
||||
import isElementActive from './isActive';
|
||||
import listTypes from './listTypes';
|
||||
|
||||
const toggleList = (editor, format) => {
|
||||
const isActive = isElementActive(editor, format);
|
||||
const isList = listTypes.includes(format);
|
||||
|
||||
let type = format;
|
||||
|
||||
if (isActive) {
|
||||
type = undefined;
|
||||
} else if (isList) {
|
||||
type = 'li';
|
||||
}
|
||||
|
||||
Transforms.unwrapNodes(editor, {
|
||||
match: (n) => Element.isElement(n) && listTypes.includes(n.type as string),
|
||||
split: true,
|
||||
mode: 'lowest',
|
||||
});
|
||||
|
||||
Transforms.setNodes(editor, { type });
|
||||
|
||||
if (!isActive && isList) {
|
||||
const block = { type: format, children: [] };
|
||||
Transforms.wrapNodes(editor, block);
|
||||
}
|
||||
|
||||
ReactEditor.focus(editor);
|
||||
};
|
||||
|
||||
export default toggleList;
|
||||
@@ -3,7 +3,15 @@ import { loremIpsum } from './loremIpsum';
|
||||
|
||||
const RichTextFields: CollectionConfig = {
|
||||
slug: 'rich-text-fields',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'selectHasMany',
|
||||
hasMany: true,
|
||||
@@ -257,7 +265,82 @@ function generateRichText() {
|
||||
];
|
||||
}
|
||||
|
||||
export const richTextBulletsDoc = {
|
||||
title: 'Bullets and Indentation',
|
||||
richText: [
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Normal bullet',
|
||||
},
|
||||
],
|
||||
type: 'li',
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am the old style of sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'Another normal bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'This text precedes a nested list',
|
||||
},
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am a sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'And I am another sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const richTextDoc = {
|
||||
title: 'Rich Text',
|
||||
selectHasMany: ['one', 'five'],
|
||||
richText: generateRichText(),
|
||||
richTextReadOnly: generateRichText(),
|
||||
|
||||
@@ -8,7 +8,7 @@ import BlockFields, { blocksDoc } from './collections/Blocks';
|
||||
import CollapsibleFields, { collapsibleDoc } from './collections/Collapsible';
|
||||
import ConditionalLogic, { conditionalLogicDoc } from './collections/ConditionalLogic';
|
||||
import DateFields, { dateDoc } from './collections/Date';
|
||||
import RichTextFields, { richTextDoc } from './collections/RichText';
|
||||
import RichTextFields, { richTextBulletsDoc, richTextDoc } from './collections/RichText';
|
||||
import SelectFields, { selectsDoc } from './collections/Select';
|
||||
import TabsFields, { tabsDoc } from './collections/Tabs';
|
||||
import TextFields, { textDoc, textFieldsSlug } from './collections/Text';
|
||||
@@ -100,6 +100,7 @@ export default buildConfig({
|
||||
richTextDocWithRelationship.richTextReadOnly[richTextUploadIndex].value = { id: createdUploadDoc.id };
|
||||
|
||||
await payload.create({ collection: 'rich-text-fields', data: richTextDocWithRelationship });
|
||||
await payload.create({ collection: 'rich-text-fields', data: richTextBulletsDoc });
|
||||
|
||||
await payload.create({ collection: 'number-fields', data: numberDoc });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user