Skip to content

Commit 9880d2e

Browse files
Merge branch 'main' of github.com:udecode/plate
2 parents 7a48b4b + 8aba85f commit 9880d2e

File tree

104 files changed

+1637
-1149
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+1637
-1149
lines changed

apps/www/public/r/indent-kit.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"files": [
1212
{
1313
"path": "src/registry/components/editor/plugins/indent-kit.tsx",
14-
"content": "'use client';\n\nimport { IndentPlugin } from '@platejs/indent/react';\nimport { KEYS } from 'platejs';\n\nexport const IndentKit = [\n IndentPlugin.configure({\n inject: {\n targetPlugins: [\n ...KEYS.heading,\n KEYS.p,\n KEYS.blockquote,\n KEYS.codeBlock,\n KEYS.toggle,\n KEYS.img\n ],\n },\n options: {\n offset: 24,\n },\n }),\n];\n",
14+
"content": "'use client';\n\nimport { IndentPlugin } from '@platejs/indent/react';\nimport { KEYS } from 'platejs';\n\nexport const IndentKit = [\n IndentPlugin.configure({\n inject: {\n targetPlugins: [\n ...KEYS.heading,\n KEYS.p,\n KEYS.blockquote,\n KEYS.codeBlock,\n KEYS.toggle,\n KEYS.img,\n ],\n },\n options: {\n offset: 24,\n },\n }),\n];\n",
1515
"type": "registry:component"
1616
}
1717
]

