执行命令
直到现在,我们学到的都是为特定编辑器编写一次性逻辑。但是 Slate 最强大的功能是可以根据个人喜好对富文本编辑器“域”进行建模,并减少一次性代码的编写。
在之前的教程中,编写了一些有用的代码来处理格式化代码块以及粗体标记。通过添加 onKeyDown
处理程序来调用代码。但这都是使用内置 Editor
助手来实现,而不是使用“命令”。
Slate 允许增强 editor
对象以处理自定义富文本命令。甚至可以使用预打包的“插件”来添加一组指定功能。
下面看看如何操作。
先从之前的应用开始:
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]
const App = () => {
const [editor] = useState(() => withReact(createEditor()))
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
return <CodeElement {...props} />
default:
return <DefaultElement {...props} />
}
}, [])
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) {
case '`': {
event.preventDefault()
const [match] = Editor.nodes(editor, {
match: n => n.type === 'code',
})
Transforms.setNodes(
editor,
{ type: match ? null : 'code' },
{ match: n => Editor.isBlock(editor, n) }
)
break
}
case 'b': {
event.preventDefault()
Editor.addMark(editor, 'bold', true)
break
}
}
}}
/>
</Slate>
)
}
它具有“代码块”和“粗体格式”的概念。但这些东西都是在 onKeyDown
处理程序中一次性定义的。如果想在其它地方复用该逻辑,需要对其进行提取。
可以通过创建自定义助手函数来实现这些特定领域的概念:
// 定义一组自定义助手。
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.' }],
},
]
const App = () => {
const [editor] = useState(() => withReact(createEditor()))
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
return <CodeElement {...props} />
default:
return <DefaultElement {...props} />
}
}, [])
const renderLeaf = useCallback(props => {
return <Leaf {...props} />
}, [])
return (
<Slate editor={editor} initialValue={initialValue}>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={event => {
if (!event.ctrlKey) {
return
}
// 使用新命令替换 `onKeyDown` 逻辑。
switch (event.key) {
case '`': {
event.preventDefault()
CustomEditor.toggleCodeBlock(editor)
break
}
case 'b': {
event.preventDefault()
CustomEditor.toggleBoldMark(editor)
break
}
}
}}
/>
</Slate>
)
}
现在命令已经明确定义,可以从任何可以访问 editor
对象的地方调用它们。比如,从工具栏按钮:
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]
const App = () => {
const [editor] = useState(() => withReact(createEditor()))
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
return <CodeElement {...props} />
default:
return <DefaultElement {...props} />
}
}, [])
const renderLeaf = useCallback(props => {
return <Leaf {...props} />
}, [])
return (
// 添加工具栏,带有调用相同方法的按钮。
<Slate editor={editor} initialValue={initialValue}>
<div>
<button
onMouseDown={event => {
event.preventDefault()
CustomEditor.toggleBoldMark(editor)
}}
>
Bold
</button>
<button
onMouseDown={event => {
event.preventDefault()
CustomEditor.toggleCodeBlock(editor)
}}
>
Code Block
</button>
</div>
<Editable
editor={editor}
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={event => {
if (!event.ctrlKey) {
return
}
switch (event.key) {
case '`': {
event.preventDefault()
CustomEditor.toggleCodeBlock(editor)
break
}
case 'b': {
event.preventDefault()
CustomEditor.toggleBoldMark(editor)
break
}
}
}}
/>
</Slate>
)
}
这就是提取逻辑的好处。
这很简单!只是使用了很小的工作量在编辑器中添加了大量的功能。可以将命令逻辑在一个地方测试和隔离,使代码更易于维护。