Skip to content

Commit 1ff95a5

Browse files
authored
Merge pull request #4486 from udecode/fix/md1
2 parents 69f031f + ef8115d commit 1ff95a5

File tree

15 files changed

+322
-84
lines changed

15 files changed

+322
-84
lines changed

.changeset/clever-owls-fix.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@platejs/markdown": patch
3+
---
4+
5+
Fix custom plugin key handling in markdown serialization/deserialization. Ensures plugin keys are properly resolved throughout the conversion process for custom plugin configurations.

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
# Allow claude to read these files
44
!.*
5-
apps/www/src/app/dev
65
apps/www/src/app/api/ai
76
apps/www/public/rd
87
**/tsconfig.tsbuildinfo

apps/www/src/app/dev/c.tsx

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
'use client';
2+
3+
import React from "react";
4+
5+
import { MarkdownPlugin } from "@platejs/markdown";
6+
import { ElementApi, normalizeNodeId, TextApi } from "platejs";
7+
import { usePlateViewEditor } from "platejs/react";
8+
import { useFilePicker } from "use-file-picker";
9+
10+
import { Button } from "@/components/ui/button";
11+
import { BaseEditorKit } from "@/registry/components/editor/editor-base-kit";
12+
import { MarkdownKit } from "@/registry/components/editor/plugins/markdown-kit";
13+
import { basicBlocksValue } from "@/registry/examples/values/basic-blocks-value";
14+
import { basicMarksValue } from "@/registry/examples/values/basic-marks-value";
15+
import { codeBlockValue } from "@/registry/examples/values/code-block-value";
16+
import { tableValue } from "@/registry/examples/values/table-value";
17+
import { EditorView } from "@/registry/ui/editor";
18+
19+
20+
21+
const withCustomType = (value: any) => {
22+
const addCustomType = (item: any): any => {
23+
if (ElementApi.isElement(item)) {
24+
return {
25+
children: item.children.map(addCustomType),
26+
type: 'custom-' + item.type
27+
}
28+
}
29+
if (TextApi.isText(item)) {
30+
const { text, ...rest } = item
31+
const props: any = {}
32+
for (const key in rest) {
33+
const value = rest[key]
34+
const newKey = 'custom-' + key
35+
props[newKey] = value
36+
}
37+
38+
return {
39+
...props,
40+
text: text.replace(/^custom-/, '')
41+
}
42+
}
43+
};
44+
45+
return value.map(addCustomType)
46+
}
47+
48+
const withCustomPlugins = (plugins: any[]): any[] => {
49+
const newPlugins: any[] = []
50+
51+
plugins.forEach(plugin => {
52+
newPlugins.push(plugin.extend({
53+
node: {
54+
type: 'custom-' + plugin.key
55+
}
56+
}))
57+
})
58+
59+
return newPlugins
60+
}
61+
62+
63+
let index = 0
64+
65+
const value = normalizeNodeId([
66+
...withCustomType(basicBlocksValue),
67+
...withCustomType(basicMarksValue),
68+
...withCustomType(tableValue),
69+
...withCustomType(codeBlockValue),
70+
], {
71+
idCreator() {
72+
return 'id-' + index++;
73+
},
74+
});
75+
76+
77+
78+
export const EditorViewClient = () => {
79+
80+
const editor = usePlateViewEditor({
81+
plugins: [
82+
...withCustomPlugins(BaseEditorKit),
83+
...MarkdownKit
84+
],
85+
value: value,
86+
});
87+
88+
const getFileNodes = (text: string,) => {
89+
90+
return editor.getApi(MarkdownPlugin).markdown.deserialize(text);
91+
};
92+
93+
const { openFilePicker: openMdFilePicker } = useFilePicker({
94+
accept: ['.md', '.mdx'],
95+
multiple: false,
96+
onFilesSelected: async ({ plainFiles }) => {
97+
const text = await plainFiles[0].text();
98+
99+
const nodes = getFileNodes(text);
100+
console.log("🚀 ~ onFilesSelected: ~ nodes:", nodes)
101+
},
102+
});
103+
104+
105+
return <>
106+
<EditorView variant="none" className="px-10" editor={editor} />
107+
108+
109+
<div className="mt-10 px-10">
110+
<Button className="mr-10" onClick={
111+
() => {
112+
console.log(editor.getApi(MarkdownPlugin).markdown.serialize());
113+
}
114+
}>Serialize</Button>
115+
116+
117+
<Button onClick={openMdFilePicker}>Deserialize</Button>
118+
</div>
119+
</>
120+
};