apps/www/public/r/markdown-joiner-transform.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"files": [
99
{
1010
"path": "src/registry/lib/markdown-joiner-transform.ts",
11-
"content": "import type { TextStreamPart, ToolSet } from 'ai';\n\n/**\n * Transform chunks like [**,bold,**] to [**bold**] make the md deserializer happy.\n * @experimental\n */\nexport const markdownJoinerTransform =\n <TOOLS extends ToolSet>() =>\n () => {\n const joiner = new MarkdownJoiner();\n\n return new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({\n async flush(controller) {\n const remaining = joiner.flush();\n if (remaining) {\n controller.enqueue({\n textDelta: remaining,\n type: 'text-delta',\n } as TextStreamPart<TOOLS>);\n }\n },\n async transform(chunk, controller) {\n if (chunk.type === 'text-delta') {\n const processedText = joiner.processText(chunk.textDelta);\n if (processedText) {\n controller.enqueue({\n ...chunk,\n textDelta: processedText,\n });\n await delay(joiner.delayInMs);\n }\n } else {\n controller.enqueue(chunk);\n }\n },\n });\n };\n\nconst DEFAULT_DELAY_IN_MS = 10;\nconst NEST_BLOCK_DELAY_IN_MS = 100;\n\nexport class MarkdownJoiner {\n private buffer = '';\n private isBuffering = false;\n private streamingCodeBlock = false;\n private streamingTable = false;\n public delayInMs = DEFAULT_DELAY_IN_MS;\n\n private clearBuffer(): void {\n this.buffer = '';\n this.isBuffering = false;\n }\n private isCompleteBold(): boolean {\n const boldPattern = /\\*\\*.*?\\*\\*/;\n\n return boldPattern.test(this.buffer);\n }\n\n private isCompleteCodeBlockEnd(): boolean {\n return this.buffer.trimEnd() === '```';\n }\n\n private isCompleteCodeBlockStart(): boolean {\n const codeLinePattern = /```[^\\s]+/;\n return codeLinePattern.test(this.buffer);\n }\n\n private isCompleteLink(): boolean {\n const linkPattern = /^\\[.*?\\]\\(.*?\\)$/;\n return linkPattern.test(this.buffer);\n }\n\n private isCompleteList(): boolean {\n const unorderedListPattern = /^[*-]\\s+.+/;\n const todoListPattern = /^[*-]\\s+\\[[ xX]\\]\\s+.+/;\n const orderedListPattern = /^\\d+\\.\\s+.+/;\n\n if (unorderedListPattern.test(this.buffer) && this.buffer.includes('['))\n return todoListPattern.test(this.buffer);\n\n return (\n unorderedListPattern.test(this.buffer) ||\n orderedListPattern.test(this.buffer) ||\n todoListPattern.test(this.buffer)\n );\n }\n\n private isCompleteMdxTag(): boolean {\n const mdxTagPattern = /<([A-Za-z][A-Za-z0-9\\-_]*)>/;\n\n return mdxTagPattern.test(this.buffer);\n }\n\n private isCompleteTableStart(): boolean {\n return this.buffer.startsWith('|') && this.buffer.endsWith('|');\n }\n\n private isFalsePositive(char: string): boolean {\n // when link is not complete, even if ths buffer is more than 30 characters, it is not a false positive\n if (this.buffer.startsWith('[') && this.buffer.includes('http')) {\n return false;\n }\n\n return char === '\\n' || this.buffer.length > 30;\n }\n\n private isListStartChar(char: string): boolean {\n return char === '-' || char === '*' || /^[0-9]$/.test(char);\n }\n\n private isTableExisted(): boolean {\n return this.buffer.length > 10 && !this.buffer.includes('|');\n }\n\n flush(): string {\n const remaining = this.buffer;\n this.clearBuffer();\n return remaining;\n }\n\n processText(text: string): string {\n let output = '';\n\n for (const char of text) {\n if (this.streamingCodeBlock || this.streamingTable) {\n this.buffer += char;\n\n if (char === '\\n') {\n output += this.buffer;\n this.clearBuffer();\n }\n\n if (this.isCompleteCodeBlockEnd() && this.streamingCodeBlock) {\n this.streamingCodeBlock = false;\n this.delayInMs = DEFAULT_DELAY_IN_MS;\n\n output += this.buffer;\n this.clearBuffer();\n }\n\n if (this.isTableExisted() && this.streamingTable) {\n this.streamingTable = false;\n this.delayInMs = DEFAULT_DELAY_IN_MS;\n\n output += this.buffer;\n this.clearBuffer();\n }\n } else if (this.isBuffering) {\n this.buffer += char;\n\n if (this.isCompleteCodeBlockStart()) {\n this.delayInMs = NEST_BLOCK_DELAY_IN_MS;\n this.streamingCodeBlock = true;\n continue;\n }\n\n if (this.isCompleteTableStart()) {\n this.delayInMs = NEST_BLOCK_DELAY_IN_MS;\n this.streamingTable = true;\n continue;\n }\n\n if (\n this.isCompleteBold() ||\n this.isCompleteMdxTag() ||\n this.isCompleteList() ||\n this.isCompleteLink()\n ) {\n output += this.buffer;\n this.clearBuffer();\n } else if (this.isFalsePositive(char)) {\n // False positive - flush buffer as raw text\n output += this.buffer;\n this.clearBuffer();\n }\n } else {\n // Check if we should start buffering\n\n if (\n char === '*' ||\n char === '<' ||\n char === '`' ||\n char === '|' ||\n char === '[' ||\n this.isListStartChar(char)\n ) {\n this.buffer = char;\n this.isBuffering = true;\n } else {\n // Pass through character directly\n output += char;\n }\n }\n }\n\n return output;\n }\n}\n\nasync function delay(delayInMs?: number | null): Promise<void> {\n return delayInMs == null\n ? Promise.resolve()\n : new Promise((resolve) => setTimeout(resolve, delayInMs));\n}\n",
11+
"content": "import type { TextStreamPart, ToolSet } from 'ai';\n\n/**\n * Transform chunks like [**,bold,**] to [**bold**] make the md deserializer\n * happy.\n *\n * @experimental\n */\nexport const markdownJoinerTransform =\n <TOOLS extends ToolSet>() =>\n () => {\n const joiner = new MarkdownJoiner();\n\n return new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({\n async flush(controller) {\n const remaining = joiner.flush();\n if (remaining) {\n controller.enqueue({\n textDelta: remaining,\n type: 'text-delta',\n } as TextStreamPart<TOOLS>);\n }\n },\n async transform(chunk, controller) {\n if (chunk.type === 'text-delta') {\n const processedText = joiner.processText(chunk.textDelta);\n if (processedText) {\n controller.enqueue({\n ...chunk,\n textDelta: processedText,\n });\n await delay(joiner.delayInMs);\n }\n } else {\n controller.enqueue(chunk);\n }\n },\n });\n };\n\nconst DEFAULT_DELAY_IN_MS = 10;\nconst NEST_BLOCK_DELAY_IN_MS = 100;\n\nexport class MarkdownJoiner {\n private buffer = '';\n private isBuffering = false;\n private streamingCodeBlock = false;\n private streamingTable = false;\n public delayInMs = DEFAULT_DELAY_IN_MS;\n\n private clearBuffer(): void {\n this.buffer = '';\n this.isBuffering = false;\n }\n private isCompleteBold(): boolean {\n const boldPattern = /\\*\\*.*?\\*\\*/;\n\n return boldPattern.test(this.buffer);\n }\n\n private isCompleteCodeBlockEnd(): boolean {\n return this.buffer.trimEnd() === '```';\n }\n\n private isCompleteCodeBlockStart(): boolean {\n const codeLinePattern = /```[^\\s]+/;\n return codeLinePattern.test(this.buffer);\n }\n\n private isCompleteLink(): boolean {\n const linkPattern = /^\\[.*?\\]\\(.*?\\)$/;\n return linkPattern.test(this.buffer);\n }\n\n private isCompleteList(): boolean {\n const unorderedListPattern = /^[*-]\\s+.+/;\n const todoListPattern = /^[*-]\\s+\\[[ xX]\\]\\s+.+/;\n const orderedListPattern = /^\\d+\\.\\s+.+/;\n\n if (unorderedListPattern.test(this.buffer) && this.buffer.includes('['))\n return todoListPattern.test(this.buffer);\n\n return (\n unorderedListPattern.test(this.buffer) ||\n orderedListPattern.test(this.buffer) ||\n todoListPattern.test(this.buffer)\n );\n }\n\n private isCompleteMdxTag(): boolean {\n const mdxTagPattern = /<([A-Za-z][A-Za-z0-9\\-_]*)>/;\n\n return mdxTagPattern.test(this.buffer);\n }\n\n private isCompleteTableStart(): boolean {\n return this.buffer.startsWith('|') && this.buffer.endsWith('|');\n }\n\n private isFalsePositive(char: string): boolean {\n // when link is not complete, even if ths buffer is more than 30 characters, it is not a false positive\n if (this.buffer.startsWith('[') && this.buffer.includes('http')) {\n return false;\n }\n\n return char === '\\n' || this.buffer.length > 30;\n }\n\n private isListStartChar(char: string): boolean {\n return char === '-' || char === '*' || /^[0-9]$/.test(char);\n }\n\n private isTableExisted(): boolean {\n return this.buffer.length > 10 && !this.buffer.includes('|');\n }\n\n flush(): string {\n const remaining = this.buffer;\n this.clearBuffer();\n return remaining;\n }\n\n processText(text: string): string {\n let output = '';\n\n for (const char of text) {\n if (this.streamingCodeBlock || this.streamingTable) {\n this.buffer += char;\n\n if (char === '\\n') {\n output += this.buffer;\n this.clearBuffer();\n }\n\n if (this.isCompleteCodeBlockEnd() && this.streamingCodeBlock) {\n this.streamingCodeBlock = false;\n this.delayInMs = DEFAULT_DELAY_IN_MS;\n\n output += this.buffer;\n this.clearBuffer();\n }\n\n if (this.isTableExisted() && this.streamingTable) {\n this.streamingTable = false;\n this.delayInMs = DEFAULT_DELAY_IN_MS;\n\n output += this.buffer;\n this.clearBuffer();\n }\n } else if (this.isBuffering) {\n this.buffer += char;\n\n if (this.isCompleteCodeBlockStart()) {\n this.delayInMs = NEST_BLOCK_DELAY_IN_MS;\n this.streamingCodeBlock = true;\n continue;\n }\n\n if (this.isCompleteTableStart()) {\n this.delayInMs = NEST_BLOCK_DELAY_IN_MS;\n this.streamingTable = true;\n continue;\n }\n\n if (\n this.isCompleteBold() ||\n this.isCompleteMdxTag() ||\n this.isCompleteList() ||\n this.isCompleteLink()\n ) {\n output += this.buffer;\n this.clearBuffer();\n } else if (this.isFalsePositive(char)) {\n // False positive - flush buffer as raw text\n output += this.buffer;\n this.clearBuffer();\n }\n } else {\n // Check if we should start buffering\n\n if (\n char === '*' ||\n char === '<' ||\n char === '`' ||\n char === '|' ||\n char === '[' ||\n this.isListStartChar(char)\n ) {\n this.buffer = char;\n this.isBuffering = true;\n } else {\n // Pass through character directly\n output += char;\n }\n }\n }\n\n return output;\n }\n}\n\nasync function delay(delayInMs?: number | null): Promise<void> {\n return delayInMs == null\n ? Promise.resolve()\n : new Promise((resolve) => setTimeout(resolve, delayInMs));\n}\n",
1212
"type": "registry:lib"
1313
}
1414
]

