diff --git a/.changeset/nervous-nails-bathe.md b/.changeset/nervous-nails-bathe.md new file mode 100644 index 0000000000..9f920c0768 --- /dev/null +++ b/.changeset/nervous-nails-bathe.md @@ -0,0 +1,5 @@ +--- +"@udecode/plate-serializer-md": minor +--- + +New option in markdown deserializer plugin: `indentList?: boolean`. Set it to true if you're using Indent List plugin instead of the List plugin. diff --git a/packages/serializer-md/src/deserializer/createDeserializeMdPlugin.ts b/packages/serializer-md/src/deserializer/createDeserializeMdPlugin.ts index 27e85bd6e3..e48427e7c3 100644 --- a/packages/serializer-md/src/deserializer/createDeserializeMdPlugin.ts +++ b/packages/serializer-md/src/deserializer/createDeserializeMdPlugin.ts @@ -36,5 +36,6 @@ export const createDeserializeMdPlugin = options: { elementRules: remarkDefaultElementRules, textRules: remarkDefaultTextRules, + indentList: false, }, }); diff --git a/packages/serializer-md/src/deserializer/types.ts b/packages/serializer-md/src/deserializer/types.ts index 220d281d29..aa21380d1f 100644 --- a/packages/serializer-md/src/deserializer/types.ts +++ b/packages/serializer-md/src/deserializer/types.ts @@ -5,4 +5,5 @@ import { RemarkElementRules, RemarkTextRules } from '../remark-slate/index'; export interface DeserializeMdPlugin { elementRules?: RemarkElementRules; textRules?: RemarkTextRules; + indentList?: boolean; } diff --git a/packages/serializer-md/src/deserializer/utils/deserializeMd.spec.tsx b/packages/serializer-md/src/deserializer/utils/deserializeMd.spec.tsx index 3806737e5e..6224a9477c 100644 --- a/packages/serializer-md/src/deserializer/utils/deserializeMd.spec.tsx +++ b/packages/serializer-md/src/deserializer/utils/deserializeMd.spec.tsx @@ -303,3 +303,98 @@ describe('deserializeMd', () => { expect(deserializeMd(editor, input)).toEqual(output); }); }); + +describe('deserializeMdIndentList', () => { + const editor = createPlateEditor({ + plugins: [ + createDeserializeMdPlugin({ options: { indentList: true } }) as any, + ], + }); + + it('should deserialize unordered lists', () => { + const input = '- List item 1\n- List item 2'; + + const output = [ + { + type: 'p', + listStyleType: 'disc', + indent: 1, + children: [ + { + text: 'List item 1', + }, + ], + }, + { + type: 'p', + listStyleType: 'disc', + indent: 1, + children: [ + { + text: 'List item 2', + }, + ], + }, + ]; + + expect(deserializeMd(editor, input)).toEqual(output); + }); + + it('should deserialize ordered lists', () => { + const input = '1. List item 1\n2. List item 2'; + + const output = [ + { + type: 'p', + listStyleType: 'decimal', + indent: 1, + children: [ + { + text: 'List item 1', + }, + ], + }, + { + type: 'p', + listStyleType: 'decimal', + indent: 1, + children: [ + { + text: 'List item 2', + }, + ], + }, + ]; + + expect(deserializeMd(editor, input)).toEqual(output); + }); + + it('should deserialize mixed nested lists', () => { + const input = '- List item 1\n 1. List item 1.1'; + + const output = [ + { + type: 'p', + listStyleType: 'disc', + indent: 1, + children: [ + { + text: 'List item 1', + }, + ], + }, + { + type: 'p', + listStyleType: 'disc', + indent: 2, + children: [ + { + text: 'List item 1.1', + }, + ], + }, + ]; + + expect(deserializeMd(editor, input)).toEqual(output); + }); +}); diff --git a/packages/serializer-md/src/deserializer/utils/deserializeMd.ts b/packages/serializer-md/src/deserializer/utils/deserializeMd.ts index d46d921411..628717c0d9 100644 --- a/packages/serializer-md/src/deserializer/utils/deserializeMd.ts +++ b/packages/serializer-md/src/deserializer/utils/deserializeMd.ts @@ -14,10 +14,10 @@ export const deserializeMd = ( editor: PlateEditor, data: string ) => { - const { elementRules, textRules } = getPluginOptions( - editor, - KEY_DESERIALIZE_MD - ); + const { elementRules, textRules, indentList } = getPluginOptions< + DeserializeMdPlugin, + V + >(editor, KEY_DESERIALIZE_MD); const tree: any = unified() .use(markdown) @@ -25,6 +25,7 @@ export const deserializeMd = ( editor, elementRules, textRules, + indentList, } as unknown as RemarkPluginOptions) .processSync(data); diff --git a/packages/serializer-md/src/remark-slate/remarkDefaultElementRules.ts b/packages/serializer-md/src/remark-slate/remarkDefaultElementRules.ts index 1a697fadeb..42ed259724 100644 --- a/packages/serializer-md/src/remark-slate/remarkDefaultElementRules.ts +++ b/packages/serializer-md/src/remark-slate/remarkDefaultElementRules.ts @@ -30,7 +30,7 @@ import { ELEMENT_IMAGE } from '@udecode/plate-media'; import { ELEMENT_PARAGRAPH } from '@udecode/plate-paragraph'; import { remarkTransformElementChildren } from './remarkTransformElementChildren'; -import { RemarkElementRules } from './types'; +import { MdastNode, RemarkElementRules } from './types'; // FIXME: underline, subscript superscript not yet supported by remark-slate export const remarkDefaultElementRules: RemarkElementRules = { @@ -52,13 +52,44 @@ export const remarkDefaultElementRules: RemarkElementRules = { }, }, list: { - transform: (node, options) => ({ - type: getPluginType( - options.editor, - node.ordered ? ELEMENT_OL : ELEMENT_UL - ), - children: remarkTransformElementChildren(node, options), - }), + transform: (node, options) => { + if (options.indentList) { + const listStyleType = node.ordered ? 'decimal' : 'disc'; + + const parseListItems = ( + _node: MdastNode, + listItems: TElement[] = [], + indent = 1 + ) => { + _node.children!.forEach((listItem) => { + const [paragraph, ...subLists] = listItem.children!; + + listItems.push({ + type: getPluginType(options.editor, ELEMENT_PARAGRAPH), + listStyleType, + indent, + children: remarkTransformElementChildren(paragraph, options), + }); + + subLists.forEach((subList) => { + parseListItems(subList, listItems, indent + 1); + }); + }); + + return listItems; + }; + + return parseListItems(node); + } else { + return { + type: getPluginType( + options.editor, + node.ordered ? ELEMENT_OL : ELEMENT_UL + ), + children: remarkTransformElementChildren(node, options), + }; + } + }, }, listItem: { transform: (node, options) => ({ diff --git a/packages/serializer-md/src/remark-slate/types.ts b/packages/serializer-md/src/remark-slate/types.ts index fbda6f31c4..3d811334cb 100644 --- a/packages/serializer-md/src/remark-slate/types.ts +++ b/packages/serializer-md/src/remark-slate/types.ts @@ -62,4 +62,5 @@ export type RemarkPluginOptions = { editor: PlateEditor; elementRules: RemarkElementRules; textRules: RemarkTextRules; + indentList?: boolean; }; diff --git a/packages/utils/src/types/types.ts b/packages/utils/src/types/types.ts index 1afe4e9058..a864493fdf 100644 --- a/packages/utils/src/types/types.ts +++ b/packages/utils/src/types/types.ts @@ -38,8 +38,8 @@ export type ModifyDeep> = { [K in keyof A]: B[K] extends never ? A[K] : B[K] extends AnyObject - ? ModifyDeep - : B[K]; + ? ModifyDeep + : B[K]; } & (A extends AnyObject ? Omit : A); export type PartialPick = {