apps/www/src/app/dev/layout.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function DevLayout(props: { children: React.ReactNode }) {
2+
return (
3+
<>
4+
<main>{props.children}</main>
5+
</>
6+
);
7+
}

apps/www/src/app/dev/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
3+
import { EditorViewClient } from './c';
4+
5+
export default async function DevPage() {
6+
return (
7+
<main>
8+
<EditorViewClient />
9+
</main>
10+
);
11+
}

packages/markdown/src/lib/deserializer/convertTextsDeserialize.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { getPluginType } from 'platejs';
2+
13
import type { MdDelete, MdEmphasis, MdStrong } from '../mdast';
24
import type { MdDecoration } from '../types';
35
import type { DeserializeMdOptions } from './deserializeMd';
@@ -11,10 +13,13 @@ export const convertTextsDeserialize = (
1113
options: DeserializeMdOptions
1214
) => {
1315
return mdastNode.children.reduce((acc: any, n: any) => {
16+
const key = mdastToPlate(options.editor!, mdastNode.type)
17+
const type = getPluginType(options.editor!, key)
18+
1419
acc.push(
1520
...buildSlateNode(
1621
n,
17-
{ ...deco, [mdastToPlate(options.editor!, mdastNode.type)]: true },
22+
{ ...deco, [type]: true },
1823
options
1924
)
2025
);

packages/markdown/src/lib/deserializer/deserializeMd.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import type { Root } from 'mdast';
22
import type { Plugin } from 'unified';
33

4-
import { type Descendant, type SlateEditor, KEYS, TextApi } from 'platejs';
4+
import {
5+
type Descendant,
6+
type SlateEditor,
7+
getPluginKey,
8+
KEYS,
9+
TextApi,
10+
} from 'platejs';
511
import remarkParse from 'remark-parse';
612
import { unified } from 'unified';
713

@@ -92,7 +98,7 @@ export const deserializeMd = (
9298
TextApi.isText(item)
9399
? {
94100
children: [item],
95-
type: editor.getType(KEYS.p),
101+
type: getPluginKey(editor, KEYS.p) ?? KEYS.p,
96102
}
97103
: item
98104
);

packages/markdown/src/lib/deserializer/mdastToSlate.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Root } from 'mdast';
22

3-
import { type Descendant, KEYS } from 'platejs';
3+
import { type Descendant, getPluginKey, KEYS } from 'platejs';
44

55
import type { MdRoot } from '../mdast';
66
import type { DeserializeMdOptions } from './deserializeMd';
@@ -40,7 +40,9 @@ const buildSlateRoot = (
4040
results.push(
4141
...Array.from({ length: count }).map(() => ({
4242
children: [{ text: '' }],
43-
type: KEYS.p,
43+
type: options.editor
44+
? (getPluginKey(options.editor, KEYS.p) ?? KEYS.p)
45+
: KEYS.p,
4446
}))
4547
);
4648
}

packages/markdown/src/lib/rules/__snapshots__/defaultRule.spec.ts.snap

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ Paragraph
77
"
88
`;
99

10-
exports[`defaultRules should serialize default keys 1`] = `
11-
"# Heading 1
12-
13-
Paragraph
10+
exports[`defaultRules should serialize custom mark 1`] = `
11+
"Paragraph**text**
1412
"
1513
`;
Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,71 @@
11
import { H1Plugin } from '@platejs/basic-nodes/react';
2+
import { BoldPlugin } from '@platejs/basic-nodes/react';
23
import { createPlateEditor, ParagraphPlugin } from 'platejs/react';
34

5+
import { deserializeMd } from '../deserializer';
46
import { MarkdownPlugin } from '../MarkdownPlugin';
57
import { serializeMd } from '../serializer';
68

79
describe('defaultRules', () => {
8-
it('should serialize default keys', () => {
10+
it('should serialize custom keys', () => {
911
const nodes = [
1012
{
1113
children: [{ text: 'Heading 1' }],
12-
type: 'h1',
14+
type: 'custom-h1',
1315
},
1416
{
1517
children: [{ text: 'Paragraph' }],
16-
type: 'p',
18+
type: 'custom-p',
1719
},
1820
];
1921

2022
const editor = createPlateEditor({
21-
plugins: [MarkdownPlugin, H1Plugin, ParagraphPlugin],
23+
plugins: [
24+
MarkdownPlugin,
25+
H1Plugin.configure({
26+
node: { type: 'custom-h1' },
27+
}),
28+
ParagraphPlugin.configure({
29+
node: { type: 'custom-p' },
30+
}),
31+
],
2232
});
2333

2434
const result = serializeMd(editor, { value: nodes });
2535
expect(result).toMatchSnapshot();
2636
});
2737

28-
it('should serialize custom keys', () => {
38+
it('should serialize custom mark', () => {
39+
const nodes = [
40+
{
41+
children: [
42+
{ text: 'Paragraph' },
43+
{ 'custom-bold': true, text: 'text' },
44+
],
45+
type: 'custom-p',
46+
},
47+
];
48+
49+
const editor = createPlateEditor({
50+
plugins: [
51+
MarkdownPlugin,
52+
H1Plugin.configure({
53+
node: { type: 'custom-h1' },
54+
}),
55+
ParagraphPlugin.configure({
56+
node: { type: 'custom-p' },
57+
}),
58+
BoldPlugin.configure({
59+
node: { type: 'custom-bold' },
60+
}),
61+
],
62+
});
63+
64+
const result = serializeMd(editor, { value: nodes });
65+
expect(result).toMatchSnapshot();
66+
});
67+
68+
it('should deserialize custom keys', () => {
2969
const nodes = [
3070
{
3171
children: [{ text: 'Heading 1' }],
@@ -40,12 +80,50 @@ describe('defaultRules', () => {
4080
const editor = createPlateEditor({
4181
plugins: [
4282
MarkdownPlugin,
43-
H1Plugin.configure({ key: 'custom-h1' as any }),
44-
ParagraphPlugin.configure({ key: 'custom-p' as any }),
83+
H1Plugin.configure({
84+
node: { type: 'custom-h1' },
85+
}),
86+
ParagraphPlugin.configure({
87+
node: { type: 'custom-p' },
88+
}),
4589
],
4690
});
4791

48-
const result = serializeMd(editor, { value: nodes });
49-
expect(result).toMatchSnapshot();
92+
const result = deserializeMd(editor, '# Heading 1\nParagraph');
93+
expect(result).toEqual(nodes);
94+
});
95+
96+
it('should deserialize custom mark', () => {
97+
const nodes = [
98+
{
99+
children: [{ text: 'Heading 1' }],
100+
type: 'custom-h1',
101+
},
102+
{
103+
children: [
104+
{ text: 'Paragraph' },
105+
{ 'custom-bold': true, text: 'text' },
106+
],
107+
type: 'custom-p',
108+
},
109+
];
110+
111+
const editor = createPlateEditor({
112+
plugins: [
113+
MarkdownPlugin,
114+
H1Plugin.configure({
115+
node: { type: 'custom-h1' },
116+
}),
117+
ParagraphPlugin.configure({
118+
node: { type: 'custom-p' },
119+
}),
120+
BoldPlugin.configure({
121+
node: { type: 'custom-bold' },
122+
}),
123+
],
124+
});
125+
126+
const result = deserializeMd(editor, '# Heading 1\nParagraph**text**');
127+
expect(result).toEqual(nodes);
50128
});
51129
});

0 commit comments

Comments
 (0)