定制默认编辑器
这一页收齐所有不必放弃默认实现就能用上的改造手段,按侵入程度从浅到深排列: 调样式配置、换主题、往侧边栏和节点上加按钮、替换个别渲染器, 最后是完全不用这一层时该从哪里接手。
样式配置
config 参数接受一份部分配置,只写想改的项,其余沿用默认值。
编辑器的配置分三块:
| 字段 | 内容 |
|---|---|
margins | background(纸张与内部元素的距离)、paragraph(段落间距)、small(小间隔) |
widths | editable_drawer(弹出抽屉的宽度)、minimum_content(有内容元素的最小宽度) |
fonts | body(正文)、structure(结构性文字,比如节点标题)、info(提示性文字) |
每种字体是一组 fontFamily、fontSize、lineHeight 这样的字段。
三种字体的分工是:body 是写作者输入的内容,
structure 是界面自己产生的文字(节点标题、按钮上的字),
info 是辅助提示。
<DefaultEditorComponent
editorcore={editor_core}
config={{
margins: {paragraph: "1rem"},
fonts : {body: {fontFamily: "Source Han Serif", fontSize: "1.1rem"}},
}}
/>
这套配置和印刷器的配置是分开的两份,因为编辑界面和印刷成品本来就应该长得不一样: 编辑时你可能想要更大的行距方便点选,成品则要紧凑美观。印刷侧的配置见 印刷侧的默认实现。
界面文字
默认实现界面上的全部文字(按钮提示、面板标题、操作后弹出的提示)都集中在一份文案表里,
默认是中文,通过 texts 参数部分覆盖,写法和 config 一样:
<DefaultEditorComponent
editorcore={editor_core}
texts={{
buttons: {delete_node: "Delete node", unwrap_node: "Unwrap node"},
areas : {concept_title: "Insert Concept", parameter_title: "Edit Parameters"},
}}
/>
完整的字段见 EditorTexts 类型,分成 buttons(按钮提示)、
areas(面板标题和概念类型标签)、messages(提示语)三块。
库本身不带任何 i18n 框架,所以你用什么方案管理翻译都可以,把最终的字符串传进来即可。
在线演示就是这么做的:中文用库的默认值,英文传一份覆盖,见
demo/src/editor_texts.ts。
主题
默认实现全程使用 MUI 的主题:颜色、圆角、按钮样式都从主题里取,自己不写死任何颜色。
所以换配色只需要在外层套一个 ThemeProvider,包括深色模式在内,
编辑器和印刷器都会跟着变:
<ThemeProvider theme={createTheme({palette: {mode: "dark"}})}>
<DefaultEditorComponent editorcore={editor_core} />
</ThemeProvider>
配置和主题的分工是:主题管颜色和控件外观,配置管字体、边距和宽度。 想改「定理块的背景色」找主题,想改「段落之间空多少」找配置。
侧边栏按钮与尾部元素
sidebar_extras 接受一个组件数组,追加到侧边栏那四个内置按钮之后,
中间自动加一条分隔线。追加的按钮会自动加入键盘导航(按住 Alt+E 后用上下方向键遍历),
不需要额外登记。按钮内部可以用 useEditor() 拿到编辑器:
import { useEditor, AutoIconButton } from "@project-callio/calliotext"
import { Download as DownloadIcon } from "lucide-react"
function ExportButton(){
const editor = useEditor()
return <AutoIconButton
icon={DownloadIcon} title="导出" size="medium"
onClick={() => {
const root = editor.get_root()
console.log(JSON.stringify(root))
}}
/>
}
<DefaultEditorComponent editorcore={editor_core} sidebar_extras={[ExportButton]} />
AutoIconButton 是默认实现自己也在用的图标按钮,它会自动接上按键提示和主题,
用它做出来的按钮和内置按钮长得一样。当然你也可以直接返回一个 MUI 的
IconButton,只是提示和样式要自己管。
end_element 则是插在纸张下方的任意内容,适合放状态栏、字数统计这类东西。
节点上的按钮
侧边栏按钮作用于整个文档,如果你要的是「作用于某一个节点」的操作,
那应该加到节点自己的按钮组里,也就是渲染器工厂的 buttons_extra 选项。
这类按钮里可以用 useNode() 和 useParameters() 拿到所属的节点:
function MarkDoneButton(){
const editor = useEditor()
const node = useNode()
return <AutoIconButton
icon={CheckIcon} title="标记完成" size="medium"
onClick={() => {
editor.set_node(node, {parameters: {
...node.parameters,
done: {val: true, type: "boolean"},
}})
}}
/>
}
const theorem_editor = get_deafult_group_editor_with_appbar({
get_label : () => useParameters().category,
buttons_extra: [MarkDoneButton],
})
替换单个渲染器
如果某个概念的外观和工厂给的差得太远,可以只替换这一个渲染器,其余照旧。
渲染器就是一个普通的 React 组件,接收 editor、node、
children 三个 props:
import { NodeInfoProvider, EditorRendererProps } from "@project-callio/calliotext"
const bare_editor = ({node, children}: EditorRendererProps) => (
<NodeInfoProvider node={node}>
<div style={{borderLeft: "3px solid #888", paddingLeft: "1rem"}}>
{children}
</div>
</NodeInfoProvider>
)
自己写渲染器时有两条硬性要求。第一,必须把 children 输出到界面上,
否则子节点的内容会丢失,Slate 也会因为找不到对应的 DOM 而报错。
第二,如果你要在里面用 useParameters() 之类的 hook,
或者放默认实现的按钮,就得自己包一层 NodeInfoProvider,
因为这层原本是工厂替你包的。
还有一条不是硬性要求但值得遵守:把不该被光标选中的部分(按钮、标题)
包进 EditorUnselecableBox。不这样做,用户在节点开头按退格就有可能把这些界面元素删掉。
完全不用默认实现
最后一档是不要这一层,自己基于 EditorComponent 搭界面。
这时你需要自己处理的东西,正好就是总览那张表里列的:
样式配置的上下文、提示气泡容器、编号冲突修复、按键分发、以及把编辑器实例交给下游组件。
这些都是公开导出的,DefaultEditorComponent 的源码本身就是一份可以照抄的样板。