apps/www/public/r/markdown-streaming-demo.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

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

Lines changed: 100 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,64 @@
11
'use client';
2-
import React from "react";
3-
4-
import { MarkdownPlugin } from "@platejs/markdown";
5-
import { ElementApi, TextApi } from "platejs";
6-
import { createTPlatePlugin, Plate, usePlateEditor } from "platejs/react";
7-
import { useFilePicker } from "use-file-picker";
8-
9-
import { Button } from "@/components/ui/button";
10-
import { EditorKit } from "@/registry/components/editor/editor-kit";
11-
import { BlockPlaceholderKit } from "@/registry/components/editor/plugins/block-placeholder-kit";
12-
import { CopilotKit } from "@/registry/components/editor/plugins/copilot-kit";
13-
import { MarkdownKit } from "@/registry/components/editor/plugins/markdown-kit";
14-
import { basicBlocksValue } from "@/registry/examples/values/basic-blocks-value";
15-
import { AIChatEditor } from "@/registry/ui/ai-chat-editor";
16-
import { Editor, EditorContainer } from "@/registry/ui/editor";
17-
18-
2+
import React from 'react';
3+
4+
import { MarkdownPlugin } from '@platejs/markdown';
5+
import { ElementApi, TextApi } from 'platejs';
6+
import { createTPlatePlugin, Plate, usePlateEditor } from 'platejs/react';
7+
import { useFilePicker } from 'use-file-picker';
8+
9+
import { Button } from '@/components/ui/button';
10+
import { EditorKit } from '@/registry/components/editor/editor-kit';
11+
import { BlockPlaceholderKit } from '@/registry/components/editor/plugins/block-placeholder-kit';
12+
import { CopilotKit } from '@/registry/components/editor/plugins/copilot-kit';
13+
import { MarkdownKit } from '@/registry/components/editor/plugins/markdown-kit';
14+
import { basicBlocksValue } from '@/registry/examples/values/basic-blocks-value';
15+
import { AIChatEditor } from '@/registry/ui/ai-chat-editor';
16+
import { Editor, EditorContainer } from '@/registry/ui/editor';
1917

