# slate
文档编辑器
在 Slate 编辑器中,renderElement 函数接收的 props 是由 Slate 编辑器根据当前的编辑状态(包括初始值或用户操作后的状态)生成的。这些 props 包含了 Slate 编辑器内部表示的元素的属性和子元素信息,这些信息被封装成 React 组件可以理解的格式。
具体来说,renderElement 函数接收到的 props 对象通常包含以下关键属性:
- attributes: 包含当前元素的 HTML 属性(如 style、className 等)。这些属性由 Slate 编辑器管理,并根据元素的类型和状态动态更新。
- children: 表示当前元素内部的文本或其他嵌套元素。在 Slate 中,文本和元素都被视为节点(Node),而 children 属性通常是一个数组,包含了当前元素下的所有子节点。
- element: 代表当前正在渲染的 Slate 元素节点。这个对象包含了元素的类型(type)和其他可能的属性,这些属性由您定义或根据编辑器的状态动态生成。 在您的代码中,renderElement 函数根据 element.type 来决定渲染哪种类型的元素。对于类型为 code 的元素,它使用 CodeElement 组件来渲染;对于其他类型的元素,则使用 DefaultElement 组件。
此外,initialValue 您提供的初始值是一个 Slate 可以理解的节点数组,当 Slate 编辑器初始化时,它会根据这个初始值构建内部的文档表示,并生成相应的 React 组件树来渲染这个文档。用户进行编辑操作时,Slate 编辑器会更新其内部状态,并触发 React 的重新渲染机制来更新用户界面。
总之,renderElement 函数中的 props 是由 Slate 编辑器根据当前的编辑状态(包括初始值和用户操作)生成的,它封装了 Slate 编辑器内部表示的元素的属性和子元素信息,以便在 React 组件中进行渲染。
// import logo from './logo.svg';
// import './App.css';
// Import React dependencies.
import React, { useState,useCallback } from 'react'
// Import the Slate editor factory.
import { createEditor,Transforms,Editor,Element } from 'slate'
// Import the Slate components and React plugin.
import { Slate, Editable, withReact } from 'slate-react'
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' },{ text: '测试用例' }],
},
]
// Define a React component renderer for our code blocks.
const CodeElement = props => {
return (
<pre {...props.attributes}>
<code>{props.children}</code>
</pre>
)
}
const DefaultElement = props => {
console.log(props)
console.log(props.children)
return <p {...props.attributes}>{props.children}</p>
}
function App() {
const [editor] = useState(() => withReact(createEditor()))
const renderElement = useCallback(props => {
console.log(props)
switch (props.element.type) {
case 'code':
return <CodeElement {...props} />
default:
return <DefaultElement {...props} />
}
}, [])
return (<Slate editor={editor} initialValue={initialValue} >
<Editable
renderElement = {renderElement}
onKeyDown={event => {
console.log(event.key)
if (event.key === '&') {
// Prevent the ampersand character from being inserted.
event.preventDefault()
// Execute the `insertText` method when the event occurs.
editor.insertText('and')
}
if (event.key === '`' && event.ctrlKey) {
// Prevent the "`" from being inserted by default.
event.preventDefault()
// Otherwise, set the currently selected blocks type to "code".
Transforms.setNodes(
editor,
{ type: 'code' },
{ match: n => {
console.log(n)
console.log('isElement',Element.isElement(n))
console.log('block',Editor.isBlock(editor, n))
return Element.isElement(n) && Editor.isBlock(editor, n)
} }
)
}
}}
/>
</Slate>)
}
export default App;
# 更多的配置
// import logo from './logo.svg';
// import './App.css';
// Import React dependencies.
import React, { useState,useCallback } from 'react'
// Import the Slate editor factory.
import { createEditor,Transforms,Editor,Element } from 'slate'
// Import the Slate components and React plugin.
import { Slate, Editable, withReact } from 'slate-react'
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' },{ text: '测试用例' }],
},
{
type: 'code',
children: [{ text: 'code' }],
},
{
type: 'custom-type',
children: [{ text: 'A line of text in a paragraph.' },{ text: '测试用例' }],
},
]
// Define a React component renderer for our code blocks.
const CodeElement = props => {
return (
<pre {...props.attributes}>
<code>{props.children}</code>
</pre>
)
}
const DefaultElement = props => {
// console.log(props)
// console.log(props.children)
return <p {...props.attributes}>{props.children}</p>
}
// Define a React component to render leaves with bold text.
const Leaf = props => {
return (
<span
{...props.attributes}
style={{ fontWeight: props.leaf.bold ? 'bold' : 'normal' }}
>
{props.children}
</span>
)
}
function App() {
const [editor] = useState(() => withReact(createEditor()))
const renderElement = useCallback(props => {
// console.log(props)
switch (props.element.type) {
case 'code':
return <CodeElement {...props} />
case 'custom-type':
return <span {...props.attributes}>{props.children}</span>
default:
return <DefaultElement {...props} />
}
}, [])
// Define a leaf rendering function that is memoized with `useCallback`.
const renderLeaf = useCallback(props => {
return <Leaf {...props} />
}, [])
return (<Slate editor={editor} initialValue={initialValue} >
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={event => {
if (!event.ctrlKey) {
return
}
switch (event.key) {
// When "`" is pressed, keep our existing code block logic.
case '`': {
event.preventDefault()
const [match] = Editor.nodes(editor, {
match: n => n.type === 'code',
})
Transforms.setNodes(
editor,
{ type: match ? 'paragraph' : 'code' },
{ match: n => Editor.isBlock(editor, n) }
)
break
}
// When "B" is pressed, bold the text in the selection.
case 'b': {
event.preventDefault()
Editor.addMark(editor, 'bold', true)
break
}
}
}}
/>
</Slate>)
}
export default App;
# 自定义封装
// import logo from './logo.svg';
// import './App.css';
// Import React dependencies.
import React, { useState,useCallback } from 'react'
// Import the Slate editor factory.
import { createEditor,Transforms,Editor,Element } from 'slate'
// Import the Slate components and React plugin.
import { Slate, Editable, withReact } from 'slate-react'
const CustomEditor = {
isBoldMarkActive(editor) {
const marks = Editor.marks(editor)
return marks ? marks.bold === true : false
},
isCodeBlockActive(editor) {
const [match] = Editor.nodes(editor, {
match: n => n.type === 'code',
})
return !!match
},
toggleBoldMark(editor) {
const isActive = CustomEditor.isBoldMarkActive(editor)
if (isActive) {
Editor.removeMark(editor, 'bold')
} else {
Editor.addMark(editor, 'bold', true)
}
},
toggleCodeBlock(editor) {
const isActive = CustomEditor.isCodeBlockActive(editor)
Transforms.setNodes(
editor,
{ type: isActive ? null : 'code' },
{ match: n => Editor.isBlock(editor, n) }
)
},
}
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' },{ text: '测试用例' }],
},
{
type: 'code',
children: [{ text: 'code' }],
},
{
type: 'custom-type',
children: [{ text: 'A line of text in a paragraph.' },{ text: '测试用例' }],
},
]
// Define a React component renderer for our code blocks.
const CodeElement = props => {
return (
<pre {...props.attributes}>
<code>{props.children}</code>
</pre>
)
}
const DefaultElement = props => {
// console.log(props)
// console.log(props.children)
return <p {...props.attributes}>{props.children}</p>
}
// Define a React component to render leaves with bold text.
const Leaf = props => {
return (
<span
{...props.attributes}
style={{ fontWeight: props.leaf.bold ? 'bold' : 'normal' }}
>
{props.children}
</span>
)
}
function App() {
const [editor] = useState(() => withReact(createEditor()))
const renderElement = useCallback(props => {
// console.log(props)
switch (props.element.type) {
case 'code':
return <CodeElement {...props} />
case 'custom-type':
return <span {...props.attributes}>{props.children}</span>
default:
return <DefaultElement {...props} />
}
}, [])
// Define a leaf rendering function that is memoized with `useCallback`.
const renderLeaf = useCallback(props => {
return <Leaf {...props} />
}, [])
return (<Slate editor={editor} initialValue={initialValue} >
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={event => {
if (!event.ctrlKey) {
return
}
// Replace the `onKeyDown` logic with our new commands.
switch (event.key) {
case '`': {
event.preventDefault()
CustomEditor.toggleCodeBlock(editor)
break
}
case 'b': {
event.preventDefault()
CustomEditor.toggleBoldMark(editor)
break
}
}
}}
/>
</Slate>)
}
export default App;
# Editor
const pointBefore = Editor.before(editor, {
path: [0, 0], // 例如,文档的第一个文本节点的路径
offset: 5, // 例如,想要获取该文本节点中第 5 个字符之前的位置
});
// pointBefore 应该表示第 5 个字符之前的位置,即第 4 个字符后的位置
// 因此,offset 应该是 4
console.log(pointBefore);
// 输出可能类似于:{ path: [0, 0], offset: 4 }
# Transforms
setTimeout(() => {
Transforms.setSelection(editor, {
anchor: { path: [editor.selection.focus.path[0] + 1, 0], offset: 0 },
focus: { path: [editor.selection.focus.path[0] + 1, 0], offset: 1 },
})
}, 1000);
# 插件扩展
为了避免手写A(B(C(D(E(F(someResult()))))))对插件的增强,可以采取以下代码去处理
type PluginProcessor<T = any> = (consumer: T) => T
const applyMiddleware = (processor: any, middleware: PluginProcessor) => {
return middleware(processor)
}
export const createProcessorChain = (
initialProcessor: any,
middlewares: PluginProcessor[]
): any => {
let processed = initialProcessor
// 使用中间件链式处理初始处理器
for (const middleware of middlewares) {
processed = applyMiddleware(processed, middleware)
}
return processed
}
# slatejs自定义某个类型的插件如加载image
// Import React dependencies.
import React, { useState, useCallback } from "react";
// Import the Slate editor factory.
import { createEditor, Transforms, Editor, Element } from "slate";
// Import the Slate components and React plugin.
import { Slate, Editable, withReact } from "slate-react";
const initialValue = [
{
type: "paragraph",
children: [
{ text: "A line of text in a paragraph." },
{ text: "测试用例" },
],
},
{
type: "code",
children: [{ text: "code" }],
},
{
type: "custom-type",
url: "https://weishell.github.io/img/logo.jpg",
children: [
{ text: "A line of text in a paragraph.", type: "zero" },
{ text: "" },
{ text: "测试用例2", type: "zero" },
],
},
];
// 这种写法子节点不会走leaf
// const CustomContainer = (props) => {
// const { attributes, children, element } = props
// // 处理混合内容
// const processedChildren = element.children.map((child, index) => {
// // 如果是zero类型,渲染普通文本
// if (child.type === 'zero') {
// return (
// <span key={index} style={{ color: 'red' }}>
// {child.text}
// </span>
// )
// }
// // 没有type且存在url时,渲染图片
// if (!child.type && element.url) {
// return (
// <img
// key={index}
// src={element.url}
// style={{ maxWidth: '100%', display: 'block', margin: '10px 0' }}
// alt="custom"
// />
// )
// }
// // 默认情况
// return <span key={index}>{child.text}</span>
// })
// return (
// <div {...attributes} style={{ border: '2px dashed #ccc', padding: 10 }}>
// {processedChildren}
// {/* 保留Slate的children占位 */}
// {children}
// </div>
// )
// }
// 这种写法子节点会走leaf
const CustomContainer = (props) => {
const { attributes, children, element } = props;
const childrenArray = React.Children.toArray(children);
const processedChildren = childrenArray.map((child, index) => {
// 判断是否是空文本节点
if (element.children[index].text === "" && !element.children[index].type) {
return (
<img
key={index}
src={element.url}
style={{ maxWidth: "100%", display: "block", margin: "10px 0" }}
alt="custom"
/>
);
}
return child;
});
return (
<div {...attributes} style={{ border: "2px dashed #ccc", padding: 10 }}>
{processedChildren}
</div>
);
};
// Define a React component renderer for our code blocks.
const CodeElement = (props) => {
return (
<pre {...props.attributes}>
<code>{props.children}</code>
</pre>
);
};
const DefaultElement = (props) => {
// console.log(props)
// console.log(props.children)
return <p {...props.attributes}>{props.children}</p>;
};
// Define a React component to render leaves with bold text.
const Leaf = (props) => {
return (
<span
{...props.attributes}
style={{ fontWeight: props.leaf.bold ? "bold" : "normal" }}
>
{props.children}--测试走leaf
</span>
);
};
function App() {
const [editor] = useState(() => withReact(createEditor()));
const renderElement = useCallback((props) => {
// console.log(props)
switch (props.element.type) {
case "code":
return <CodeElement {...props} />;
case "custom-type":
return <CustomContainer {...props} />;
default:
return <DefaultElement {...props} />;
}
}, []);
// Define a leaf rendering function that is memoized with `useCallback`.
const renderLeaf = useCallback((props) => {
return <Leaf {...props} />;
}, []);
return (
<Slate editor={editor} initialValue={initialValue}>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={(event) => {
if (!event.ctrlKey) {
return;
}
switch (event.key) {
// When "`" is pressed, keep our existing code block logic.
case "`": {
event.preventDefault();
const [match] = Editor.nodes(editor, {
match: (n) => n.type === "code",
});
Transforms.setNodes(
editor,
{ type: match ? "paragraph" : "code" },
{ match: (n) => Editor.isBlock(editor, n) }
);
break;
}
// When "B" is pressed, bold the text in the selection.
case "b": {
event.preventDefault();
Editor.addMark(editor, "bold", true);
break;
}
}
}}
/>
</Slate>
);
}
export default App;
renderElement 的优化很重要,否则会触发很多次的无效调用.