feat: add new autoformat and drag-and-drop functionality to the editor, refactor list handling and update dependencies

This commit is contained in:
Anish Sarkar 2026-02-16 16:05:16 +05:30
parent 1450e22f54
commit 93a0487e56
14 changed files with 972 additions and 321 deletions

View file

@ -1,7 +1,6 @@
'use client';
import type { AutoformatBlockRule, AutoformatRule } from '@platejs/autoformat';
import type { SlateEditor } from 'platejs';
import type { AutoformatRule } from '@platejs/autoformat';
import {
autoformatArrow,
@ -13,38 +12,9 @@ import {
autoformatSmartQuotes,
} from '@platejs/autoformat';
import { insertEmptyCodeBlock } from '@platejs/code-block';
import { toggleList, toggleTaskList, unwrapList } from '@platejs/list-classic';
import { toggleList } from '@platejs/list';
import { openNextToggles } from '@platejs/toggle/react';
import { ElementApi, isType, KEYS } from 'platejs';
const preFormat: AutoformatBlockRule['preFormat'] = (editor) =>
unwrapList(editor);
const format = (editor: SlateEditor, customFormatting: any) => {
if (editor.selection) {
const parentEntry = editor.api.parent(editor.selection);
if (!parentEntry) return;
const [node] = parentEntry;
if (ElementApi.isElement(node) && !isType(editor, node, KEYS.codeBlock)) {
customFormatting();
}
}
};
const formatTaskList = (editor: SlateEditor, defaultChecked = false) => {
format(editor, () => toggleTaskList(editor, defaultChecked));
};
const formatList = (editor: SlateEditor, elementType: string) => {
format(editor, () =>
toggleList(editor, {
type: elementType,
})
);
};
import { KEYS } from 'platejs';
const autoformatMarks: AutoformatRule[] = [
{
@ -123,49 +93,41 @@ const autoformatBlocks: AutoformatRule[] = [
{
match: '# ',
mode: 'block',
preFormat,
type: KEYS.h1,
},
{
match: '## ',
mode: 'block',
preFormat,
type: KEYS.h2,
},
{
match: '### ',
mode: 'block',
preFormat,
type: KEYS.h3,
},
{
match: '#### ',
mode: 'block',
preFormat,
type: KEYS.h4,
},
{
match: '##### ',
mode: 'block',
preFormat,
type: KEYS.h5,
},
{
match: '###### ',
mode: 'block',
preFormat,
type: KEYS.h6,
},
{
match: '> ',
mode: 'block',
preFormat,
type: KEYS.blockquote,
},
{
match: '```',
mode: 'block',
preFormat,
type: KEYS.codeBlock,
format: (editor) => {
insertEmptyCodeBlock(editor, {
@ -198,29 +160,52 @@ const autoformatLists: AutoformatRule[] = [
{
match: ['* ', '- '],
mode: 'block',
preFormat,
type: KEYS.li,
format: (editor) => formatList(editor, KEYS.ulClassic),
type: 'list',
format: (editor) => {
toggleList(editor, {
listStyleType: KEYS.ul,
});
},
},
{
match: [String.raw`^\d+\.$ `, String.raw`^\d+\)$ `],
matchByRegex: true,
mode: 'block',
preFormat,
type: KEYS.li,
format: (editor) => formatList(editor, KEYS.olClassic),
type: 'list',
format: (editor, { matchString }) => {
toggleList(editor, {
listRestartPolite: Number(matchString) || 1,
listStyleType: KEYS.ol,
});
},
},
{
match: '[] ',
match: ['[] '],
mode: 'block',
type: KEYS.taskList,
format: (editor) => formatTaskList(editor, false),
type: 'list',
format: (editor) => {
toggleList(editor, {
listStyleType: KEYS.listTodo,
});
editor.tf.setNodes({
checked: false,
listStyleType: KEYS.listTodo,
});
},
},
{
match: '[x] ',
match: ['[x] '],
mode: 'block',
type: KEYS.taskList,
format: (editor) => formatTaskList(editor, true),
type: 'list',
format: (editor) => {
toggleList(editor, {
listStyleType: KEYS.listTodo,
});
editor.tf.setNodes({
checked: true,
listStyleType: KEYS.listTodo,
});
},
},
];
@ -238,13 +223,16 @@ export const AutoformatKit = [
...autoformatArrow,
...autoformatMath,
...autoformatLists,
].map((rule) => ({
...rule,
query: (editor) =>
!editor.api.some({
match: { type: editor.getType(KEYS.codeBlock) },
}),
})),
].map(
(rule): AutoformatRule => ({
...rule,
query: (editor) =>
!editor.api.some({
match: { type: editor.getType(KEYS.codeBlock) },
}),
})
),
},
}),
];

View file

@ -0,0 +1,23 @@
'use client';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { DndPlugin } from '@platejs/dnd';
import { BlockDraggable } from '@/components/ui/block-draggable';
export const DndKit = [
DndPlugin.configure({
options: {
enableScroller: true,
},
render: {
aboveNodes: BlockDraggable,
aboveSlate: ({ children }) => (
<DndProvider backend={HTML5Backend}>{children}</DndProvider>
),
},
}),
];

View file

@ -1,36 +0,0 @@
'use client';
import {
BulletedListPlugin,
ListItemContentPlugin,
ListItemPlugin,
ListPlugin,
NumberedListPlugin,
TaskListPlugin,
} from '@platejs/list-classic/react';
import {
BulletedListElement,
ListItemElement,
NumberedListElement,
TaskListElement,
} from '@/components/ui/list-classic-node';
export const ListKit = [
ListPlugin,
ListItemPlugin,
ListItemContentPlugin,
BulletedListPlugin.configure({
node: { component: BulletedListElement },
shortcuts: { toggle: { keys: 'mod+alt+5' } },
}),
NumberedListPlugin.configure({
node: { component: NumberedListElement },
shortcuts: { toggle: { keys: 'mod+alt+6' } },
}),
TaskListPlugin.configure({
node: { component: TaskListElement },
shortcuts: { toggle: { keys: 'mod+alt+7' } },
}),
ListItemPlugin.withComponent(ListItemElement),
];

View file

@ -0,0 +1,26 @@
'use client';
import { ListPlugin } from '@platejs/list/react';
import { KEYS } from 'platejs';
import { IndentKit } from '@/components/editor/plugins/indent-kit';
import { BlockList } from '@/components/ui/block-list';
export const ListKit = [
...IndentKit,
ListPlugin.configure({
inject: {
targetPlugins: [
...KEYS.heading,
KEYS.p,
KEYS.blockquote,
KEYS.codeBlock,
KEYS.toggle,
],
},
render: {
belowNodes: BlockList,
},
}),
];