2018
const withCustomType = (value: any) => {
2119
const addCustomType = (item: any): any => {
2220
if (ElementApi.isElement(item)) {
23-
const { children, type, ...rest } = item
21+
const { children, type, ...rest } = item;
2422
return {
2523
children: children.map(addCustomType),
2624
type: 'custom-' + type,
27-
...rest
28-
}
25+
...rest,
26+
};
2927
}
3028
if (TextApi.isText(item)) {
31-
const { text, ...rest } = item
32-
const props: any = {}
29+
const { text, ...rest } = item;
30+
const props: any = {};
3331
for (const key in rest) {
34-
const value = rest[key]
35-
const newKey = 'custom-' + key
36-
props[newKey] = value
32+
const value = rest[key];
33+
const newKey = 'custom-' + key;
34+
props[newKey] = value;
3735
}
3836

3937
return {
4038
...props,
41-
text: text.replace(/^custom-/, '')
42-
}
39+
text: text.replace(/^custom-/, ''),
40+
};
4341
}
4442
};
4543

46-
return value.map(addCustomType)
47-
}
44+
return value.map(addCustomType);
45+
};
4846

4947
const withCustomPlugins = (plugins: any[]): any[] => {
50-
const newPlugins: any[] = []
51-
52-
plugins.forEach(plugin => {
53-
newPlugins.push(plugin.extend({
54-
node: {
55-
type: 'custom-' + plugin.key
56-
}
57-
}))
58-
})
59-
60-
return newPlugins
61-
}
62-
48+
const newPlugins: any[] = [];
49+
50+
plugins.forEach((plugin) => {
51+
newPlugins.push(
52+
plugin.extend({
53+
node: {
54+
type: 'custom-' + plugin.key,
55+
},
56+
})
57+
);
58+
});
6359

60+
return newPlugins;
61+
};
6462

