插件
您已经看到了如何覆盖 Slate 编辑器的行为。这些覆盖也可以打包成 "插件",以便重用、测试和共享。这是 Slate 架构中最强大的方面之一。
插件简单地是一个接受 Editor 对象并在某种方式上增强它后返回它的函数。
例如,一个将图像节点标记为 "void" 的插件:
javascript
const withImages = editor => {
const { isVoid } = editor
editor.isVoid = element => {
return element.type === 'image' ? true : isVoid(element)
}
return editor
}
然后要使用插件,简单地:
javascript
import { createEditor } from 'slate'
const editor = withImages(createEditor())
这种插件组合模型使得 Slate 极易扩展!
辅助函数
除了插件函数之外,您可能还想公开与您的插件一起使用的辅助函数。例如:
javascript
import { Editor, Element } from 'slate'
const MyEditor = {
...Editor,
insertImage(editor, url) {
const element = { type: 'image', url, children: [{ text: '' }] }
Transforms.insertNodes(editor, element)
},
}
const MyElement = {
...Element,
isImageElement(value) {
return Element.isElement(element) && element.type === 'image'
},
}
然后您可以在任何地方使用 MyEditor 和 MyElement,并在一个地方访问到所有的辅助函数。
渲染
Slate
最棒的部分之一是它构建在 React
上,因此它可以完美地适应您现有的应用程序。它不会重新发明自己的视图层,您不必学习新的东西。它尽可能地保持与 React
的一致性。
为此,Slate
允许您控制自定义节点和属性在您的富文本领域中的渲染行为。
您可以通过向顶级 <Editable>
组件传递 "render props"
来定义这些行为。
例如,如果您想要渲染自定义元素组件,您可以传递 renderElement prop
:
javascript
import { createEditor } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
const MyEditor = () => {
const [editor] = useState(() => withReact(createEditor()))
const renderElement = useCallback(({ attributes, children, element }) => {
switch (element.type) {
case 'quote':
return <blockquote {...attributes}>{children}</blockquote>
case 'link':
return (
<a {...attributes} href={element.url}>
{children}
</a>
)
default:
return <p {...attributes}>{children}</p>
}
}, [])
return (
<Slate editor={editor}>
<Editable renderElement={renderElement} />
</Slate>
)
}
请确保在自定义组件中混合使用
props.attributes
和render props.children
!这些attributes
必须添加到组件内部的顶级DOM
元素中,因为它们是Slate
的DOM
帮助函数所必需的。而children
则是文本内容和内联元素所持有的"leaves"
。
您不必使用简单的 HTML
元素,您也可以使用自己的自定义 React
组件:
javascript
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'quote':
return <QuoteElement {...props} />
case 'link':
return <LinkElement {...props} />
default:
return <DefaultElement {...props} />
}
}, [])
叶子
当渲染文本级别的格式时,字符被分组为每个具有相同格式(标记)的文本 "leaves"
。
要自定义每个叶子的渲染,您可以使用自定义 renderLeaf prop
:
javascript
const renderLeaf = useCallback(({ attributes, children, leaf }) => {
return (
<span
{...attributes}
style={{
fontWeight: leaf.bold ? 'bold' : 'normal',
fontStyle: leaf.italic ? 'italic' : 'normal',
}}
>
{children}
</span>
)
}, [])
请注意,我们处理它的方式与 renderElement
稍有不同。由于文本格式化通常相对简单,我们选择放弃 switch
语句,而只是切换一些样式开关。(但是,如果您愿意,您也可以使用自定义组件!)
与 Element 渲染器一样,确保在叶子渲染器中混合使用
props.attributes
和render props.children
!这些attributes
必须添加到组件内部的顶级DOM
元素中,因为它们是Slate
的DOM
帮助函数所必需的。而children
则是Slate
为您自动管理的文档的实际文本内容。
文本级别的格式化的一个缺点是您不能保证任何给定格式是 "连续的" ------ 也就是说它会作为一个单独的叶子保留。这与叶子相关的限制类似于 DOM
,其中这是无效的:
html
<em>t<strong>e</em>x</strong>t
上面示例中的元素未正确关闭,因此无效。相反,您应该按以下方式编写上面的 HTML:
html
<em>t</em><strong><em>e</em>x</strong>t
如果您还添加了另一个重叠的 <strike>
部分到该文本中,您可能不得不再次调整闭合标签。在 Slate
中渲染叶子是类似的------您不能保证即使一个单词具有一种格式,该叶子也是连续的,因为它取决于它与其他格式的重叠方式。
当然,这个叶子的东西听起来很复杂。但是,只要您将文本级别的格式化和元素级别的格式化用于其预期目的,就不必过多考虑它:
- 文本属性用于非连续的、字符级别的格式化。
- 元素属性用于文档中连续的、语义化的元素。
装饰
装饰是另一种文本级别的格式化。它们与普通的自定义属性类似,只是每个装饰应用于文档的一个 Range
范围,而不是与给定文本节点关联。
然而,装饰是在渲染时基于内容本身计算的。这对于动态格式化(如语法高亮或搜索关键字)非常有帮助,因为内容的更改(或一些外部数据)可能会改变格式化。
装饰与 Marks
不同之处在于它们不存储在编辑器状态中。
工具栏、菜单、覆盖等等!
除了控制 Slate
内部节点的渲染之外,您还可以使用 useSlate hook
从其他组件中检索当前编辑器上下文。
这样,其他组件就可以执行命令、查询编辑器状态或执行其他任何操作。
一个常见的用例是渲染一个工具栏,其中的格式按钮基于当前选择而高亮:
javascript
const MyEditor = () => {
const [editor] = useState(() => withReact(createEditor()))
return (
<Slate editor={editor}>
<Toolbar />
<Editable />
</Slate>
)
}
const Toolbar = () => {
const editor = useSlate()
return (
<div>
<Button active={isBoldActive(editor)}>B</Button>
<Button active={isItalicActive(editor)}>I</Button>
</div>
)
}
因为 <Toolbar>
使用 useSlate
hook
检索上下文,所以当编辑器更改时它会重新渲染,这样按钮的活动状态就保持同步。
编辑器样式
可以通过在 <Editable>
组件上使用 style prop
来为编辑器自定义样式。
javascript
const MyEditor = () => {
const [editor] = useState(() => withReact(createEditor()))
return (
<Slate editor={editor}>
<Editable style={{ minHeight: '200px', backgroundColor: 'lime' }} />
</Slate>
)
}
也可以使用样式表和 className
来应用自定义样式。但是,Slate
使用内联样式为编辑器提供了一些默认样式。由于内联样式优先于样式表,您使用样式表提供的样式将不会覆盖默认样式。如果您尝试使用样式表,但规则没有生效,请执行以下操作之一:
使用 style prop
而不是样式表来提供您的样式,这样会覆盖默认的内联样式。
将 disableDefaultStyles
prop
传递给 <Editable>
组件。
在样式表声明中使用 !important
,使其覆盖内联样式。