ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â ð shadcn/directory/udecode/plate/migration/v48.cn â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â
PlateElementãPlateLeaf å PlateText ç HTML 屿§ä»é¡¶å± props ç§»è³ attributes propïŒé€äº classNameãstyle å asãè¿ç§»æ¹åŒïŒ// ä¹å
<PlateElement
{...props}
ref={ref}
contentEditable={false}
>
{children}
</PlateElement>
// ä¹å
<PlateElement
{...props}
ref={ref}
attributes={{
...props.attributes,
contentEditable: false,
}}
>
{children}
</PlateElement>
PlateElementãPlateLeafãPlateText ç nodeProps propïŒå·²åå¹¶è³ attributes propãnode.props åºçŽæ¥è¿å props èéå
è£¹åš nodeProps 对象äžãè¿ç§»æ¹åŒïŒ// ä¹å
node: {
props: ({ element }) => ({
nodeProps: {
colSpan: element?.attributes?.colspan,
rowSpan: element?.attributes?.rowspan,
},
});
}
// ä¹å
node: {
props: ({ element }) => ({
colSpan: element?.attributes?.colspan,
rowSpan: element?.attributes?.rowspan,
});
}
PlateElementãPlateLeafãPlateText ç asChild propïŒæ¹çš as propãPlateElementãPlateLeafãPlateText ç elementToAttributesãleafToAttributesãtextToAttributes propsãDefaultElementãDefaultLeafãDefaultTextïŒæ¹çš PlateElementãPlateLeafãPlateTextãPlateRenderElementPropsãPlateRenderLeafPropsãPlateRenderTextPropsïŒæ¹çš PlateElementPropsãPlateLeafPropsãPlateTextPropsãPlateElementãPlateLeafãPlateText ç§»è³ @udecode/plate-coreãè¥ä» @udecode/plate 富å
¥åæ éè¿ç§»ã#4225 by @bbyiringiro â
hocuspocusProviderOptions æ¿æ¢äžºæ°ç providers æ°ç»ã瀺äŸåŠäžãä¹åïŒ
YjsPlugin.configure({
options: {
cursorOptions: {
/* ... */
},
hocuspocusProviderOptions: {
url: 'wss://hocuspocus.example.com',
name: 'document-1',
// ... å
¶ä» Hocuspocus é项
},
},
});
ä¹åïŒä» HocuspocusïŒïŒ
YjsPlugin.configure({
options: {
cursors: {
/* ... */
},
providers: [
{
type: 'hocuspocus',
options: {
url: 'wss://hocuspocus.example.com',
name: 'document-1',
// ... å
¶ä» Hocuspocus é项
},
},
],
},
});
ä¹åïŒHocuspocus + WebRTCïŒïŒ
YjsPlugin.configure({
options: {
cursors: {
/* ... */
},
providers: [
{
type: 'hocuspocus',
options: {
url: 'wss://hocuspocus.example.com',
name: 'document-1',
},
},
{
type: 'webrtc',
options: {
roomName: 'document-1',
// signaling: ['wss://signaling.example.com'], // å¯é
},
},
],
},
});
UnifiedProvider æ¥å£ïŒæ¯æèªå®ä¹ provider å®ç°ïŒåŠ IndexedDB çšäºçŠ»çº¿æä¹
åïŒãcursorOptions éåœå䞺 cursorsãyjsOptions åå¹¶è³ optionsã
yjsOptions äžçéé¡¹çŽæ¥ç§»è³äž» options 对象ãYjsAboveEditableãç°åšéèŠæåšè°çš init å destroyïŒReact.useEffect(() => {
if (!mounted) return;
// åå§å Yjs è¿æ¥å忥
editor.getApi(YjsPlugin).yjs.init({
id: roomName, // æäœ çææ¡£æ è¯ç¬Š
value: INITIAL_VALUE, // çŒèŸåšåå§å
容
});
// ç»ä»¶åžèœœæ¶éæ¯è¿æ¥
return () => {
editor.getApi(YjsPlugin).yjs.destroy();
};
}, [editor, mounted, roomName]); // æ·»å çžå
³äŸèµ
#4174 by @felixfeng33 â #### æ°ç¹æ§
<u>underline</u>slate nodes => MDAST nodes => markdown stringallowedNodesïŒçœååç¹å®èç¹disallowedNodesïŒé»ååç¹å®èç¹allowNodeïŒèªå®ä¹è¿æ»€åœæ°rules é项çšäºèªå®ä¹åºåååååºååè§åïŒå
æ¬èªå®ä¹ mdx æ¯æremarkPlugins éé¡¹ä»¥äœ¿çš remark æä»¶æä»¶é项
ç§»é€çé项ïŒ
elementRules æ¹çš rulestextRules æ¹çš rulesindentList ç°èªå𿣿µæ¯åŠäœ¿çšäº IndentList æä»¶splitLineBreaks ä»
çšäºååºååelementRules å textRules é项
rules.key.deserializeè¿ç§»ç€ºäŸïŒ
export const markdownPlugin = MarkdownPlugin.configure({
options: {
disallowedNodes: [SuggestionPlugin.key],
rules: {
// å¯¹åº textRules
[BoldPlugin.key]: {
mark: true,
deserialize: (mdastNode) => ({
bold: true,
text: node.value || '',
}),
},
// å¯¹åº elementRules
[EquationPlugin.key]: {
deserialize: (mdastNode, options) => ({
children: [{ text: '' }],
texExpression: node.value,
type: EquationPlugin.key,
}),
},
},
remarkPlugins: [remarkMath, remarkGfm],
},
});
editor.api.markdown.deserialize äžç processor
remarkPluginsserializeMdNodes
editor.markdown.serialize({ value: nodes })SerializeMdOptions å éçšæ°åºååæµçš
slate nodes => mdslate nodes => md-ast => mdnodesbreakTagcustomNodesignoreParagraphNewlinelistDepthmarkFormatsulListStyleTypesignoreSuggestionTypeè¿ç§» SerializeMdOptions.customNodes å SerializeMdOptions.nodes ç瀺äŸïŒ
export const markdownPlugin = MarkdownPlugin.configure({
options: {
rules: {
// å¿œç¥ææ `insert` ç±»åç建议
[SuggestionPlugin.key]: {
mark: true,
serialize: (slateNode: TSuggestionText, options): mdast.Text => {
const suggestionData = options.editor
.getApi(SuggestionPlugin)
.suggestion.suggestionData(node);
return suggestionData?.type === 'insert'
? { type: 'text', value: '' }
: { type: 'text', value: node.text };
},
},
// å¯¹åº elementRules
[EquationPlugin.key]: {
serialize: (slateNode) => ({
type: 'math',
value: node.texExpression,
}),
},
},
remarkPlugins: [remarkMath, remarkGfm],
},
});
#4122 by @zbeyens â ä» prismjs è¿ç§»è³ highlight.js + lowlight å®ç°è¯æ³é«äº®ã
CodeBlockPluginïŒç§»é€ prism éé¡¹ïŒæ¹çš lowlight é项ïŒimport { all, createLowlight } from 'lowlight';
const lowlight = createLowlight(all);
CodeBlockPlugin.configure({
options: {
lowlight,
},
});
defaultLanguagesyntax é项ãçç¥ lowlight é项å³å¯çŠçšè¯æ³é«äº®ãsyntaxPopularFirst é项ãåšèªå®ä¹ç»ä»¶äžæ§å¶æ€è¡äžºãuseCodeBlockComboboxãuseCodeBlockElementãuseCodeSyntaxLeafãuseToggleCodeBlockButtonãçžå
³é»èŸå·²ç§»è³ç»ä»¶äžã#4064 by @felixfeng33 â æ¬æ¬¡éåç§»é€äºè¯è®ºæä»¶ç UI é»èŸïŒæ 倎åïŒã
æä»¶é项
options.commentsoptions.myUserIdoptions.usersç»ä»¶
CommentDeleteButtonCommentEditActionsCommentEditButtonCommentEditCancelButtonCommentEditSaveButtonCommentEditTextareaCommentNewSubmitButtonCommentNewTextareaCommentResolveButtonCommentsPositionerCommentUserNameAPI
findCommentNode â api.comment.node()findCommentNodeById â api.comment.node({ id })getCommentNodeEntries â api.comment.nodes()getCommentNodesById â api.comment.nodes({ id })removeCommentMark â tf.comment.remove()unsetCommentNodesById â tf.comment.unsetMark({ id })getCommentFragmentgetCommentUrlgetElementAbsolutePositiongetCommentPositiongetCommentCount 以æé€èçš¿è¯è®ºç¶æç®¡ç
CommentProvider - çšæ·åºèªè¡å®ç°ç¶æç®¡ç â block-discussion.tsxuseHooksComments ç§»è³ UI 泚å衚 â comments-plugin.tsxuseActiveCommentNodeuseCommentsResolveduseCommentAddButtonuseCommentItemContentuseCommentLeafuseCommentsShowResolvedButtonuseFloatingCommentsContentStateuseFloatingCommentsStateç±»å
CommentUserTComment ç§»è³ UI 泚å衚 â comment.tsx#4064 by @felixfeng33 â æ³šæïŒæ€æä»¶ç®åå€äºå®éªé¶æ®µïŒå¯èœåšäžåçº§äž»çæ¬çæ åµäžåŒå ¥ç Žåæ§åæŽã
findSuggestionNode æ¹çš findSuggestionProps.tsaddSuggestionMark.tsuseHooksSuggestion.ts å æä»¬å·²æŽæ° activeId é»èŸäžåäŸèµ useEditorSelectorzustand-x@6
eventEditorSelectors -> EventEditorStore.geteventEditorActions -> EventEditorStore.setuseEventEditorSelectors -> useEventEditorValue(key)jotai-x@2
usePlateEditorStore -> usePlateStoreusePlateActions -> usePlateSeteditor.setPlateStateïŒæ¹çš usePlateSetusePlateSelectors -> usePlateValueusePlateStates -> usePlateStateeditor.useOption, ctx.useOption -> usePluginOption(plugin, key, ...args)editor.useOptions, ctx.useOptions -> usePluginOption(plugin, 'state')usePluginOptions(plugin, selector) çšäºéæ©æä»¶é项ïŒZustand æ¹åŒïŒãextendOptions åæä»¶æ·»å éæ©åšãè¿äºéæ©åšäžéé¡¹ç¶ææ··åïŒå¯èœå¯ŒèŽå²çªåæ··æ·ã
extendSelectorsplugin.selectors èé plugin.options äžïŒäœè®¿é®æ¹åŒäžåïŒäœ¿çš editor.getOption(plugin, 'selectorName')ãctx.getOption('selectorName') æäžè¿°é©åãPluginConfig ç第äºäžªæ³ååæ°äžïŒæä»¬æ°å¢ç¬¬äºäžªæ³ååæ°çšäºæ€ã// ä¹åïŒ
export type BlockSelectionConfig = PluginConfig<
'blockSelection',
{ selectedIds?: Set<string>; } & BlockSelectionSelectors,
>;
// ä¹åïŒ
export type BlockSelectionConfig = PluginConfig<
'blockSelection',
{ selectedIds?: Set<string>; },
{}, // API
{}, // Transforms
BlockSelectionSelectors, // Selectors
}>
â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