RichTextEditor
基础表单编辑器基于 Bootstrap 5 与内置无框架富文本内核的编辑器组件,提供文本格式、段落与对齐、缩进与行高、引用与代码块、链接与图片、媒体与表格、表情与符号、日期与自定义卡片等 40 余项功能,支持撤销重做、剪切复制粘贴、粘贴为纯文本、全选、查找替换与格式刷,并提供完整快捷键、受控用法、命令式句柄与扩展机制
Component Demo
Basic usage and examples of the RichTextEditor component
基础用法
展示用示例隐藏了工具栏以便阅读;实际使用时工具栏默认渲染在正文上方,包含文本格式、 段落对齐、缩进、行高、引用、代码块、分割线、插入与编辑操作共 40 余项功能
文本格式与段落
区块类型下拉可在正文与 H1–H6 之间切换,行高与段间距面板同时提供代码块语言选择; 工具栏的每一项都是图标,名称与快捷键显示在悬浮提示中,选中面板里的选项后面板随即收起
插入内容
图片支持 URL、本地上传与图库三种来源;表情、符号、日期以原子节点写入文档, 保留名称与时间戳以便检索与再次格式化。媒体面板支持 iframe 地址或嵌入代码;平台视频使用 iframe,视频文件使用原生播放器。图片与媒体均带常驻焦点工具栏
编辑操作
查找替换支持区分大小写、逐个替换与全部替换;格式刷先取样当前选区格式,再刷到目标选区
受控与命令式调用
组件内部持有唯一的编辑器实例,重复渲染不会丢失文档、撤销历史与光标位置
只读与无工具栏
JSON 自定义视图与帖子详情
编辑内容
使用 editor.getJSON() 导出内容,交给 RichTextRenderer 展示。 默认支持正文、列表、表格、媒体与文字格式,无需创建编辑器实例。nodeRenderers 与 markRenderers 可接入业务组件; 回调提供 children、path 与 defaultRender(), 返回 undefined 继续回退,返回 null 隐藏内容。未知节点与格式默认保留子内容。 存储的 HTML 不会直接执行,嵌入代码和业务卡片可通过自定义回调展示。 需要完全自定义外层布局时,可直接调用 renderRichTextContent(content, options)。 自定义回调中的 URL 与 HTML 处理由业务负责。
自定义与内核直用
组件内部按 StarterKit + MediaKit + TableKit + extendedKit 组合扩展; 传入 extensions 即可完全接管节点、命令与快捷键。需要自定义视图规则、 节点视图或嵌入非 React 宿主页面时,可以直接使用无框架的内核。
内置扩展节点包含emoji、symbol、date、embed与custom_block,并导出对应的命令、DOM 规则与节点名。
快捷键
| 功能 | 快捷键 |
| 加粗 | Ctrl + B |
| 斜体 | Ctrl + I |
| 下划线 | Ctrl + U |
| 删除线 | Ctrl + Shift + X |
| 行内代码 | Ctrl + E |
| 撤销 / 重做 | Ctrl + Z / Ctrl + Shift + Z |
| 全选 | Ctrl + A |
| 插入链接 | Ctrl + K |
| 查找替换 | Ctrl + F |
| 粘贴为纯文本 | Ctrl + Shift + V |
| 格式刷 | Ctrl + Shift + C |
| 换行不分段 | Shift + Enter |
工具栏控件在按下时会取消 pointerdown 默认行为,因此命令始终作用于用户选中的 区间;弹窗打开时记录选区,确认后恢复选区再执行命令,一次操作对应一步撤销
API Documentation
Complete API reference for the RichTextEditor component
Props
RichTextRenderer
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
content | NodeJSON | null | - | editor.getJSON() 或 ref.getJSON() 导出的文档;为空时不渲染正文 |
nodeRenderers | Record<string, RichTextNodeRenderer> | - | 按节点类型替换视图,如 heading、image、custom_block;优先于 renderNode |
markRenderers | Record<string, RichTextMarkRenderer> | - | 按文字格式类型替换视图,如 bold、link、highlight;优先于 renderMark |
renderNode | RichTextNodeRenderer | - | 全局节点渲染回调;返回 undefined 使用默认视图,返回 null 隐藏节点 |
renderMark | RichTextMarkRenderer | - | 全局格式渲染回调;返回 undefined 使用默认视图,返回 null 隐藏格式及其内容 |
...rest | HTMLAttributes<HTMLDivElement> | - | 容器原生属性,支持 className、style、aria-* 和事件;children 和 dangerouslySetInnerHTML 由 JSON 渲染管理 |
RichTextEditor
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | 自定义类名,作用于编辑器根元素 |
content | NodeJSON | string | - | 初始文档,支持 HTML 字符串或 JSON 文档;挂载后修改该属性会替换整篇文档,未修改时不会干扰用户输入 |
editable | boolean | true | 是否可编辑;设为 `false` 时正文只读,工具栏按钮全部禁用 |
extensions | Extension[] | StarterKit + MediaKit + TableKit + extendedKit | 编辑器扩展列表,传入后完全覆盖默认组合;节点、命令与快捷键都由该列表决定 |
gallery | RichTextEditorGalleryGroup[] | [] | 图片弹窗「图库」页签展示的图片分组 |
focusToolbar | false | ((context: RichTextEditorFocusToolbarContext) => ReactNode) | 图片、媒体与自定义卡片工具栏 | 常驻于叶子节点上方的焦点工具栏,独立占位、不覆盖内容。接收节点、位置及 update/remove 操作;返回 null 忽略该节点,设为 false 禁用 |
hideToolbar | boolean | false | 是否隐藏工具栏,仅保留可编辑正文 |
locale | EditorLocale | - | 写入 DOM 的文案(编辑区无障碍名称、`translate` 等) |
onChange | (editor: Editor) => void | - | 文档变化后触发,入参为编辑器实例 |
onHTMLChange | (html: string) => void | - | 文档变化后触发,入参为 HTML 字符串,便于受控表单直接保存 |
placeholder | string | - | 空文档时展示的占位文案 |
toolbarExtra | ReactNode | - | 追加到工具栏末尾的额外控件 |
upload | { file?: FileUploader; image?: FileUploader } | - | 上传适配器,`image` 用于图片、`file` 用于附件;未提供时图片以本地 data URL 嵌入。上传状态显示在正文下方,成功后隐藏 |
...rest | HTMLAttributes | - | 根元素的所有原生属性(如 `style`、`aria-*` 等) |
ref
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
ref | Ref<RichTextEditorHandle> | - | 通过 `useRef<RichTextEditorHandle>` 读取文档、替换文档、撤销重做与聚焦 |
ToolbarButton
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
title | string | - | 无障碍名称,同时作为 tooltip 文本 |
active | boolean | false | 是否为激活态,对应 `aria-pressed` 与 Bootstrap 的 `active` 类 |
keepFocus | boolean | true | 按下时是否保持编辑器选区;默认 `true`,控件会取消 `pointerdown` 默认行为以免光标丢失 |
shortcut | string | - | tooltip 中追加的快捷键提示 |
ToolbarDropdown
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
open | boolean | - | 面板是否展开,组件为受控模式 |
onOpenChange | (open: boolean) => void | - | 面板开合回调 |
placement | 'bottom-start' | 'bottom-end' | 'top-start' | 'bottom-start' | 面板优先出现的位置;超出视口时自动翻转 |
width | number | string | - | 面板宽度,数字按像素处理 |
ToolbarMenuItem
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
label | ReactNode | - | 行标题内容 |
active | boolean | false | 是否为当前值 |
onSelect | () => void | - | 点击回调 |
icon | ReactNode | - | 行首图标 |
hint | ReactNode | - | 行尾提示,例如快捷键或当前值 |
ColorPicker
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
groups | RichTextEditorColorGroup[] | - | 色板分组,来自 `TEXT_COLOR_GROUPS` / `HIGHLIGHT_COLOR_GROUPS` 或自定义 |
onChange | (color: string) => void | - | 选择颜色回调;传入空字符串表示清除该格式 |
value | null | string | - | 当前颜色,用于高亮选中的色块 |
Type Definitions
RichTextRendererProps / RichTextRenderOptions
JSON 展示组件、渲染函数与自定义节点/格式回调接口
type RichTextMarkJSON = NonNullable<NodeJSON['marks']>[number];
interface RichTextNodeRenderContext {
node: NodeJSON;
parent: NodeJSON | null;
path: readonly number[];
children: ReactNode;
defaultRender: () => ReactNode;
}
interface RichTextMarkRenderContext {
mark: RichTextMarkJSON;
node: NodeJSON;
path: readonly number[];
children: ReactNode;
defaultRender: () => ReactNode;
}
type RichTextNodeRenderer = (context: RichTextNodeRenderContext) => ReactNode;
type RichTextMarkRenderer = (context: RichTextMarkRenderContext) => ReactNode;
interface RichTextRenderOptions {
nodeRenderers?: Readonly<Record<string, RichTextNodeRenderer>>;
markRenderers?: Readonly<Record<string, RichTextMarkRenderer>>;
renderNode?: RichTextNodeRenderer;
renderMark?: RichTextMarkRenderer;
}
interface RichTextRendererProps
extends
Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'content' | 'dangerouslySetInnerHTML'>,
RichTextRenderOptions {
content?: NodeJSON | null;
}
function renderRichTextContent(
content: NodeJSON | null | undefined,
options?: RichTextRenderOptions,
): ReactNode;RichTextEditorProps
富文本编辑器组件属性接口
export interface RichTextEditorProps extends Omit<
HTMLAttributes<HTMLDivElement>,
'content' | 'defaultValue' | 'onChange'
> {
className?: string;
content?: NodeJSON | string;
editable?: boolean;
extensions?: Extension[];
gallery?: RichTextEditorGalleryGroup[];
hideToolbar?: boolean;
locale?: EditorLocale;
onChange?: (editor: Editor) => void;
onHTMLChange?: (html: string) => void;
placeholder?: string;
toolbarExtra?: ReactNode;
focusToolbar?: false | ((context: RichTextEditorFocusToolbarContext) => ReactNode);
upload?: { file?: FileUploader; image?: FileUploader };
}RichTextEditorHandle
通过 `ref` 暴露的命令式句柄
export interface RichTextEditorHandle {
focus: () => void;
getEditor: () => Editor;
getHTML: () => string;
getJSON: () => NodeJSON;
getMarkdown: () => string;
getText: () => string;
getView: () => EditorView | null;
redo: () => void;
setHTML: (html: string) => void;
setJSON: (content: NodeJSON) => void;
undo: () => void;
}RichTextEditorContextValue
上下文值,可通过 `useRichTextEditor` 读取,自定义工具条按钮依赖它
export interface RichTextEditorContextValue {
can: (name: string, options?: Attrs) => boolean;
editable: boolean;
editor: Editor;
getAttributes: (name: string) => Attrs;
isActive: (name: string, attrs?: Attrs) => boolean;
run: (name: string, options?: Attrs) => boolean;
state: EditorState;
}RichTextEditorFocusToolbarContext
通用节点焦点工具栏上下文,图片默认接入,可扩展其他节点
export interface RichTextEditorFocusToolbarContext {
editor: Editor;
node: NodeJSON;
position: number;
remove: () => boolean;
update: (attrs: Attrs) => boolean;
}RichTextEditorGalleryGroup
图库分组与图片描述
export interface RichTextEditorGalleryGroup {
images: RichTextEditorImage[];
label: string;
}
export interface RichTextEditorImage {
alt?: string;
height?: number;
id: string;
src: string;
title?: string;
width?: number;
}RichTextEditorPanel
工具栏同时只会展开一个面板,面板标识即为此类型
export type RichTextEditorPanel =
| 'code-language'
| 'block-type'
| 'color'
| 'datetime'
| 'emoji'
| 'font-family'
| 'font-size'
| 'highlight'
| 'insert'
| 'line-height'
| 'spacing'
| 'symbol'
| null;