6563
const value = [
6664
...withCustomType(basicBlocksValue),
@@ -69,38 +67,38 @@ const value = [
6967
// ...withCustomType(codeBlockValue),
7068
// ...withCustomType(listValue),
7169
// ...listValue,
72-
]
73-
74-
70+
];
7571

7672
export const EditorViewClient = () => {
77-
78-
const editor = usePlateEditor({
79-
plugins: [
80-
...withCustomPlugins([
81-
...CopilotKit,
82-
...EditorKit,
83-
]),
84-
...BlockPlaceholderKit,
85-
86-
createTPlatePlugin({
87-
key: 'ai-test',
88-
render: {
89-
afterEditable: () => <AIChatEditor content={`| Element | Description |
73+
const editor = usePlateEditor(
74+
{
75+
plugins: [
76+
...withCustomPlugins([...CopilotKit, ...EditorKit]),
77+
...BlockPlaceholderKit,
78+
79+
createTPlatePlugin({
80+
key: 'ai-test',
81+
render: {
82+
afterEditable: () => (
83+
<AIChatEditor
84+
content={`| Element | Description |
9085
|------------------|-----------------------------------------------------------------------------|
9186
| Third-level Headings | Provide further content structure and hierarchy. |
9287
| Blockquotes | Perfect for highlighting important information, quotes from external sources, or emphasizing key points in your content. |
9388
| Headings | Create a clear document structure that helps readers navigate your content effectively. |
94-
| Combination | Use headings with blockquotes to emphasize important information. |`} />
95-
}
96-
}),
97-
...MarkdownKit
98-
],
99-
value: value,
100-
}, []);
101-
102-
const getFileNodes = (text: string,) => {
89+
| Combination | Use headings with blockquotes to emphasize important information. |`}
90+
/>
91+
),
92+
},
93+
}),
94+
...MarkdownKit,
95+
],
96+
value: value,
97+
},
98+
[]
99+
);
103100

101+
const getFileNodes = (text: string) => {
104102
return editor.getApi(MarkdownPlugin).markdown.deserialize(text);
105103
};
106104

@@ -111,38 +109,42 @@ export const EditorViewClient = () => {
111109
const text = await plainFiles[0].text();
112110

113111
const nodes = getFileNodes(text);
114-
console.log("🚀 ~ onFilesSelected: ~ nodes:", nodes)
112+
console.log('🚀 ~ onFilesSelected: ~ nodes:', nodes);
115113
},
116114
});
117115

118-
119-
return <>
120-
121-
<Plate editor={editor}>
122-
<EditorContainer>
123-
<Editor
124-
variant="demo"
125-
className="pb-[20vh]"
126-
placeholder="Type something..."
127-
spellCheck={false}
128-
/>
129-
</EditorContainer>
130-
</Plate>
131-
132-
133-
<div className="mt-10 px-10 gap-10 flex">
134-
<Button onClick={
135-
() => {
136-
console.log(editor.getApi(MarkdownPlugin).markdown.serialize());
137-
}
138-
}>Serialize</Button>
139-
140-
141-
<Button onClick={openMdFilePicker}>Deserialize</Button>
142-
143-
<Button onClick={() => {
144-
console.log(editor.children)
145-
}}>Current Value</Button>
146-
</div >
147-
</>
116+
return (
117+
<>
118+
<Plate editor={editor}>
119+
<EditorContainer>
120+
<Editor
121+
variant="demo"
122+
className="pb-[20vh]"
123+
placeholder="Type something..."
124+
spellCheck={false}
125+
/>
126+
</EditorContainer>
127+
</Plate>
128+
129+
<div className="mt-10 flex gap-10 px-10">
130+
<Button
131+
onClick={() => {
132+
console.log(editor.getApi(MarkdownPlugin).markdown.serialize());
133+
}}
134+
>
135+
Serialize
136+
</Button>
137+
138+
<Button onClick={openMdFilePicker}>Deserialize</Button>
139+
140+
<Button
141+
onClick={() => {
142+
console.log(editor.children);
143+
}}
144+
>
145+
Current Value
146+
</Button>
147+
</div>
148+
</>
149+
);
148150
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ export default function DevLayout(props: { children: React.ReactNode }) {
44
<main>{props.children}</main>
55
</>
66
);
7-
}
7+
}

0 commit comments

Comments
 (0)