# wangEditor-next 中文全量文档(Raw Markdown)
> 本文件由脚本自动生成,汇总中文 Guide 的完整 Markdown,适合一次性投喂 AI。
> 这是原始 Markdown 文件,不经过页面渲染。
> 生成时间: 2026年6月13日 23:43:32 (Asia/Shanghai)
> 语言切换: [English Raw Markdown](/ai/en-guide-full.txt)
## 包含文件
1. `docs/zh/guide/index.md`
2. `docs/zh/guide/installation.md`
3. `docs/zh/guide/getting-started.md`
4. `docs/zh/guide/for-frame.md`
5. `docs/zh/guide/content.md`
6. `docs/zh/guide/toolbar-config.md`
7. `docs/zh/guide/editor-config.md`
8. `docs/zh/guide/csp-class-mode.md`
9. `docs/zh/guide/menu-config.md`
10. `docs/zh/guide/API.md`
11. `docs/zh/guide/node-define.md`
12. `docs/zh/guide/development.md`
13. `docs/zh/guide/i18n.md`
14. `docs/zh/guide/theme.md`
15. `docs/zh/guide/for-ts.md`
16. `docs/zh/guide/plugins.md`
17. `docs/zh/guide/video-course.md`
---
## 1. index.md
# 优势
## 其他富文本编辑器的问题
网络搜索“Web 富文本编辑器”你会得到很多结果,例如国内的 UEditor kindEditor ,国外的 CKEditor TinyMCE Quill ProseMirror Draft Slate 等等。也有很多人使用这些编辑器,或者来做二次开发。
但他们都存在下面几个问题(敲黑板~),这可能会大大影响你的**开发效率、开发成本和产品稳定性**。
### 技术老旧
如 UEditor KindEditor ,依然使用 `document.execCommand` API 。这将大大影响产品的稳定性、扩展性。
到时候啥啥都实现不了,哪儿哪儿都有问题,会被 PM 鄙视:“人家 xxx 咋能行?”
### 中文不友好
如 CKEditor TinyMCE Quill ProseMirror 等,没有官方的中文文档。这将大大影响你的开发效率(今晚加班~)
PS:英语特别好的,请略过。
### 需要大量二次开发
如 ProseMirror Draft Slate ,他们虽然也是富文本编辑器,但他们仅仅是一个 core 或者 controller ,并不是一个完整的功能。
大量的二次开发,不仅仅会导致研发成本大增(本月封闭~),还可能因为测试不完善而出现无尽的 bug ,陷入泥潭。
PS:除非你们有强烈的定制开发需要。
### 有框架的约束
如 Slate 和 Draft ,是依赖于 React 框架的。如果你想用到 Vue 中,工作量和难度是非常大的。
### 无官方 React Vue 等组件
一些无框架依赖的,如 ProseMirror ,如果你要用到 Vue React ,需要自己封装组件。
### 新产品尚未稳定
上述列出来的编辑器,都是比较成熟的产品,用户量较大。你可能还会搜到其他产品,如新开发的、用户量不大的。
无论如何,请你慎重选择,因为富文本编辑器的坑真的太多了,需要经过大量的测试、使用才会慢慢稳定。
选择稳定的产品,可参考
- github stars
- npm 下载量
- npm 发布时间和频率
- 搜索引擎的相关结果数量
- 是否有大厂背书
- 是否有单元测试 / e2e 测试
## wangEditor 的优势
一个产品的价值,就在于解决用户的问题,提高效率、降低成本、增加稳定性和扩展性。
wangEditor 不是为了做而做,也不是单纯的模仿谁,而是经过上述问题分析之后,给出一个系统的解决方案。旨在真正去解决用户的问题,产出自己的价值。
### 使用主流技术
wangEditor 从 V5 版本开始,有较大的技术更新。
#### 1. 升级为 L1 能力
弃用了 `document.execCommand` API ,使用 [slate.js](https://www.slatejs.org/)(但不依赖 React)为内核,升级为 L1 能力。
这也是目前主流富文本编辑器的技术方案,如知名的 Quill ProseMirror Draft 都在使用。
#### 2. 使用 vdom
使用 vdom 技术(基于 [snabbdom.js](https://github.com/snabbdom/snabbdom) )做视图更新,model 和 view 分离,增加稳定性。
#### 3. 扩展性
使用扩展插件和模块的机制,保证**扩展性**。未来还会继续扩展更多功能。
其实,现在 wangEditor 内置的各个功能,也都是通过扩展插件和模块的形式搭建起来的。
### 中文文档
wangEditor 有详细的中文文档,以及中文交流环境。
### 及时反馈和沟通
也可以去 github 提交 issue ,团队都会及时反馈或受理。
### 集成所有功能,无需二次开发
wangEditor 内置了所有常见的富文本操作功能,能满足绝大部分使用需求。直接配置使用即可,无需再二次开发。
```js
// wangEditor 已内置 50+ 菜单
editor.getAllMenuKeys()
[
"bold","underline","italic","through","code","clearStyle","headerSelect","header1","header2","header3",
"color","bgColor","insertLink","editLink","unLink","viewLink","insertImage","deleteImage","editImage",
"viewImageLink","imageWidth30","imageWidth50","imageWidth100","blockquote","emotion","fontSize","fontFamily",
"indent","delIndent","justifyLeft","justifyRight","justifyCenter","lineHeight","redo","undo","divider","codeBlock",
"bulletedList","numberedList","insertTable","deleteTable","insertTableRow","deleteTableRow","insertTableCol",
"deleteTableCol","tableHeader","tableFullWidth","insertVideo","deleteVideo","uploadImage","codeSelectLang"
]
```
PS:同时,wangEditor 有丰富的 [API](./API.md) 和足够的扩展性,允许你[自定义开发](./development.md)菜单、模块、插件等。
### 很方便地应用于 Vue React
wangEditor 基于 slate 内核开发,但不依赖于 React ,所以它本身是无框架依赖的。
并且,我们官方封装了 Vue React 组件,可以很方便的[用于 Vue React 等框架](./for-frame.md)。
其他框架,我们会继续支持,大家也可以提交 issue 。
### 踩过 5000 个坑
wangEditor 开源多年,大量用户使用和反馈,已经解决了[很多问题](https://github.com/wangeditor-next/wangEditor-next/issues)。在 V5 版本测试过程中,也这些问题进行了重复测试,最大程度保证稳定性。
### 团队作业,持续迭代升级
wangEditor 由多人团队持续维护,一起修复 bug 、升级功能、跟踪问题、社区答疑。
---
## 2. installation.md
# 安装
包括 vue React 组件
## npm
安装 editor
```shell
yarn add @wangeditor-next/editor
# 或者 npm install @wangeditor-next/editor --save
```
安装 React 组件(可选)
```shell
yarn add @wangeditor-next/editor-for-react
# 或者 npm install @wangeditor-next/editor-for-react --save
```
安装 Vue2 组件(可选)
```shell
yarn add @wangeditor-next/editor-for-vue2
# 或者 npm install @wangeditor-next/editor-for-vue2 --save
```
安装 Vue3 组件(可选)
```shell
yarn add @wangeditor-next/editor-for-vue
# 或者 npm install @wangeditor-next/editor-for-vue --save
```
## CDN
```html
```
如果上述 CDN 访问不成功,可以在 npm 安装完成之后,在安装包找到 JS CSS 文件,步骤如下:
- 新建一个 `test1` 文件夹,打开控制台,目录定位到该文件夹,执行 `npm install @wangeditor-next/editor` 或 `yarn add @wangeditor-next/editor`
- 安装完成,打开 `node_modules/@wangeditor-next/editor/dist` 文件夹,即可找到 JS CSS 文件:
- `index.js`
- `css/style.css`
- 把这俩文件拷贝出来,然后删掉 `test1` 文件夹
---
## 3. getting-started.md
# 快速开始
快速了解可查看[视频教程](./video-course.md)。用于 Vue React 参考[这里](./for-frame.md)。
## 创建空白编辑器
可直接参考 [demo 示例](https://wangeditor-next.github.io/demo/)的网页源码。
### 引入 CSS 定义样式
可自定义编辑器、工具栏的尺寸、边框、`z-index` 等样式。
```html
```
### 自定义元素样式
```html
```
### 定义 HTML 结构
```html
```
::: tip
- 如果想要“全屏”功能,则要求工具栏、编辑器 DOM 节点必须是同一层级
- 当然,工具栏、编辑器 DOM 节点也可自由组合,例如 [仿腾讯文档 demo](https://wangeditor-next.github.io/demo/like-qq-doc.html)
:::
### 引入 JS 创建编辑器
```html
```
::: tip
不同 `mode` 可参考 demo
- [mode: 'default'](https://wangeditor-next.github.io/demo/index.html) 默认模式 - 集成了 wangEditor 所有功能
- [mode: 'simple'](https://wangeditor-next.github.io/demo/simple-mode.html) 简洁模式 - 仅有部分常见功能,但更加简洁易用
:::
这样就创建出了一个最基本的编辑器。

## 接下来
要实现一个完整的富文本编辑器功能,你可能还需要以下功能:
- [内容处理](./content.md) - 获取内容,**设置内容**,展示内容
- [工具栏配置](./toolbar-config.md) - 插入新菜单,屏蔽某个菜单等
- [编辑器配置](./editor-config.md) - 兼听各个**生命周期**,自定义**粘贴**
- [菜单配置](./menu-config.md) - 配置颜色、字体、字号、链接校验、**上传图片、上传视频**等
- [编辑器 API](./API.md) - 控制编辑器内容和选区
- [扩展新功能](./development.md) - 扩展菜单、元素、插件等
---
## 4. for-frame.md
# 用于 Vue React
快速了解可查看[视频教程](./video-course.md)。
## Vue2
### Demo
- [Demo 源码](https://github.com/wangeditor-next/vue2-wangeditor-demo)
- [在线 demo](https://stackblitz.com/edit/vue2-vite-starter-hkmsif)
### 安装
```sh
yarn add @wangeditor-next/editor
# 或者 npm install @wangeditor-next/editor --save
yarn add @wangeditor-next/editor-for-vue2
# 或者 npm install @wangeditor-next/editor-for-vue2 --save
```
### 使用
模板
```xml
```
script
```html
```
:::tip
- 赋值 `this.editor` 时要用 `Object.seal()`
- 组件销毁时,要及时销毁编辑器
:::
记得引入 style
```html
```
### 配置
可通过 `toolbarConfig` 和 `editorConfig` 来修改菜单栏和编辑器的配置,详细文档参考
- [工具栏配置](./toolbar-config.md) - 插入新菜单,屏蔽某个菜单等
- [编辑器配置](./editor-config.md) - 兼听各个**生命周期**,自定义**粘贴**
- [菜单配置](./menu-config.md) - 配置颜色、字体、字号、链接校验、**上传图片、视频**等
【注意】编辑器配置中 `onXxx` 格式的生命周期函数,**必须通过 Vue 事件来传递,不可以放在 `editorConfig` 中**,例如:
```xml
```
```js
methods: {
onCreated(editor) {
this.editor = Object.seal(editor)
console.log('onCreated', editor)
},
onChange(editor) { console.log('onChange', editor.children) },
onDestroyed(editor) { console.log('onDestroyed', editor) },
onMaxLength(editor) { console.log('onMaxLength', editor) },
onFocus(editor) { console.log('onFocus', editor) },
onBlur(editor) { console.log('onBlur', editor) },
customAlert(info: string, type: string) { window.alert(`customAlert in Vue demo\n${type}:\n${info}`) },
customPaste(editor, event, callback) {
console.log('ClipboardEvent 粘贴事件对象', event)
// const html = event.clipboardData.getData('text/html') // 获取粘贴的 html
// const text = event.clipboardData.getData('text/plain') // 获取粘贴的纯文本
// const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据(如从 word wsp 复制粘贴)
// 自定义插入内容
editor.insertText('xxx')
// 返回 false ,阻止默认粘贴行为
event.preventDefault()
callback(false) // 返回值(注意,vue 事件的返回值,不能用 return)
// 返回 true ,继续默认的粘贴行为
// callback(true)
},
}
```
### 调用 API
当编辑器渲染完成之后,通过 `this.editor` 获取 editor 实例,即可调用它的 API 。参考 [编辑器 API](./API.md) 。
```xml
```
```js
methods: {
insertText() {
const editor = this.editor // 获取 editor 实例
if (editor == null) return
// 调用 editor 属性和 API
editor.insertText('一段文字')
console.log(editor.children)
},
},
```
## Vue3
### Demo
- [Demo 源码](https://github.com/wangeditor-next/vue3-wangeditor-demo)
- [在线 demo](https://stackblitz.com/edit/vue3-wangeditor-demo-8emmc7)
### 安装
```sh
yarn add @wangeditor-next/editor
# 或者 npm install @wangeditor-next/editor --save
yarn add @wangeditor-next/editor-for-vue
# 或者 npm install @wangeditor-next/editor-for-vue --save
```
### 使用
模板
```xml
```
script
```html
```
:::tip
- `editorRef` 必须用 `shallowRef`
- 组件销毁时,要及时销毁编辑器
:::
### 配置
可通过 `toolbarConfig` 和 `editorConfig` 来修改菜单栏和编辑器的配置,详细文档参考
- [工具栏配置](./toolbar-config.md) - 插入新菜单,屏蔽某个菜单等
- [编辑器配置](./editor-config.md) - 兼听各个**生命周期**,自定义**粘贴**
- [菜单配置](./menu-config.md) - 配置颜色、字体、字号、链接校验、**上传图片、视频**等
【注意】编辑器配置中 `onXxx` 格式的生命周期函数,**必须通过 Vue 事件来传递,不可以放在 `editorConfig` 中**,例如:
```xml
```
```js
const handleCreated = (editor) => {
editorRef.value = editor
console.log('created', editor)
}
const handleChange = (editor) => { console.log('change:', editor.children) }
const handleDestroyed = (editor) => { console.log('destroyed', editor) }
const handleFocus = (editor) => { console.log('focus', editor) }
const handleBlur = (editor) => { console.log('blur', editor) }
const customAlert = (info, type) => { alert(`【自定义提示】${type} - ${info}`) }
const customPaste = (editor, event, callback) => {
console.log('ClipboardEvent 粘贴事件对象', event)
// const html = event.clipboardData.getData('text/html') // 获取粘贴的 html
// const text = event.clipboardData.getData('text/plain') // 获取粘贴的纯文本
// const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据(如从 word wsp 复制粘贴)
// 自定义插入内容
editor.insertText('xxx')
// 返回 false ,阻止默认粘贴行为
event.preventDefault()
callback(false) // 返回值(注意,vue 事件的返回值,不能用 return)
// 返回 true ,继续默认的粘贴行为
// callback(true)
}
return {
// 省略其他 ...
handleCreated,
handleChange,
handleDestroyed,
handleFocus,
handleBlur,
customAlert,
customPaste
}
```
### 调用 API
当编辑器渲染完成之后,通过 `editorRef.value` 获取 editor 实例,即可调用它的 API 。参考 [编辑器 API](./API.md) 。
```xml
```
```js
const insertText = () => {
const editor = editorRef.value // 获取 editor ,必须等待它渲染完之后
if (editor == null) return
editor.insertText('hello world') // 执行 editor API
}
return {
// 省略其他 ...
insertText
}
```
## React
### Demo
- [Demo 源码](https://github.com/wangeditor-next/react-wangeditor-demo)
- [在线 demo](https://stackblitz.com/edit/react-4osjqn)
### 安装
```sh
yarn add @wangeditor-next/editor
# 或者 npm install @wangeditor-next/editor --save
yarn add @wangeditor-next/editor-for-react
# 或者 npm install @wangeditor-next/editor-for-react --save
```
### 使用
```tsx
import '@wangeditor-next/editor/dist/css/style.css' // 引入 css
import React, { useState, useEffect } from 'react'
import { Editor, Toolbar } from '@wangeditor-next/editor-for-react'
import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor-next/editor'
function MyEditor() {
// editor 实例
const [editor, setEditor] = useState(null) // TS 语法
// const [editor, setEditor] = useState(null) // JS 语法
// 编辑器内容
const [html, setHtml] = useState('hello
')
// 模拟 ajax 请求,异步设置 html
useEffect(() => {
setTimeout(() => {
setHtml('hello world
')
}, 1500)
}, [])
// 工具栏配置
const toolbarConfig: Partial = { } // TS 语法
// const toolbarConfig = { } // JS 语法
// 编辑器配置
const editorConfig: Partial = { // TS 语法
// const editorConfig = { // JS 语法
placeholder: '请输入内容...',
}
// 及时销毁 editor ,重要!
useEffect(() => {
return () => {
if (editor == null) return
editor.destroy()
setEditor(null)
}
}, [editor])
return (
<>
setHtml(editor.getHtml())}
mode="default"
style={{ height: '500px', overflowY: 'hidden' }}
/>
{html}
>
)
}
export default MyEditor
```
### React 内置 loading(editor-for-react)
`@wangeditor-next/editor-for-react` 支持 `loading` 和 `loadingText` 属性,可直接显示编辑器内部遮罩层。
```tsx
const [uploading, setUploading] = useState(false)
setHtml(editor.getHtml())}
loading={uploading}
loadingText="Uploading..."
/>
```
建议优先使用该方式,而不是在外层再套 `Spin/Loader` 改变编辑器 DOM 层级。
### 配置
可通过 `toolbarConfig` 和 `editorConfig` 来修改菜单栏和编辑器的配置,详细文档参考
- [工具栏配置](./toolbar-config.md) - 插入新菜单,屏蔽某个菜单等
- [编辑器配置](./editor-config.md) - 兼听各个**生命周期**,自定义**粘贴**
- [菜单配置](./menu-config.md) - 配置颜色、字体、字号、链接校验、**上传图片、视频**等
### 调用 API
当编辑器渲染完成之后,即可调用它的 API 。参考 [编辑器 API](./API.md) 。
```jsx
function insertText() {
if (editor == null) return
editor.insertText('hello')
}
return (
<>
insert text
>
)
```
---
## 5. content.md
# 内容处理
快速了解可查看[视频教程](./video-course.md)。
## 获取内容
### 获取 HTML 和 Text
使用 `editor.getHtml()` 获取 HTML 内容,可参考 [demo](https://wangeditor-next.github.io/demo/get-html.html)。使用 `editor.getText()` 获取纯文本内容。
如果你需要导出带唯一标识属性的 HTML(用于节点追踪/定位),可使用 `editor.getHtmlWithId(idKey?)` :
```js
const html = editor.getHtmlWithId() // 默认 data-w-e-id
const html2 = editor.getHtmlWithId('data-node-id') // 自定义属性名
```
推荐使用 HTML 格式存储数据。
### 获取 JSON
使用 `editor.children` 获取 JSON 内容。
JSON 格式可以转换为 HTML 和 Text 格式,支持浏览器和 nodejs 。
如果是在 nodejs 中,需要安装 `yarn add jsdom global-jsdom` ,并且引入 `require('global-jsdom/register')`。
```js
const editor = createEditor({ content }) // `content` 即为 JSON 内容
const html = editor.getHtml()
const text = editor.getText()
```
### 自定义样式
编辑器输出或者生成的 HTML 都是**纯标签**,没有内联样式。所以,显示 HTML 时需要你自定义样式。可参考以下示例
- [显示 HTML](https://wangeditor-next.github.io/demo/get-html.html)
- [自定义样式](https://wangeditor-next.github.io/demo/css/view.css)
另外,**代码高亮**也需要自行处理,推荐使用 [Prism.js](https://prismjs.com/) ,因为编辑器内容内部也是基于 Prism.js 来实现的。可参考 [demo](https://wangeditor-next.github.io/demo/code-highlight.html)。
## 设置内容
创建编辑器时,传入的默认内容。即编辑器创建完成后,立马显示这些内容。
### 设置 HTML
【注意】这里的 HTML 内容必须是 wangEditor 生成的(即 `editor.getHtml()` 返回的) HTML 格式,不可以自己随意写 。HTML 格式非常灵活,wangEditor 无法兼容所有的 HTML 格式。
例如,wangEditor 可以识别 `hello ` 为加粗,但无法识别 `hello ` 等其他加粗方式。
#### 创建时设置 HTML
```js
const editor = createEditor({
html: 'hello world
', // 从 editor.getHtml() 获取的 html 内容
// 其他属性...
})
```
#### 动态设置 HTML
参考 [demo](https://wangeditor-next.github.io/demo/set-html.html)
```js
editor.setHtml('hello world
')
```
:::tip
注意,`setHtml` 主要用于回显编辑器输出的 HTML ,即 `editor.getHtml()` 的内容。
如果想插入一段 HTML ,请使用 [dangerouslyInsertHtml](./API.md#dangerouslyinserthtml)
:::
### 设置 Text
```js
// 1. 把 text 转换为 html
const text = '...' // text 内容
const html = text.split(/\n/).map(line => `${line}
`).join('\n')
// 2. 设置 html
const editor = createEditor({
html,
// 其他属性...
})
// 3. 或,在创建完 editor 之后执行 setHtml
// editor.setHtml(html)
```
### 设置 JSON
```js
const editor = createEditor({
content: [...], // editor.children 获取的内容
// 其他属性
})
```
### Ajax 异步设置内容
可等待 Ajax 返回之后再创建编辑器。
```ts
// 伪代码
import { IDomEditor } from '@wangeditor-next/editor'
let editor: IDomEditor | null = null // TS 语法
// let editor = null // JS 语法
ajax(url, res => {
editor = createEditor({
// content 或 html
// 其他属性
})
})
```
::: tip
其他的内容处理,可参考 [API](./API.md)
:::
---
## 6. toolbar-config.md
# 工具栏配置
快速了解可查看[视频教程](./video-course.md)。
:::tip
wangEditor 从 V5 版本开始,工具栏配置和[菜单配置](./menu-config.md)(如配置颜色、字体、链接校验、上传图片等)分离了。本文只讲工具栏配置。
:::
```ts{5}
import { IToolbarConfig } from '@wangeditor-next/editor'
const toolbarConfig: Partial = { // TS 语法
// const toolbarConfig = { // JS 语法
/* 工具栏配置 */
}
// 创建 toolbar ,或者传入 Vue React 组件中
```
## getConfig
可通过 `toolbar.getConfig()` 查看工具栏的默认配置。
如果你使用 Vue React ,可以通过如下代码获取 `toolbar` 实例
```ts
import { DomEditor } from '@wangeditor-next/editor'
const toolbar = DomEditor.getToolbar(editor)
const curToolbarConfig = toolbar.getConfig()
console.log(curToolbarConfig.toolbarKeys) // 当前菜单排序和分组
```
:::tip
在 editor.onCreated 生命周期中 toolbar 实例还没创建好,所以不能在 created 中获取 toolbar 实例。
:::
## toolbarKeys
**重新**配置工具栏,显示哪些菜单,以及菜单的排序、分组。
- `toolbar.getConfig().toolbarKeys` 查看当前的默认配置
- `editor.getAllMenuKeys()` 查询编辑器注册的所有菜单 key (可能有的不在工具栏上)
```ts
toolbarConfig.toolbarKeys = [
// 菜单 key
'headerSelect',
// 分割线
'|',
// 菜单 key
'bold',
'italic',
// 菜单组,包含多个菜单
{
key: 'group-more-style', // 必填,要以 group 开头
title: '更多样式', // 必填
iconSvg: '.... ', // 可选
menuKeys: ['through', 'code', 'clearStyle'] // 下级菜单 key ,必填
},
// 继续配置其他菜单...
]
```
## insertKeys
可以在当前 `toolbarKeys` 的基础上继续插入新菜单,如自定义扩展的菜单。
```ts
toolbarConfig.insertKeys = {
index: 5, // 插入的位置,基于当前的 toolbarKeys
keys: ['menu-key1', 'menu-key2']
}
```
## excludeKeys
如果仅仅想排除掉某些菜单,其他都保留,可以使用 `excludeKeys` 来配置。
可通过 `toolbar.getConfig().toolbarKeys` 查看工具栏的默认配置
```ts
toolbarConfig.excludeKeys = [
'headerSelect',
'italic',
'group-more-style' // 排除菜单组,写菜单组 key 的值即可
]
```
如果你想排除某个菜单组,可通过 `toolbar.getConfig().toolbarKeys` 找到这个菜单组的 key 。

## modalAppendToBody
将菜单弹出的 modal 添加到 body 下,并自定义 modal 的定位和其他样式。

```ts{1}
toolbarConfig.modalAppendToBody = true
// 创建 toolbar 和 editor
// 可监听 `modalOrPanelShow` 和 `modalOrPanelHide` 自定义事件来设置样式、蒙层
editor.on('modalOrPanelShow', modalOrPanel => {
if (modalOrPanel.type !== 'modal') return
const { $elem } = modalOrPanel // modal element
// 设置 modal 样式(定位、z-index)
// 显示蒙层
})
editor.on('modalOrPanelHide', () => {
// 隐藏蒙层
})
```
上述代码细节可以参考 [example 源码](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/editor/examples/modal-appendTo-body.html)
---
## 7. editor-config.md
# 编辑器配置
快速了解可查看[视频教程](./video-course.md)。
```ts{5}
import { IEditorConfig } from '@wangeditor-next/editor'
const editorConfig: Partial = { // TS 语法
// const editorConfig = { // JS 语法
/* 编辑器配置 */
}
// 创建 editor 或传入 Vue React 组件
```
:::tip
可通过 `editor.getConfig()` 查看编辑器默认配置
:::
## placeholder
配置编辑器 placeholder
```ts
editorConfig.placeholder = '请输入内容...'
```
## readOnly
配置编辑器是否只读,默认为 `false`
```ts
editorConfig.readOnly = true
```
只读状态可通过 `editor.enable()` 和 `editor.disable()` 切换,详见 [API](./API.md) 。
## autoFocus
配置编辑器默认是否 focus ,默认为 `true`
```ts
editorConfig.autoFocus = false
```
## scroll
配置编辑器是否支持滚动,默认为 `true` 。注意,此时**不要固定 `editor-container` 的高度**,设置一个 `min-height` 即可。
```ts
editorConfig.scroll = false
```
:::tip
可将 scroll 设置为 `false` 的情况:
- 编辑器高度自增
- 在线文档,如腾讯文档、语雀那样的,参考 [demo](https://wangeditor-next.github.io/demo/like-qq-doc.html) 中的“仿腾讯文档”
:::
## textStyleMode
配置文本/段落样式导出模式。
```ts
editorConfig.textStyleMode = 'class' // 'inline' | 'class'
```
- `inline`(默认):输出内联 `style`
- `class`:输出 `class + data-w-e-*`,适合严格 CSP 场景
详细使用与迁移建议见 [CSP class 样式模式](./csp-class-mode.md)。
## classStylePolicy
当 `textStyleMode = 'class'` 时,配置未注册 token 的处理策略。
```ts
editorConfig.classStylePolicy = 'preserve-data'
// 'preserve-data' | 'fallback-inline' | 'strict'
```
- `preserve-data`(默认):保留 `data-w-e-*`
- `fallback-inline`:回退到 inline style
- `strict`:直接抛错
## styleClassTokens
扩展 class 模式允许的 token 集合。
```ts
editorConfig.styleClassTokens = {
color: ['rgb(1, 2, 3)'],
fontSize: ['20px'],
}
```
:::tip
`styleClassTokens` 只注册 token,不自动注入业务 CSS。
:::
## onClassStyleUnsupported
class 模式遇到未注册 token 时的通知回调。
```ts
editorConfig.onClassStyleUnsupported = payload => {
// payload: { type, value, scene, fallback, message }
console.warn(payload)
}
```
## maxLength onMaxLength
配置编辑器的 maxlength ,参考 [demo](https://wangeditor-next.github.io/demo/max-length.html)。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
editorConfig.maxLength = 1000
editorConfig.onMaxLength = function (editor: IDomEditor) { // TS 语法
// editorConfig.onMaxLength = function (editor) { // JS 语法
// 当达到 maxlength 限制时,触发该回调函数
}
```
:::tip
无特殊需求,请慎用 maxLength ,这可能会导致编辑器内容过多时,编辑卡顿。
:::
## hoverbarKeys
配置编辑器的 hoverbar 菜单。通过 `editor.getConfig().hoverbarKeys` 可查看当前的 hoverbarKeys

:::tip
createEditor 时设置 `mode: 'simple'` 可隐藏选中文本时的 hoverbar 。
:::
### 使用 element type
可以通过元素 `type` 配置某种元素的 hoverbar
- 元素的 `type` 可通过 `editor.children` 查看,如下图
- 使用 `editor.getAllMenuKeys()` 可查看所有内置 menu key

```ts
editorConfig.hoverbarKeys = {
link: {
// 重写 link 元素的 hoverbar
menuKeys: ['editLink', 'unLink', 'viewLink'],
},
image: {
// 清空 image 元素的 hoverbar
menuKeys: [],
}
}
```
### 自定义 match 函数
如果 element type 无法满足需求,可通过自定义 `match` 函数匹配元素。
```ts
import { SlateNode, IDomEditor } from '@wangeditor-next/editor'
editorConfig.hoverbarKeys = {
'text': {
// 如有 match 函数,则优先根据 match 判断,而忽略 element type
match: (editor: IDomEditor, n: SlateNode) => { // TS 语法
// match: (editor, n) => { // JS 语法
// 可参考下文的源码
},
menuKeys: [ ... ], // 定义你想要的 menu keys
}
}
```
可参考 hoverbar 配置的[源码](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/editor/src/init-default-config/config/hoverbar.ts)。
## onCreated
编辑器创建完毕时的回调函数。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
editorConfig.onCreated = (editor: IDomEditor) => { // TS 语法
// editorConfig.onCreated = (editor) => { // JS 语法
// editor created
}
```
## onChange
编辑器内容、选区变化时的回调函数。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
editorConfig.onChange = (editor: IDomEditor) => { // TS 语法
// editorConfig.onChange = (editor) => { // JS 语法
// editor changed
console.log('content', editor.children)
}
```
## onDestroyed
编辑器销毁时的回调函数。调用 `editor.destroy()` 即可销毁编辑器,详见 [API](./API.md) 。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
editorConfig.onDestroyed = (editor: IDomEditor) => { // TS 语法
// editorConfig.onDestroyed = (editor) => { // JS 语法
// editor destroyed
}
```
## onFocus
编辑器 focus 时的回调函数。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
editorConfig.onFocus = (editor: IDomEditor) => { // TS 语法
// editorConfig.onFocus = (editor) => { // JS 语法
// editor focused
}
```
## onBlur
编辑器 blur 时的回调函数。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
editorConfig.onBlur = (editor: IDomEditor) => { // TS 语法
// editorConfig.onBlur = (editor) => { // JS 语法
// editor blur
}
```
## customPaste
自定义粘贴。可阻止编辑器的默认粘贴,实现自己的粘贴逻辑。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
editorConfig.customPaste = (editor: IDomEditor, event: ClipboardEvent): boolean => { // TS 语法
// editorConfig.customPaste = (editor, event) => { // JS 语法
// event 是 ClipboardEvent 类型,可以拿到粘贴的数据
// 可参考 https://developer.mozilla.org/zh-CN/docs/Web/API/ClipboardEvent
// const html = event.clipboardData.getData('text/html') // 获取粘贴的 html
// const text = event.clipboardData.getData('text/plain') // 获取粘贴的纯文本
// const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据(如从 word wsp 复制粘贴)
// 同步
editor.insertText('xxx')
// 异步
setTimeout(() => {
editor.insertText('yy')
}, 1000)
// 阻止默认的粘贴行为
event.preventDefault()
return false
// 继续执行默认的粘贴行为
// return true
}
```
## customCopy
自定义复制,可修改编辑器的复制结果。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
editorConfig.customCopy = (editor: IDomEditor, event: ClipboardEvent): void => { // TS 语法
// editorConfig.customCooy = (editor, event) => { // JS 语法
const originalText = event.clipboardData.getData('text/plain');
const originalHtml = event.clipboardData.getData('text/html');
// 修改或扩展内容
const modifiedText = `${originalText}\n---\n添加的文本`;
const modifiedHtml = `${originalHtml}添加的HTML内容
`;
// 将修改后的内容写回剪贴板
event.clipboardData.setData('text/plain', modifiedText);
event.clipboardData.setData('text/html', modifiedHtml);
}
```
## customAlert
自定义编辑器 alert 。如想用 antd 的 message 功能。
```ts
import { message } from 'antd'
editorConfig.customAlert = (s: string, t: string) => { // TS 语法
// editorConfig.customAlert = (s, t) => { // JS 语法
switch (t) {
case 'success':
message.success(s)
break
case 'info':
message.info(s)
break
case 'warning':
message.warning(s)
break
case 'error':
message.error(s)
break
default:
message.info(s)
break
}
}
```
## EXTEND_CONF
用于第三方插件做扩展配置,如 [mention 插件](https://github.com/wangeditor-next/wangEditor-next/tree/master/packages/plugin-mention)。
---
## 8. csp-class-mode.md
# CSP class 样式模式
在严格 CSP(例如禁止内联 `style`)场景下,可使用 `textStyleMode: 'class'` 让样式以 `class + data-w-e-*` 输出。
:::tip
默认模式仍是 `inline`,老项目无需改动即可继续使用。
:::
## 快速开始
```ts
import { createEditor, IEditorConfig } from '@wangeditor-next/editor'
const editorConfig: Partial = {
textStyleMode: 'class',
classStylePolicy: 'preserve-data',
styleClassTokens: {
color: ['rgb(1, 2, 3)'],
},
onClassStyleUnsupported(payload) {
console.warn('[class-style-unsupported]', payload)
},
}
const editor = createEditor({
selector: '#editor-container',
config: editorConfig,
html: '
',
})
```
同时请确保引入编辑器样式文件(内置默认 token 的 class 样式):
```html
```
## 配置项说明
### `textStyleMode`
- `inline`(默认):样式输出到 `style`。
- `class`:样式输出到 `class + data-w-e-*`。
### `classStylePolicy`
仅在 `textStyleMode: 'class'` 下生效。
- `preserve-data`(默认):仅保留 `data-w-e-*`,不输出 class/inline(可回读,可能不展示)。
- `fallback-inline`:保留 `data-w-e-*`,并回退到内联样式(优先展示)。
- `strict`:遇到未注册 token 直接抛错(避免静默降级)。
### `styleClassTokens`
- 用于注册额外可接受的 token。
- 仅注册 token,不会自动注入你的业务样式。
### `onClassStyleUnsupported`
当遇到未注册 token 时回调,便于日志与监控。
回调 payload 包含:
- `type`
- `value`
- `scene`(`render` 或 `toHtml`)
- `fallback`(`preserve-data` / `inline` / `throw`)
- `message`
## 支持的文本样式类型
- `color`
- `bgColor`
- `fontSize`
- `fontFamily`
- `textAlign`
- `lineHeight`
- `indent`
## 自定义 token 的 CSS 约定
推荐优先基于 `data-w-e-*` 写规则,不依赖 hash class 名:
```css
[data-w-e-color="rgb(1, 2, 3)"] { color: rgb(1, 2, 3); }
[data-w-e-font-size="20px"] { font-size: 20px; }
[data-w-e-line-height="2"] { line-height: 2; }
```
## 模块行为说明
- `basic-modules`:文本样式按策略输出 class/data/inline。
- `list-module`:列表颜色 class 使用 `w-e-list-color-*`,并保留 `data-w-e-color`。
- `table-module`:`border-style` 在 class 模式下按策略处理(支持单值 class,复杂值按策略降级)。
- `video-module`、`image`、`plugin-float-image`:对齐、尺寸等优先走 class/data 输出。
## 迁移建议
1. 先在测试环境启用 `textStyleMode: 'class'` + `classStylePolicy: 'preserve-data'`。
2. 观察 `onClassStyleUnsupported` 日志,补齐 `styleClassTokens` 与 CSS。
3. 再按业务要求切到 `fallback-inline` 或 `strict`。
---
## 9. menu-config.md
# 菜单配置
快速了解可查看[视频教程](./video-course.md)。
本文是各个菜单项的详细配置。如想要自定义工具栏的菜单(隐藏某些菜单、排序、分组等),请参考[工具栏配置](./toolbar-config.md)。
## 通用方法
### 确定 menu key
要配置哪个菜单,首先要知道这个菜单的 key 。执行 `editor.getAllMenuKeys()` 可获取编辑器所有菜单,从中找到自己想要的菜单 key 即可。
### 获取菜单的默认配置
找到菜单 key 之后,可以先看看菜单的当前配置,再自行修改。
```ts
editor.getMenuConfig('uploadImage') // 获取 uploadImage 的当前配置
```
### 修改配置
```ts
import { IEditorConfig } from '@wangeditor-next/editor'
// 初始化 MENU_CONF 属性
const editorConfig: Partial = { // TS 语法
// const editorConfig = { // JS 语法
MENU_CONF: {}
// 其他属性...
}
// 修改 uploadImage 菜单配置
editorConfig.MENU_CONF['uploadImage'] = {
server: '/api/upload-image',
fieldName: 'custom-field-name'
// 继续写其他配置...
//【注意】不需要修改的不用写,wangEditor 会去 merge 当前其他配置
}
// 修改 otherMenuKey 菜单配置
editorConfig.MENU_CONF['otherMenuKey'] = {
// 配置
}
// 创建 editor 或传入 Vue React 组件
```
### 修改默认配置
#### 修改默认字体、字号、行高
```ts
const jsonContent = [
{
type: 'paragraph',
lineHeight: '1.5',
children: [
{ text: 'hello world', fontFamily: '黑体', fontSize: '32px' }
]
},
]
```
Vue React Editor组件有 defaultContent 属性,可传入上述 jsonContent
HTML 格式
```ts
const htmlContent = 'hello world
'
```
Vue Editor件可以使用 v-model 属性传入 HTML 内容,React Editor组件可以使用 value 属性传入 HTML 内容使用
- [Vue修改默认字体字号行高](https://codesandbox.io/p/sandbox/vue2-wangeditor-demo-forked-67fh5s)
- [React修改默认字体字号行高](https://codesandbox.io/p/sandbox/react-wangeditor-defaultfont-59c48n)
#### 修改默认图标
在 menu conf 中对对应的 toolbar 和 hoverbar key 加上 iconSvg 属性,填入对应的 svg 字符串
~~~JavaScript
MEEN_CONF = {
// toolbar 文字颜色 key
color: {
iconSvg:
' '
,
},
// hoverbar 图片宽度 key
imageWidth30: {
iconSvg: ' ',
},
}
~~~
## 颜色
```ts
// 文字颜色
editorConfig.MENU_CONF['color'] = {
colors: ['#000', '#333', '#666']
}
// 背景色
editorConfig.MENU_CONF['bgColor'] = {
colors: ['#000', '#333', '#666']
}
```
## 字号
```ts
editorConfig.MENU_CONF['fontSize'] = {
fontSizeList: [
// 元素支持两种形式
// 1. 字符串;
// 2. { name: 'xxx', value: 'xxx' }
'12px',
'16px',
{ name: '24px', value: '24px' },
'40px',
]
}
```
## 字体
:::tip
请注意,某些字体不能商用。具体请自行查找。
:::
```ts
editorConfig.MENU_CONF['fontFamily'] = {
fontFamilyList: [
// 元素支持两种形式
// 1. 字符串;
// 2. { name: 'xxx', value: 'xxx' }
'黑体',
'楷体',
{ name: '仿宋', value: '仿宋' },
'Arial',
'Tahoma',
'Verdana'
]
}
```
## 行高
```ts
editorConfig.MENU_CONF['lineHeight'] = {
lineHeightList: ['1', '1.5', '2', '2.5']
}
```
## 表情
```ts
editorConfig.MENU_CONF['emotion'] = {
emotions: '😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉'.split(' ') // 数组
}
```
## 链接
- `checkLink` 校验链接
- `parseLinkUrl` 转换链接 url
```ts
// 自定义校验链接
function customCheckLinkFn(text: string, url: string): string | boolean | undefined { // TS 语法
// function customCheckLinkFn(text, url) { // JS 语法
if (!url) {
return
}
if (url.indexOf('http') !== 0) {
return '链接必须以 http/https 开头'
}
return true
// 返回值有三种选择:
// 1. 返回 true ,说明检查通过,编辑器将正常插入链接
// 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
// 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止插入。但不会提示任何信息
}
// 自定义转换链接 url
function customParseLinkUrl(url: string): string { // TS 语法
// function customParseLinkUrl(url) { // JS 语法
if (url.indexOf('http') !== 0) {
return `http://${url}`
}
return url
}
// 插入链接
editorConfig.MENU_CONF['insertLink'] = {
checkLink: customCheckLinkFn, // 也支持 async 函数
parseLinkUrl: customParseLinkUrl, // 也支持 async 函数
}
// 更新链接
editorConfig.MENU_CONF['editLink'] = {
checkLink: customCheckLinkFn, // 也支持 async 函数
parseLinkUrl: customParseLinkUrl, // 也支持 async 函数
}
```
## 图片
如果用于 Typescript ,需定义图片元素类型。可单独放在 `.d.ts` 中定义。
```ts
import { SlateElement } from '@wangeditor-next/editor'
type ImageElement = SlateElement & {
src: string
alt: string
url: string
href: string
}
```
图片菜单的配置
- `onInsertedImage` 插入图片之后的回调
- `onUpdatedImage` 更新图片之后的回调
- `checkImage` 校验图片链接
- `parseImageSrc` 转换图片链接
```ts
// 自定义校验图片
function customCheckImageFn(src: string, alt: string, url: string): boolean | undefined | string { // TS 语法
// function customCheckImageFn(src, alt, url) { // JS 语法
if (!src) {
return
}
if (src.indexOf('http') !== 0) {
return '图片网址必须以 http/https 开头'
}
return true
// 返回值有三种选择:
// 1. 返回 true ,说明检查通过,编辑器将正常插入图片
// 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
// 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止插入。但不会提示任何信息
}
// 转换图片链接
function customParseImageSrc(src: string): string { // TS 语法
// function customParseImageSrc(src) { // JS 语法
if (src.indexOf('http') !== 0) {
return `http://${src}`
}
return src
}
// 插入图片
editorConfig.MENU_CONF['insertImage'] = {
onInsertedImage(imageNode: ImageElement | null) { // TS 语法
// onInsertedImage(imageNode) { // JS 语法
if (imageNode == null) return
const { src, alt, url, href } = imageNode
console.log('inserted image', src, alt, url, href)
},
checkImage: customCheckImageFn, // 也支持 async 函数
parseImageSrc: customParseImageSrc, // 也支持 async 函数
}
// 编辑图片
editorConfig.MENU_CONF['editImage'] = {
onUpdatedImage(imageNode: ImageElement | null) { // TS 语法
// onUpdatedImage(imageNode) { // JS 语法
if (imageNode == null) return
const { src, alt, url } = imageNode
console.log('updated image', src, alt, url)
},
checkImage: customCheckImageFn, // 也支持 async 函数
parseImageSrc: customParseImageSrc, // 也支持 async 函数
}
```
## 上传图片
上传图片的配置比较复杂,拆分为几个部分来讲解。可参考这个 [demo](https://github.com/wangeditor-next/server)。
```ts{2}
editorConfig.MENU_CONF['uploadImage'] = {
// 上传图片的配置
}
```
### 服务端地址
使用内置上传器时 **必填**,否则上传图片会报错。
如果你使用 `customUpload` 或 `uploadAdapter` ,则不需要配置 `server` 。
```ts
editorConfig.MENU_CONF['uploadImage'] = {
server: '/api/upload',
}
```
**【特别注意】服务端 response body 格式要求如下:**
上传成功的返回格式:
```ts
{
"errno": 0, // 注意:值是数字,不能是字符串
"data": {
"url": "xxx", // 图片 src ,必须
"alt": "yyy", // 图片描述文字,非必须
"href": "zzz" // 图片的链接,非必须
}
}
```
上传失败的返回格式:
```ts
{
"errno": 1, // 只要不等于 0 就行
"message": "失败信息"
}
```
:::tip
如果你的服务端 response body 无法按照上述格式,可以使用下文的 `customInsert`
:::
### 基本配置
```ts
editorConfig.MENU_CONF['uploadImage'] = {
// form-data fieldName ,默认值 'wangeditor-uploaded-image'
fieldName: 'your-custom-name',
// 单个文件的最大体积限制,默认为 2M
maxFileSize: 1 * 1024 * 1024, // 1M
// 最多可上传几个文件,默认为 100
maxNumberOfFiles: 10,
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
allowedFileTypes: ['image/*'],
// 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
meta: {
token: 'xxx',
otherKey: 'yyy'
},
// 将 meta 拼接到 url 参数中,默认 false
metaWithUrl: false,
// 自定义增加 http header
headers: {
Accept: 'text/x-json',
otherKey: 'xxx'
},
// 跨域是否传递 cookie ,默认为 false
withCredentials: true,
// 超时时间,默认为 10 秒
timeout: 5 * 1000, // 5 秒
}
```
### 回调函数
```ts
editorConfig.MENU_CONF['uploadImage'] = {
// 上传之前触发
onBeforeUpload(file: File) { // TS 语法
// onBeforeUpload(file) { // JS 语法
// file 选中的文件,格式如 { key: file }
return file
// 可以 return
// 1. return file 或者 new 一个 file ,接下来将上传
// 2. return false ,不上传这个 file
},
// 上传进度的回调函数
onProgress(progress: number) { // TS 语法
// onProgress(progress) { // JS 语法
// progress 是 0-100 的数字
console.log('progress', progress)
},
// 单个文件上传成功之后
onSuccess(file: File, res: any) { // TS 语法
// onSuccess(file, res) { // JS 语法
console.log(`${file.name} 上传成功`, res)
},
// 单个文件上传失败
onFailed(file: File, res: any) { // TS 语法
// onFailed(file, res) { // JS 语法
console.log(`${file.name} 上传失败`, res)
},
// 上传错误,或者触发 timeout 超时
onError(file: File, err: any, res: any) { // TS 语法
// onError(file, err, res) { // JS 语法
console.log(`${file.name} 上传出错`, err, res)
},
}
```
### 自定义功能
如果用于 Typescript ,则要定义插入函数的类型。
```ts
type InsertFnType = (url: string, alt: string, href: string) => void
```
#### 自定义插入
如果你的服务端 response body 无法按照上文规定的格式,则无法插入图片,提示失败。
但你可以使用 `customInsert` 来自定义插入图片。
```ts
editorConfig.MENU_CONF['uploadImage'] = {
// 自定义插入图片
customInsert(res: any, insertFn: InsertFnType) { // TS 语法
// customInsert(res, insertFn) { // JS 语法
// res 即服务端的返回结果
// 从 res 中找到 url alt href ,然后插入图片
insertFn(url, alt, href)
},
}
```
#### 自定义上传
如果你不想使用 wangEditor 自带的上传功能,例如你要上传到阿里云 OSS 。
可以通过 `customUpload` 来自定义上传。
```ts
editorConfig.MENU_CONF['uploadImage'] = {
// 自定义上传
async customUpload(file: File, insertFn: InsertFnType) { // TS 语法
// async customUpload(file, insertFn) { // JS 语法
// file 即选中的文件
// 自己实现上传,并得到图片 url alt href
// 最后插入图片
insertFn(url, alt, href)
}
}
```
#### 自定义上传适配器
如果你想替换底层上传实现,但仍然复用 wangEditor 内置的上传回调和插入链路,可以使用 `uploadAdapter` 。
- `customUpload` 表示你完全接管上传和插入
- `uploadAdapter` 表示你只替换上传器实现,编辑器仍然复用现有的进度、成功、失败、错误和插入流程
- 如果同时配置了 `customUpload` 和 `uploadAdapter` ,则优先使用 `customUpload`
```ts
editorConfig.MENU_CONF['uploadImage'] = {
uploadAdapter({ config, editor }) {
const files: File[] = []
return {
addFiles(fileList) {
files.push(...fileList.map(item => item.data as File))
},
async upload() {
for (const file of files) {
const fileInfo = {
name: file.name,
type: file.type,
size: file.size,
}
try {
// 例如:上传到 OSS / S3 / 自定义服务
const res = await myUpload(file, editor)
config.onProgress?.(100)
config.onSuccess(fileInfo, {
errno: 0,
data: {
url: res.url,
alt: file.name,
href: res.url,
},
})
} catch (err) {
config.onError(fileInfo, err, null)
}
}
},
}
},
}
```
:::tip
`uploadAdapter` 需要在合适的时机主动调用 `config.onProgress`、`config.onSuccess`、`config.onError` 等回调。
如果你想复用编辑器默认插入逻辑,请给 `config.onSuccess` 传入和内置上传一致的 response body;如果你的返回格式不同,可以配合 `customInsert` 一起使用。
:::
#### 自定义选择图片
如果你不想使用 wangEditor 自带的选择文件功能,例如你有自己的图床,或者图片选择器。
可以通过 `customBrowseAndUpload` 来自己实现选择图片、上传图片,并插入图片。
```ts
editorConfig.MENU_CONF['uploadImage'] = {
// 自定义选择图片
customBrowseAndUpload(insertFn: InsertFnType) { // TS 语法
// customBrowseAndUpload(insertFn) { // JS 语法
// 自己选择文件
// 自己上传文件,并得到图片 url alt href
// 最后插入图片
insertFn(url, alt, href)
}
}
```
### base64 插入图片
```ts
editorConfig.MENU_CONF['uploadImage'] = {
// 其他配置...
// 小于该值就插入 base64 格式(而不上传),默认为 0
base64LimitSize: 5 * 1024 // 5kb
}
```
### 获取已删除的图片
这是一个常见的需求。
上传图片到编辑器,然后又把图片删除了。此时你可能想要拿到这张删除的图片,在服务器也把图片文件删了。
- 使用 [onInsertedImage](./menu-config.md#图片) 来收集所有上传或者插入的图片,记录为 `imageList1`
- 最后保存编辑器内容之前,使用 `editor.getElemsByType('image')` 获取当前编辑器的所有图片,记录为 `imageList2`
- 对比 `imageList1` 和 `imageList2` ,两者的差异,就是删除过的图片
可能会有疑问:为何要在最后去对比?我想要在图片删除时就及时得到反馈。
但,这样是不行的,因为图片删除了,还可能会被**撤销**回来。所以,一定要在最后去操作。
## 视频
如果用于 Typescript ,需定义视频元素类型。可单独放在 `.d.ts` 中定义。
```ts
import { SlateElement } from '@wangeditor-next/editor'
type VideoElement = SlateElement & {
src: string
poster?: string
}
```
菜单配置
- `onInsertedVideo` 插入视频之后的回调
- `checkVideo` 校验视频链接
- `parseVideoSrc` 转换视频链接
```ts
// 自定义校验视频
function customCheckVideoFn(src: string, poster: string): boolean | string | undefined { // TS 语法
// function customCheckVideoFn(src, poster) { // JS 语法
if (!src) {
return
}
if (src.indexOf('http') !== 0) {
return '视频地址必须以 http/https 开头'
}
return true
// 返回值有三种选择:
// 1. 返回 true ,说明检查通过,编辑器将正常插入视频
// 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
// 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止插入。但不会提示任何信息
}
// 自定义转换视频
function customParseVideoSrc(src: string): string { // TS 语法
// function customParseVideoSrc(src) { // JS 语法
if (src.includes('.bilibili.com')) {
// 转换 bilibili url 为 iframe (仅作为示例,不保证代码正确和完整)
const arr = location.pathname.split('/')
const vid = arr[arr.length - 1]
return ``
}
return src
}
editorConfig.MENU_CONF['insertVideo'] = {
onInsertedVideo(videoNode: VideoElement | null) { // TS 语法
// onInsertedVideo(videoNode) { // JS 语法
if (videoNode == null) return
const { src } = videoNode
console.log('inserted video', src)
},
checkVideo: customCheckVideoFn, // 也支持 async 函数
parseVideoSrc: customParseVideoSrc, // 也支持 async 函数
}
```
## 上传视频
上传视频的配置比较复杂,拆分为几个部分来讲解。可参考这个 [demo](https://github.com/wangeditor-next/server)。
```ts{2}
editorConfig.MENU_CONF['uploadVideo'] = {
// 上传视频的配置
}
```
### 服务端地址
使用内置上传器时 **必填**,否则上传视频会报错。
如果你使用 `customUpload` 或 `uploadAdapter` ,则不需要配置 `server` 。
```ts
editorConfig.MENU_CONF['uploadVideo'] = {
server: '/api/upload',
}
```
**【特别注意】服务端 response body 格式要求如下:**
上传成功的返回格式:
```json
{
"errno": 0, // 注意:值是数字,不能是字符串
"data": {
"url": "xxx", // 视频 src ,必须
"poster": "xxx.png" // 视频封面图片 url ,可选
}
}
// 注意:@wangeditor-next/editor 版本 >= 5.1.8 才支持 video poster
```
上传失败的返回格式:
```json
{
"errno": 1, // 只要不等于 0 就行
"message": "失败信息"
}
```
:::tip
如果你的服务端 response body 无法按照上述格式,可以使用下文的 `customInsert`
:::
### 基本配置
```ts
editorConfig.MENU_CONF['uploadVideo'] = {
// form-data fieldName ,默认值 'wangeditor-uploaded-video'
fieldName: 'your-custom-name',
// 单个文件的最大体积限制,默认为 10M
maxFileSize: 5 * 1024 * 1024, // 5M
// 最多可上传几个文件,默认为 5
maxNumberOfFiles: 3,
// 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
allowedFileTypes: ['video/*'],
// 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
meta: {
token: 'xxx',
otherKey: 'yyy'
},
// 将 meta 拼接到 url 参数中,默认 false
metaWithUrl: false,
// 自定义增加 http header
headers: {
Accept: 'text/x-json',
otherKey: 'xxx'
},
// 跨域是否传递 cookie ,默认为 false
withCredentials: true,
// 超时时间,默认为 30 秒
timeout: 15 * 1000, // 15 秒
// 视频不支持 base64 格式插入
}
```
### 回调函数
```ts
editorConfig.MENU_CONF['uploadVideo'] = {
// 上传之前触发
onBeforeUpload(file: File) { // TS 语法
// onBeforeUpload(file) { // JS 语法
// file 选中的文件,格式如 { key: file }
return file
// 可以 return
// 1. return file 或者 new 一个 file ,接下来将上传
// 2. return false ,不上传这个 file
},
// 上传进度的回调函数
onProgress(progress: number) { // TS 语法
// onProgress(progress) { // JS 语法
// progress 是 0-100 的数字
console.log('progress', progress)
},
// 单个文件上传成功之后
onSuccess(file: File, res: any) { // TS 语法
// onSuccess(file, res) { // JS 语法
console.log(`${file.name} 上传成功`, res)
},
// 单个文件上传失败
onFailed(file: File, res: any) { // TS 语法
// onFailed(file, res) { // JS 语法
console.log(`${file.name} 上传失败`, res)
},
// 上传错误,或者触发 timeout 超时
onError(file: File, err: any, res: any) { // TS 语法
// onError(file, err, res) { // JS 语法
console.log(`${file.name} 上传出错`, err, res)
},
}
```
### 自定义功能
如果用于 Typescript ,则要定义插入函数的类型。
```ts
type InsertFnType = (url: string, poster: string = '') => void
```
#### 自定义插入
如果你的服务端 response body 无法按照上文规定的格式,则无法插入视频,提示失败。
但你可以使用 `customInsert` 来自定义插入视频。
```ts
editorConfig.MENU_CONF['uploadVideo'] = {
// 自定义插入视频
customInsert(res: any, insertFn: InsertFnType) { // TS 语法
// customInsert(res, insertFn) { // JS 语法
// res 即服务端的返回结果
// 从 res 中找到 url poster ,然后插入视频
insertFn(url, poster)
},
}
```
#### 自定义上传
如果你不想使用 wangEditor 自带的上传功能,例如你要上传到阿里云 OSS 。
可以通过 `customUpload` 来自定义上传。
```ts
editorConfig.MENU_CONF['uploadVideo'] = {
// 自定义上传
async customUpload(file: File, insertFn: InsertFnType) { // TS 语法
// async customUpload(file, insertFn) { // JS 语法
// file 即选中的文件
// 自己实现上传,并得到视频 url poster
// 最后插入视频
insertFn(url, poster)
}
}
```
#### 自定义上传适配器
如果你想替换底层上传实现,但仍然复用 wangEditor 内置的上传回调和插入链路,可以使用 `uploadAdapter` 。
- `customUpload` 表示你完全接管上传和插入
- `uploadAdapter` 表示你只替换上传器实现,编辑器仍然复用现有的进度、成功、失败、错误和插入流程
- 如果同时配置了 `customUpload` 和 `uploadAdapter` ,则优先使用 `customUpload`
```ts
editorConfig.MENU_CONF['uploadVideo'] = {
uploadAdapter({ config, editor }) {
const files: File[] = []
return {
addFiles(fileList) {
files.push(...fileList.map(item => item.data as File))
},
async upload() {
for (const file of files) {
const fileInfo = {
name: file.name,
type: file.type,
size: file.size,
}
try {
const res = await myUpload(file, editor)
config.onProgress?.(100)
config.onSuccess(fileInfo, {
errno: 0,
data: {
url: res.url,
poster: res.poster || "",
},
})
} catch (err) {
config.onError(fileInfo, err, null)
}
}
},
}
},
}
```
:::tip
`uploadAdapter` 需要在合适的时机主动调用 `config.onProgress`、`config.onSuccess`、`config.onError` 等回调。
如果你想复用编辑器默认插入逻辑,请给 `config.onSuccess` 传入和内置上传一致的 response body;如果你的返回格式不同,可以配合 `customInsert` 一起使用。
:::
#### 自定义选择视频
如果你不想使用 wangEditor 自带的选择文件功能,例如你有自己的图床,或者视频文件选择器。
可以通过 `customBrowseAndUpload` 来自己实现选择视频、上传视频,并插入视频。
```ts
editorConfig.MENU_CONF['uploadVideo'] = {
// 自定义选择视频
customBrowseAndUpload(insertFn: InsertFnType) { // TS 语法
// customBrowseAndUpload(insertFn) { // JS 语法
// 自己选择文件
// 自己上传文件,并得到视频 url poster
// 最后插入视频
insertFn(url, poster)
}
}
```
## 表格
- `minWidth` 单元格最小宽度
- `tableHeader` 表头
- `tableFullWidth` 表格宽度自适应
- `widthExportMode` 表格宽度导出策略(`explicit` | `adaptive`,默认 `explicit`)
- `insertTableCol.insertPosition` 插入列位置(`before` | `after`,默认 `before`)
`tableFullWidth` 点击后会切换为 `width: 100%` 的响应式模式。后续容器宽度变化时,表格会自动跟随,无需再次点击。
```ts
editorConfig.MENU_CONF['insertTable'] = {
minWidth: 60,
// 表头
tableHeader: {
selected: false, // 默认不启用表头
},
// 表格宽度自适应
tableFullWidth: {
selected: true, // 默认启用表格宽度自适应
},
// 表格宽度导出策略
// explicit: 兼容历史行为,优先导出固定像素宽
// adaptive: 保留 width:auto(适合不希望自动固化列宽的场景)
widthExportMode: 'explicit',
}
```
如果你希望 `setHtml` 导入 `width:auto` 的表格后,`getHtml` 仍然保持 `width:auto`,可以开启 `adaptive`:
```ts
editorConfig.MENU_CONF['insertTable'] = {
widthExportMode: 'adaptive',
}
```
```ts
editorConfig.MENU_CONF['insertTableCol'] = {
// 'before' 表示在当前列前插入(默认)
// 'after' 表示在当前列后插入
insertPosition: 'after',
}
```
## 代码高亮
- `codeLangs` 配置代码语言
- `selected: true` 配置代码块默认语言 **(可选)**
```ts
editorConfig.MENU_CONF['codeSelectLang'] = {
// 代码语言
codeLangs: [
{ text: 'CSS', value: 'css', selected: true },
{ text: 'HTML', value: 'html' },
{ text: 'XML', value: 'xml' },
// 其他
]
}
```
:::tip
配置代码语言时,只能从 `editor.getMenuConfig('codeSelectLang').codeLangs` 中选择,不能自己随意增加。
如有其他语言的需要,可以给我们提交 issue ,这需要修改源码。
:::
## 代码块
- `showCopyButton` 是否显示代码块复制按钮(默认 `false`)
```ts
editorConfig.MENU_CONF['codeBlock'] = {
showCopyButton: true,
}
```
## 其他
其他菜单的配置,请参考上文的 [通用方法](#通用方法) 自行修改。
---
## 10. API.md
# 编辑器 API
## config 相关
### getConfig
获取编辑器所有配置
```ts
editor.getConfig()
```
### getMenuConfig
获取单个 menu 的配置。menu 配置相关的可参考[这里](./menu-config.md)。
```ts
editor.getMenuConfig(menuKey)
```
### getAllMenuKeys
获取编辑器所有 menu 的 key
```ts
editor.getAllMenuKeys()
```
### alert
编辑器 alert ,可通过 [customAlert](./editor-config.md#customalert) 配置。
```ts
editor.alert('错误信息', 'error')
```
## 内容处理
### handleTab
控制编辑器按 tab 键时,输入什么。默认如下
```ts
editor.handleTab = () => editor.insertText(' ')
```
### getHtml
`editor.getHtml()` 获取**非格式化**的 html
```html
head
hello word
```
你可以自行格式化 html ,如使用 [xml-formatter](https://www.npmjs.com/package/xml-formatter)
### getHtmlWithId
`editor.getHtmlWithId(idKey?)` 获取带唯一标识属性的 HTML。
```ts
const html = editor.getHtmlWithId() // 默认属性名为 data-w-e-id
const html2 = editor.getHtmlWithId('data-node-id') // 自定义属性名
```
适用于需要在服务端或展示层做“节点追踪 / 精确定位”的场景。默认不影响 `editor.getHtml()` 的输出。
### getText
获取当前编辑器的纯文本内容
```ts
const text = editor.getText()
```
### setHtml
重置编辑器的 HTML 内容。【注意】只能解析 `editor.getHtml()` 返回的 HTML 格式,不支持自定义 HTML 格式。
```rs
editor.setHtml('hello
')
```
如果想插入一段 HTML ,请使用 [dangerouslyInsertHtml](#dangerouslyinserthtml)
### isEmpty
判断当前编辑器内容是否为空(只有一个空段落)
```ts
editor.isEmpty()
```
:::tip
该方法只能识别**只有一个空段落**情况,其他情况(如有一个空标题、空表格)请使用 `editor.getText()` 来判断。
:::
### getSelectionText
获取选中的文本
```ts
editor.getSelectionText()
```
### getElemsByType
通过 type 获取编辑器的 element 列表。
```ts
editor.getElemsByType('image') // 所有图片
editor.getElemsByType('link') // 所有链接
// 其他
```
### getElemsByTypePrefix
通过 type 前缀获取编辑器的 element 列表。
```ts
editor.getElemsByTypePrefix('header') // 获取所有标题 header1 header2 header3...
// 其他
```
### deleteBackward
向后删除,相当于按 backspace 键。
```ts
editor.deleteBackward()
```
### deleteForward
向后删除,相当于按 delete 键(部分键盘没有这个键)
```ts
editor.deleteForward()
```
### deleteFragment
删除选中的内容
```ts
editor.deleteFragment()
```
### getFragment
获取选中的内容,json 格式
```ts
editor.getFragment()
```
### insertBreak
在选区回车换行
```ts
editor.insertBreak()
```
### insertText
在选区插入文本
```ts
editor.insertText('xxx')
```
### dangerouslyInsertHtml
- 如果是 `editor.getHtml()` 获取的 HTML 格式,可以完美解析。
- 如果是其他的 HTML 格式,则不能保证语义正确 —— **dangerously** 。
```ts
editor.dangerouslyInsertHtml(`标题 文本 加粗
`)
```
:::tip
如果你想**重置**编辑器 HTML 内容,请使用 [setHtml](#sethtml)
:::
### clear
清空编辑器内容
```ts
editor.clear()
```
### undo
撤销
```ts
editor.undo()
```
### redo
重做
```ts
editor.redo()
```
### clear history
清空 undo / redo 历史栈。
```ts
editor.clearHistory()
```
常见用法是在 `editor.setHtml(...)` 或数据回显完成后执行一次,避免用户撤销回旧内容。
### history without merging
如果你需要让某次程序化插入单独成为一条历史记录,而不是和前一次操作合并,可以直接使用 `slate-history` 的 `HistoryEditor.withoutMerging(...)`。
```ts
import { HistoryEditor } from 'slate-history'
HistoryEditor.withoutMerging(editor as HistoryEditor, () => {
editor.insertText('plain text')
})
```
这类用法常见于“粘贴 HTML 后,用户选择转成纯文本再插入”的场景。因为编辑器实例运行时已经经过 `slate-history` 扩展,所以不需要额外等待 wangEditor 再封装一个新 API。
## 节点操作
使用节点操作 API 前,请查看 [节点数据结构](./node-define.md) 。
### insertNode
在选区插入一个节点
```ts
const node = { type: 'paragraph', children: [{ text: 'simple text' }] }
editor.insertNode(node)
```
### insertNodes
在选区插入多个节点
```ts
import { SlateTransforms } from '@wangeditor-next/editor'
const node1 = { type: 'paragraph', children: [{ text: 'aaa' }] }
const node2 = { type: 'paragraph', children: [{ text: 'bbb' }] }
const nodeList = [node1, node2]
SlateTransforms.insertNodes(editor, nodeList)
```
### removeNodes
删除选区所在的节点
```ts
import { SlateTransforms } from '@wangeditor-next/editor'
SlateTransforms.removeNodes(editor)
```
### 获取选中节点
可使用 `SlateEditor.nodes` 获取选中的节点。详情可参考 [Slate.js](https://docs.slatejs.org/) 中的 `Editor.nodes` API 。
```ts
import { SlateEditor, SlateElement, SlateNode } from '@wangeditor-next/editor'
const nodeEntries = SlateEditor.nodes(editor, {
match: (node: SlateNode) => { // TS syntax
// match: (node) => { // JS syntax
if (SlateElement.isElement(node)) {
if (node.type === 'paragraph') {
return true // 匹配 paragraph
}
}
return false
},
universal: true,
})
if (nodeEntries == null) {
console.log('当前未选中的 paragraph')
} else {
for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry
console.log('选中了 paragraph 节点', node)
console.log('节点 path 是', path)
}
}
```
### setNodes
设置选中节点的属性
```ts
import { SlateTransforms } from '@wangeditor-next/editor'
SlateTransforms.setNodes(editor, {
// @ts-ignore
textAlign: 'right'
}, {
mode: 'highest' // 针对最高层级的节点
})
```
### getParentNode
获取一个节点的父节点
```ts
const parentNode = editor.getParentNode(node) // 返回 node 或者 null
```
### toDOMNode
获取一个节点对应的 DOM 节点
```ts
const elem = editor.toDOMNode(node) // 返回 HTMLElement
```
### isInline
判断一个节点是否是 inline
```ts
const inline = editor.isInline(node)
```
### isVoid
判断一个节点是否是 void
```ts
const void = editor.isVoid(node)
```
:::tip
void node 即没有子元素的节点(它本身就可以看作是一个特殊字符),例如 image video 。可参考 [html void element](https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-element) 定义。
你可以通过 `editor.isVoid` 自定义哪些元素是 void ,但需要详细学习 slate 。
:::
### isText
判断一个节点是否是 text
```ts
import { SlateText } from '@wangeditor-next/editor'
SlateText.isText(node) // true/false
```
### isElement
判断一个节点是否是 elem
```ts
import { SlateElement } from '@wangeditor-next/editor'
SlateElement.isElement(node) // true/false
```
### addMark
为选中的文本添加标记(文本样式)
```ts
editor.addMark('bold', true) // 加粗
editor.addMark('color', '#999') // 文本颜色
```
### removeMark
对选中的文字,取消标记(文本样式)
```ts
editor.removeMark('bold') // 取消加粗
```
### marks
获取选中文字的标记(文本样式)
```ts
import { SlateEditor } from '@wangeditor-next/editor'
SlateEditor.marks(editor) // 例如 { bold: true, color: "#595959" }
```
## DOM 相关
### id 属性
获取编辑器 id
```ts
editor.id // 如 'wangEditor-1'
```
### isFullScreen 属性
编辑器是否全屏
```ts
editor.isFullScreen // true/false
```
### focus
聚焦到编辑器
```ts
editor.focus()
// editor.focus(true) // 选区定位到最后
```
### blur
失焦编辑器
```ts
editor.blur()
```
### isFocused
判断当前编辑器是否聚焦?
```ts
editor.isFocused() // true/false
```
### updateView
强制更新视图
```ts
editor.updateView()
```
:::tip
updateView 是内部 API ,不建议用户使用。如要使用,也请勿频繁执行。
:::
### scrollToElem
滚动到指定元素,类似锚点。如滚动到某个标题的位置。可实现标题目录,参考 [demo](https://wangeditor-next.github.io/demo/catalog.html)。
可根据 `toDOMNode` 获取 node 对应的 DOM 元素。
```ts
editor.scrollToElem(elemId)
```
### showProgressBar
显示进度条,一般用于上传功能
```ts
editor.showProgressBar(progress) // progress 为 0-100 的数字
```
### hidePanelOrModal
隐藏当前的弹框 (如插入链接) 和下拉列表(如设置标题、设置字体)
```ts
editor.hidePanelOrModal()
```
### fullScreen
设置为全屏
```ts
editor.fullScreen()
```
:::tip
全屏功能,有 html 结构的要求,请参考[这里](./getting-started.md#全屏)
:::
### unFullScreen
取消全屏
```ts
editor.unFullScreen()
```
### disable
禁用编辑器,设置为只读
```ts
editor.disable()
```
### isDisabled
判断当前编辑器是否只读?
```ts
editor.isDisabled() // true/false
```
### enable
取消禁用,取消只读
```ts
editor.enable()
```
### destroy
销毁编辑器和工具栏
```ts
editor.destroy()
```
:::tip
destroy 仅仅是移除编辑器、工具栏的 DOM 节点,全局绑定的事件等。
自己定义的变量,如 `const editor = createEditor({...})` ,这个 `editor` 还需要自己来销毁。
:::
### 获取编辑区域容器 DOM
获取编辑区域容器 DOM 节点
```ts
editor.getEditableContainer()
```
## selection 相关
selection 数据结构参考 [slate Location](https://docs.slatejs.org/concepts/03-locations) 。
### selection 属性
获取编辑器当前的选区。如果未选中,则返回 `null` 。
```ts
editor.selection // selection 或 null
```
selection 数据结构如下:
```json
{
"anchor": { "path": [1,0], "offset":8 },
"focus": { "path": [1,0], "offset":10 }
}
```
### select
选中一个指定的选区。
```ts
const newSelection = {
anchor: { path: [1,0], offset:8 },
focus: { path: [1,0], offset:10 }
}
editor.select(newSelection)
```
### selectAll
选中所有内容
```ts
editor.selectAll()
```
### deselect
取消选中
```ts
editor.deselect()
```
### move
移动光标
```ts
editor.move(3) // 移动 3 个字符
```
### moveReverse
反向移动光标
```ts
editor.moveReverse(2) // 反向移动 2 个字符
```
### restoreSelection
恢复最近一次非 null 选区。如编辑器 blur 之后,再重新恢复选区。
```ts
editor.restoreSelection()
```
### isSelectedAll
判断编辑器是否全部选中。
```ts
editor.isSelectedAll() // true/false
```
### getSelectionPosition
获取选区的定位,**将视情况返回 `left` `right` `top` `bottom` 的其中几个**。
```ts
editor.getSelectionPosition() // 例如 { left: "80.15px", top: "116px" }
```
【注意】该定位是**相对于编辑区域**的,而非 body 。
你可以获取编辑区域 DOM 元素的定位 `editor.getEditableContainer().getBoundingClientRect()` 从而计算出相对于 body 的定位。
### getNodePosition
获取某个节点的定位,**将视情况返回 `left` `right` `top` `bottom` 的其中几个**。
```ts
editor.getNodePosition(node) // 例如 { left: "80.15px", top: "116px" }
```
【注意】该定位是**相对于编辑区域**的,而非 body。
你可以获取编辑区域 DOM 元素的定位 `editor.getEditableContainer().getBoundingClientRect()` 从而计算出相对于 body 的定位。
## 自定义事件
wangEditor 使用 [event-emitter](https://www.npmjs.com/package/event-emitter) 来做自定义事件。
### on
监听某个事件
```ts
editor.on('event-key', fn)
```
### off
取消监听
```ts
editor.off('event-key', fn)
```
### once
只监听一次
```ts
editor.once('event-key', fn)
```
### emit
触发事件
```ts
editor.emit('event-key')
```
### 内置的事件
```ts
editor.on('fullScreen', () => { console.log('fullScreen') })
editor.on('unFullScreen', () => { console.log('unFullScreen') })
editor.on('scroll', () => { console.log('scroll') })
editor.on('modalOrPanelShow', modalOrPanel => { console.log(modalOrPanel) })
editor.on('modalOrPanelHide', () => { console.log('modalOrPanelHide') })
```
## 使用 slate 解锁更多 API
> wangEditor 基于 [slate.js](https://docs.slatejs.org/)(但不依赖 React)开发
上文已列出了比较常用的 API ,但这并不是全部。 slate.js 还提供了更多 API ,可满足你的所有操作需求。
### Transforms API
参考 [slate Transforms API](https://docs.slatejs.org/api/transforms)
使用如下方式即可得到 slate Transforms 对象,不用再单独安装 slate 。
```ts
import { SlateTransforms } from '@wangeditor-next/editor'
```
### Node Editor API
参考 [slate Node API](https://docs.slatejs.org/api/nodes)
使用如下方式即可得到 slate Node 相关对象,不用再单独安装 slate 。
```ts
import { SlateEditor, SlateNode, SlateElement, SlateText } from '@wangeditor-next/editor'
```
### Location API
参考 [slate Location API](https://docs.slatejs.org/api/locations)
使用如下方式即可得到 slate Location 相关对象,不用再单独安装 slate 。
```ts
import { SlateLocation, SlatePath, SlatePoint, SlateRange } from '@wangeditor-next/editor'
```
---
## 11. node-define.md
# 节点数据结构
wangEditor 是基于 slate.js 为内核开发的,所以学习本文之前,要先了解 [slate Node 设计](https://docs.slatejs.org/concepts/02-nodes) 。
## 是什么
很多同学可能根本不知道本文要讲什么,对于这里的“节点”和“数据结构”也不知何意。
没关系,接下来通过几个问题,就可以让你快速入门。
我们通过 [API](./API.md) 的学习,已经知道了 wangEditor 有丰富的 API 可供使用。
那么问题来了:
- `editor.addMark(key, value)` 可以设置文本样式,如何设置删除线呢?此时 `key` `value` 该怎么写?
- `editor.insertNode(node)` 可以插入一个节点,如何插入一个链接呢?此时 `node` 该怎么写?
- `SlateTransforms.setNodes(editor, {...})` 可以设置节点的属性,如何设置行高呢?此时 `{...}` 这个属性该怎么写?
通过上述问题,你大概知道了本文的目的 —— 就是告诉你,编辑器内所有内容、节点的数据结构 —— 它们都是由哪些数据构成的。
## 快速了解
如果想快速了解各个节点的数据结构,其实方法很简单。
- 创建一个编辑器,操作一下
- 查看 `editor.children`
例如,写一段文字、设置一个标题或列表,查看 `editor.children` 即可看到他们的数据结构

再例如,对文字设置行高,设置文本样式,查看 `editor.children` 即可看到他们的数据结构

## Text Node
文本节点,例如 `{ text: 'hello' }` **必须有 `text` 属性**。还可以自定义属性,例如加粗的文本可表示为 `{ text: 'hello', bold: true }` ,其他属性可自行扩展。
注意,文本节点是底层节点,所以没有子节点,**没有 `children` 属性**。
## Element Node
元素节点,例如 `{ type: 'header1', children: [ { text: 'hello' } ] }` **必须有两个属性 `type` 和 `children` 属性**。还可以自定义属性,例如居中对齐可表示为 `{ type: 'header1', textAlign: 'center', children: [ { text: 'hello' } ] }` ,其他属性自行扩展。
## Inline Element
元素默认是 block 显示,即占满一整行。但有些元素需要变为 inline 显示,如 ` ` `` 等。
我们可以**通过[插件](./development.md#劫持编辑器事件和操作-插件)来修改 `isInline` 把一个元素改为 inline** ,参考链接元素的[插件源码](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/link/plugin.ts)。
## Void Element
有些元素需要定义为 void 类型(即没有子节点),例如 ` ` `` 等。
我们可以**通过[插件](./development.md#劫持编辑器事件和操作-插件)来修改 `isVoid` 把一个元素改为 void** ,参考图片元素的[插件源码](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/image/plugin.ts)。
注意,void 类型虽然在语义上没有子节点,但 slate.js 规定,**它必须有一个 `children` 属性,其中只有一个空字符串**。例如图片元素:
```js
{
type: 'image',
// 其他属性 ...
children: [{ text: '' }] // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
}
```
## 各种节点的数据结构
详细的节点数据结构,可以直接查看源码中 `type` 定义。
- [文本样式](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/text-style/custom-types.ts) - 扩展 text node 属性
- [文字颜色 背景色](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/color/custom-types.ts) - 扩展 text node 属性
- [段落](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/paragraph/custom-types.ts) - 定义 element node
- [行高](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/line-height/custom-types.ts) - 扩展 element node 属性
- [字号 字体](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/font-size-family/custom-types.ts) - 扩展 text node 属性
- [对齐](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/justify/custom-types.ts) - 扩展 element node 属性
- [缩进](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/indent/custom-types.ts) - 扩展 element node 属性
- [链接](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/link/custom-types.ts) - 定义 **inline** element node
- [标题](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/header/custom-types.ts) - 定义 element node
- [引用](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/blockquote/custom-types.ts) - 定义 element node
- [图片](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/image/custom-types.ts) - 定义 **inline void** element node
- [分割线](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/divider/custom-types.ts) - 定义 **void** element node
- [代码块](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/basic-modules/src/modules/code-block/custom-types.ts) - 定义 element node
- [列表](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/list-module/src/module/custom-types.ts) - 定义 element node
- [表格](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/table-module/src/module/custom-types.ts) - 定义 element node
- [视频](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/video-module/src/module/custom-types.ts) - 定义 **void** element node
---
## 12. development.md
# 自定义扩展新功能
快速了解可查看[视频教程](./video-course.md)。
wangEditor 从 V5 开始,源码上就分离了 core editor 还有各个 module 。
core 是核心 API ,editor 负责汇总集成。所有的具体功能,都分布在各个 module 中来实现。

基于这种扩展性,官方开发了几个常用的[插件](./plugins.md),其源码也可作为二次开发的参考。
## 注册新菜单
菜单分为几种
- ButtonMenu 按钮菜单,如 加粗、斜体
- SelectMenu 下拉菜单,如 标题、字体、行高
- DropPanelMenu 下拉面板菜单,如 字体颜色、创建表格
- ModalMenu 弹出框菜单,如 插入链接、插入网络图片
### ButtonMenu
可参考这个 [demo](https://wangeditor-next.github.io/demo/extend-menu.html) 网页源码。在实际开发中,会用到很多 editor [API](./API.md) 。
第一,定义菜单 class
```ts
import { IButtonMenu, IDomEditor } from '@wangeditor-next/editor'
class MyButtonMenu implements IButtonMenu { // TS 语法
// class MyButtonMenu { // JS 语法
constructor() {
this.title = 'My menu title' // 自定义菜单标题
// this.iconSvg = '... ' // 可选
this.tag = 'button'
}
// 获取菜单执行时的 value ,用不到则返回空 字符串或 false
getValue(editor: IDomEditor): string | boolean { // TS 语法
// getValue(editor) { // JS 语法
return ' hello '
}
// 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
isActive(editor: IDomEditor): boolean { // TS 语法
// isActive(editor) { // JS 语法
return false
}
// 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
isDisabled(editor: IDomEditor): boolean { // TS 语法
// isDisabled(editor) { // JS 语法
return false
}
// 点击菜单时触发的函数
exec(editor: IDomEditor, value: string | boolean) { // TS 语法
// exec(editor, value) { // JS 语法
if (this.isDisabled(editor)) return
editor.insertText(value) // value 即 this.value(editor) 的返回值
}
}
```
第二,[注册菜单到 wangEditor](#注册菜单到-wangeditor)
第三,[插入菜单到工具栏](#插入菜单到工具栏)
到此,自定义菜单就已经注册成功了,参考这个 [demo](https://wangeditor-next.github.io/demo/extend-menu.html)
### SelectMenu
可参考这个 [demo](https://wangeditor-next.github.io/demo/extend-menu-select.html) 网页源码。在实际开发中,会用到很多 editor [API](./API.md) 。
第一,定义菜单 class
```ts
import { IDomEditor, ISelectMenu } from '@wangeditor-next/editor'
class MySelectMenu implements ISelectMenu { // TS 语法
// class MySelectMenu { // JS 语法
constructor() {
this.title = 'My Select Menu',
this.tag = 'select'
this.width = 60
}
// 下拉框的选项
getOptions(editor: IDomEditor) { // TS 语法
// getOptions(editor) { // JS 语法
const options = [
{ value: 'beijing', text: '北京', styleForRenderMenuList: { 'font-size': '32px', 'font-weight': 'bold' } },
{ value: 'shanghai', text: '上海', selected: true },
{ value: 'shenzhen', text: '深圳' }
]
return options
}
// 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
isActive(editor: IDomEditor): boolean { // TS 语法
// isActive(editor) { // JS 语法
return false
}
// 获取菜单执行时的 value ,用不到则返回空 字符串或 false
getValue(editor: IDomEditor): string | boolean { // TS 语法
// getValue(editor) { // JS 语法
return 'shanghai' // 匹配 options 其中一个 value
}
// 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
isDisabled(editor: IDomEditor): boolean { // TS 语法
// isDisabled(editor) { // JS 语法
return false
}
// 点击菜单时触发的函数
exec(editor: IDomEditor, value: string | boolean) { // TS 语法
// exec(editor, value) { // JS 语法
// Select menu ,这个函数不用写,空着即可
}
}
```
第二,[注册菜单到 wangEditor](#注册菜单到-wangeditor)
第三,[插入菜单到工具栏](#插入菜单到工具栏)
到此,自定义菜单就已经注册成功了,参考这个 [demo](https://wangeditor-next.github.io/demo/extend-menu-select.html)
### DropPanelMenu
可参考这个 [demo](https://wangeditor-next.github.io/demo/extend-menu-drop-panel.html) 网页源码。在实际开发中,会用到很多 editor [API](./API.md) 。
第一,定义菜单 class
```ts
import { IDomEditor, IDropPanelMenu } from '@wangeditor-next/editor'
class MyDropPanelMenu implements IDropPanelMenu { // TS 语法
// class MyDropPanelMenu { // JS 语法
constructor() {
this.title = 'My menu'
// this.iconSvg = '... '
this.tag = 'button'
this.showDropPanel = true
}
// 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
isActive(editor: IDomEditor): boolean { // TS 语法
// isActive(editor) { // JS 语法
return false
}
// 获取菜单执行时的 value ,用不到则返回空 字符串或 false
getValue(editor: IDomEditor): string | boolean { // TS 语法
// getValue(editor) { // JS 语法
return ''
}
// 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
isDisabled(editor: IDomEditor): boolean { // TS 语法
// isDisabled(editor) { // JS 语法
return false
}
// 点击菜单时触发的函数
exec(editor: IDomEditor, value: string | boolean) { // TS 语法
// exec(editor, value) { // JS 语法
// DropPanel menu ,这个函数不用写,空着即可
}
// 定义 DropPanel 内部的 DOM Element
getPanelContentElem(editor: IDomEditor): DOMElement { // TS 语法
// getPanelContentElem(editor) { // JS 语法
const $list = $(``)
$list.on('click', 'li', function () {
editor.insertText(this.innerHTML)
editor.insertText(' ')
})
return $list[0] // 返回 DOM Element 类型
// PS:也可以把 $list 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
}
}
```
第二,[注册菜单到 wangEditor](#注册菜单到-wangeditor)
第三,[插入菜单到工具栏](#插入菜单到工具栏)
到此,自定义菜单就已经注册成功了,参考这个 [demo](htthttps://wangeditor-next.github.io/demo/extend-menu-drop-panel.html)
### ModalMenu
可参考这个 [demo](https://wangeditor-next.github.io/demo/extend-menu-modal.html) 网页源码。在实际开发中,会用到很多 editor [API](./API.md) 。
第一,定义菜单 class
```ts
import { IDomEditor, IModalMenu, SlateNode } from '@wangeditor-next/editor'
class MyModalMenu implements IModalMenu { // TS 语法
// class MyModalMenu { // JS 语法
constructor() {
this.title = 'My menu'
// this.iconSvg = '... '
this.tag = 'button'
this.showModal = true
this.modalWidth = 300
}
// 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
isActive(editor: IDomEditor): boolean { // TS 语法
// isActive(editor) { // JS 语法
return false
}
// 获取菜单执行时的 value ,用不到则返回空 字符串或 false
getValue(editor: IDomEditor): string | boolean { // TS 语法
// getValue(editor) { // JS 语法
return ''
}
// 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
isDisabled(editor: IDomEditor): boolean { // TS 语法
// isDisabled(editor) { // JS 语法
return false
}
// 点击菜单时触发的函数
exec(editor: IDomEditor, value: string | boolean) { // TS 语法
// exec(editor, value) { // JS 语法
// Modal menu ,这个函数不用写,空着即可
}
// 弹出框 modal 的定位:1. 返回某一个 SlateNode; 2. 返回 null (根据当前选区自动定位)
getModalPositionNode(editor: IDomEditor): SlateNode | null { // TS 语法
// getModalPositionNode(editor) { // JS 语法
return null // modal 依据选区定位
}
// 定义 modal 内部的 DOM Element
getModalContentElem(editor: IDomEditor): DOMElement { // TS 语法
// getModalContentElem(editor) { // JS 语法
const $content = $('
')
const $button = $('do something ')
$content.append($button)
$button.on('click', () => {
editor.insertText(' hello ')
})
return $content[0] // 返回 DOM Element 类型
// PS:也可以把 $content 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
}
}
```
第二,[注册菜单到 wangEditor](#注册菜单到-wangeditor)
第三,[插入菜单到工具栏](#插入菜单到工具栏)
到此,自定义菜单就已经注册成功了,参考这个 [demo](https://wangeditor-next.github.io/demo/extend-menu-modal.html)
#### 用 Vue React 组件实现 modal
如果你用 Vue React 开发了 modal 组件,想通过菜单来显示/隐藏
- 不用 ModalMenu ,改用最简单的 ButtonMenu
- 在 `exec` 函数中通过自定义事件(或其他方式)来控制 modal 组件的显示和隐藏
可再参考这个分享:[在 React 中更方便的扩展 Menu ,替代原有的 ModalMenu 方案](https://github.com/wangeditor-team/wangEditor/issues/4598)
### 注册菜单到 wangEditor
先根据菜单 class 来定义菜单配置
```js
const menu1Conf = {
key: 'menu1', // 定义 menu key :要保证唯一、不重复(重要)
factory() {
return new YourMenuClass() // 把 `YourMenuClass` 替换为你菜单的 class
},
}
// const menu2Conf = { ... }
// const menu3Conf = { ... }
```
然后,再把菜单注册到 wangEditor 。有两种选择:
第一,如果只注册一个菜单,没有别的功能了,则推荐使用 `registerMenu`
```ts
import { Boot } from '@wangeditor-next/editor'
Boot.registerMenu(menu1Conf)
```
第二,如果除了菜单之外还要同时注册其他能力,则建议使用 `registerModule`
```ts
import { Boot, IModuleConf } from '@wangeditor-next/editor'
const module: Partial = { // TS 语法
// const module = { // JS 语法
menus: [menu1Conf, menu2Conf, menu3Conf],
// 其他功能,下文讲解...
}
Boot.registerModule(module)
```
:::tip
- 必须在创建编辑器之前注册
- 全局只能注册一次,不要重复注册
:::
### 插入菜单到工具栏
在创建编辑器(或渲染 Vue React 组件时)注册到工具栏,可选择以下方式
- 注册到工具栏 [insertKeys](./toolbar-config.md#insertkeys)
- 注册到悬浮菜单 [hoverbarKeys](./editor-config.md#hoverbarkeys)
## 劫持编辑器事件和操作(插件)
如[支持 markdown 语法](https://github.com/wangeditor-next/wangEditor-next-plugin-md),以及 [ctrl + enter 回车](https://github.com/wangeditor-next/wangEditor-plugin-ctrl-enter)等。可参考它们的源码。
### 定义插件
在实际开发中,会用到很多 editor [API](./API.md) 。
```ts
import { IDomEditor } from '@wangeditor-next/editor'
function withBreakAndDelete(editor: T): T { // TS 语法
// function withBreakAndDelete(editor) { // JS 语法
const { insertBreak, deleteBackward } = editor // 获取当前 editor API
const newEditor = editor
// 重写 insertBreak 换行
newEditor.insertBreak = () => {
// if: 是 ctrl + enter ,则执行 insertBreak
insertBreak()
// else: 则不执行换行
return
}
// 重写 deleteBackward 向后删除
newEditor.deleteBackward = unit => {
// if: 某种情况下,执行默认的删除
deleteBackward(unit)
// else: 其他情况,则不执行删除
return
}
// 重写其他 API ...
// 返回 newEditor ,重要!
return newEditor
}
```
### 注册插件到 wangEditor
有两种方式。
第一,如果你仅仅注册一个插件,没有别的需求,则推荐使用 `registerPlugin`
```ts
import { Boot } from '@wangeditor-next/editor'
Boot.registerPlugin(withBreakAndDelete)
```
第二,如果你除了注册插件之外,同时还注册其他功能,则推荐使用 `registerModule`
```ts
import { Boot, IModuleConf } from '@wangeditor-next/editor'
const module: Partial = { // TS 语法
// const module = { // JS 语法
// menus: [menu1Conf, menu2Conf, menu3Conf], // 菜单
editorPlugin: withBreakAndDelete, // 插件
// 其他功能,下文讲解...
}
Boot.registerModule(module)
```
:::tip
- 必须在创建编辑器之前注册
- 全局只能注册一次,不要重复注册
:::
至此一个插件就注册完成,可以监听编辑器的 `insertBreak` 和 `deleteBackward` 事件。
## 定义新元素
编辑器默认只有基本的标题、列表、文字、图片、表格等元素,如果你想让编辑器渲染一个新元素,如 [数学公式](https://github.com/wangeditor-next/wangEditor-next/tree/master/packages/plugin-formula) [链接卡片](https://github.com/wangeditor-next/wangEditor-next/tree/master/packages/plugin-link-card) 等,你就需要根据本节内容来定义。
编辑器的输入和输出通常都是 HTML ,但其内部却有复杂的渲染机制,主要过程是:**model -> 生成 vdom -> 渲染 DOM**,如下图。
所以,我们也需要了解很多知识,定义很多函数来完成这一功能。不过别担心,它其实并难理解,跟着文档一步一步操作即可。

### 定义节点数据结构
数据驱动视图,这也是 Vue React 设计思路。要想显示什么,必须先定义相应的数据结构。
在此需要你详细了解 wangEditor [节点数据结构](./node-define.md)的相关知识,并熟悉以下知识点:
- Text node 和 Element node 区别
- 如何扩展 Text node 和 Element node 属性
- 如何设置 Inline node
- 如何设置 Void node ,以及它的 `children` 有何特点
例如,对“附件”元素,我们设计为: `type: 'attachment'` + inline + void ,然后扩展一些必要的属性,数据结构示例:
```ts
const myResume: AttachmentElement = { // TS 语法
// const resume = { // JS 语法
type: 'attachment'
fileName: 'resume.pdf'
link: 'https://xxx.com/files/resume.pdf'
children: [{ text: '' }] // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
}
```
如果你使用 TS , `AttachmentElement` 的定义在[这里](https://github.com/wangeditor-next/wangEditor-plugin-upload-attachment/blob/main/src/module/custom-types.ts)。
### 定义 inline 和 void
我们把“附件”元素设计为 inline 和 void ,就需要在代码中体现出来。
第一,定义一个插件,重写 `isInline` 和 `isVoid` API
```ts
import { DomEditor, IDomEditor } from '@wangeditor-next/editor'
function withAttachment(editor: T) { // TS 语法
// function withAttachment(editor) { // JS 语法
const { isInline, isVoid } = editor
const newEditor = editor
newEditor.isInline = elem => {
const type = DomEditor.getNodeType(elem)
if (type === 'attachment') return true // 针对 type: attachment ,设置为 inline
return isInline(elem)
}
newEditor.isVoid = elem => {
const type = DomEditor.getNodeType(elem)
if (type === 'attachment') return true // 针对 type: attachment ,设置为 void
return isVoid(elem)
}
return newEditor // 返回 newEditor ,重要!!!
}
```
第二,把插件 `withAttachment` 注册到 wangEditor ,参考[上文](#注册插件到-wangeditor)。
### 在编辑器中渲染新元素
数据结构定义好了,但编辑器现在还不认识它,执行 `editor.insertNode(myResume)` 也不会有任何效果。接下来就需要让编辑器认识它,能根据 `myResume` 的数据,渲染出我们想要的 UI 界面。
#### 安装 snabbdom.js
```shell
yarn add snabbdom --peer
## 安装到 package.json 的 peerDependencies 中即可
```
编辑器的内部渲染使用了 VDOM 技术,[snabbdom.js](https://github.com/snabbdom/snabbdom) 是一个优秀的 VDOM diff 工具。
我们主要会用到它的 `h` 函数,你可以先在[文档](https://github.com/snabbdom/snabbdom#h)中了解一下。
#### 定义 renderElem 函数
以下是“附件”元素 renderElem 的代码示例,完整代码请参考它的[源码](https://github.com/wangeditor-next/wangEditor-plugin-upload-attachment/blob/main/src/module/render-elem.ts)
```ts
import { h, VNode } from 'snabbdom'
import { IDomEditor, SlateElement } from '@wangeditor-next/editor'
/**
* 渲染“附件”元素到编辑器
* @param elem 附件元素,即上文的 myResume
* @param children 元素子节点,void 元素可忽略
* @param editor 编辑器实例
* @returns vnode 节点(通过 snabbdom.js 的 h 函数生成)
*/
function renderAttachment(elem: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode { // TS 语法
// function renderAttachment(elem, children, editor) { // JS 语法
// 获取“附件”的数据,参考上文 myResume 数据结构
const { fileName = '', link = '' } = elem
// 附件 icon 图标 vnode
const iconVnode = h(
// HTML tag
'img',
// HTML 属性
{
props: { src: 'xxxx.png' } // HTML 属性,驼峰式写法
style: { width: '1em', marginRight: '0.1em', /* 其他... */ } // HTML style ,驼峰式写法
}
// img 没有子节点,所以第三个参数不用写
)
// 附件元素 vnode
const attachVnode = h(
// HTML tag
'span',
// HTML 属性、样式、事件
{
props: { contentEditable: false }, // HTML 属性,驼峰式写法
style: { display: 'inline-block', marginLeft: '3px', /* 其他... */ }, // style ,驼峰式写法
on: { click() { console.log('clicked', link) }, /* 其他... */ }
},
// 子节点
[ iconVnode, fileName ]
)
return attachVnode
}
```
#### 注册 renderElem 到 wangEditor
先定义 renderElem 配置
```js
const renderElemConf = {
type: 'attachment', // 新元素 type ,重要!!!
renderElem: renderAttachment,
}
```
然后把 `renderElemConf` 注册到 wangEditor ,有两种方式。
第一,如果你只想注册一个 renderElem ,没有其他功能,推荐使用 `registerRenderElem`
```js
import { Boot } from '@wangeditor-next/editor'
Boot.registerRenderElem(renderElemConf)
```
第二,如果你除了 renderElem 同时还要注册其他功能,推荐使用 `registerModule`
```ts
import { Boot, IModuleConf } from '@wangeditor-next/editor'
const module: Partial = { // TS 语法
// const module = { // JS 语法
// menus: [menu1Conf, menu2Conf, menu3Conf], // 菜单
// editorPlugin: withBreakAndDelete, // 插件
renderElems: [renderElemConf, /* 其他元素... */] // renderElem
// 其他功能,下文讲解...
}
Boot.registerModule(module)
```
:::tip
- 必须在创建编辑器之前注册
- 全局只能注册一次,不要重复注册
:::
此时,你再执行 `editor.insertNode(myResume)` 就可以看到“附件”元素被渲染到了编辑器中。
### 把新元素转换为 HTML
当你把 `myResume` 插入到编辑器,并渲染成功,此时执行 `editor.getHtml()` 获取的 HTML 里并没有“附件”元素。接下来需要定义如何输入 HTML 。
#### 定义 elemToHtml 函数
以下是代码示例,完整源码可参考[这里](https://github.com/wangeditor-next/wangEditor-plugin-upload-attachment/blob/main/src/module/elem-to-html.ts)
```ts
import { SlateElement } from '@wangeditor-next/editor'
/**
* 生成“附件”元素的 HTML
* @param elem 附件元素,即上文的 myResume
* @param childrenHtml 子节点的 HTML 代码,void 元素可忽略
* @returns “附件”元素的 HTML 字符串
*/
function attachmentToHtml(elem: SlateElement, childrenHtml: string): string { // TS 语法
// function attachmentToHtml(elem, childrenHtml) { // JS 语法
// 获取附件元素的数据
const { link = '', fileName = '' } = elem
// 生成 HTML 代码
const html = `${fileName} `
return html
}
```
注意以下事项:
- 自定义元素生成的 HTML tag 尽量使用 ``(针对 block 元素) 或 `
`(针对 inline 元素)等通用标签。**谨慎使用 `` `` `
` 等编辑器默认支持的标签,那可能会带来冲突**。
- 使用 `data-w-e-type` 记录元素 `type` ,以便解析 HTML 时(下文讲)能识别到
- 使用 `data-w-e-is-void` 标记元素是 void ,以便解析 HTML 时能识别
- 使用 `data-w-e-is-inline` 标记元素是 inline ,以便解析 HTML 时能识别
- HTML 结构尽量扁平、简洁,这样更容易解析 HTML ,更稳定
#### 注册 elemToHtml 到 wangEditor
先定义 elemToHtml 配置
```ts
const elemToHtmlConf = {
type: 'attachment', // 新元素的 type ,重要!!!
elemToHtml: attachmentToHtml,
}
```
然后注册到 wangEditor ,有两种方式
第一,如果你只想注册 elemToHtml ,没有其他需求,则推荐使用 `registerElemToHtml`
```js
import { Boot } from '@wangeditor-next/editor'
Boot.registerElemToHtml(elemToHtmlConf)
```
第二,如果你除了注册 elemToHtml 之外,还需要注册其他功能,则推荐使用 `registerModule`
```ts
import { Boot, IModuleConf } from '@wangeditor-next/editor'
const module: Partial = { // TS 语法
// const module = { // JS 语法
// menus: [menu1Conf, menu2Conf, menu3Conf], // 菜单
// editorPlugin: withBreakAndDelete, // 插件
// renderElems: [renderElemConf], // renderElem
elemsToHtml: [elemToHtmlConf, /* 其他元素... */] // elemToHtml
// 其他功能,下文讲解...
}
Boot.registerModule(module)
```
:::tip
- 必须在创建编辑器之前注册
- 全局只能注册一次,不要重复注册
:::
此时,你再执行 `editor.getHtml()` 即可得到“附件”元素的 HTML 代码,显示 HTML 时可配合 JS 实现点击下载附件的效果。
### 解析新元素 HTML 到编辑器
通过 `const html = editor.getHtml()` 可以得到正确的 HTML ,但再去设置 HTML `editor.setHtml(html)` 却无效。需要你自定义解析 HTML 的逻辑。
#### 定义 parseElemHtml 函数
```ts
import { IDomEditor, SlateDescendant, SlateElement } from '@wangeditor-next/editor'
/**
* 解析 HTML 字符串,生成“附件”元素
* @param domElem HTML 对应的 DOM Element
* @param children 子节点
* @param editor editor 实例
* @returns “附件”元素,如上文的 myResume
*/
function parseAttachmentHtml(domElem: Element, children: SlateDescendant[], editor: IDomEditor): SlateElement { // TS 语法
// function parseAttachmentHtml(domElem, children, editor) { // JS 语法
// 从 DOM element 中获取“附件”的信息
const link = domElem.getAttribute('data-link') || ''
const fileName = domElem.getAttribute('data-fileName') || ''
// 生成“附件”元素(按照此前约定的数据结构)
const myResume = {
type: 'attachment',
link,
fileName,
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!!
}
return myResume
}
```
#### 注册 parseElemHtml 到 wangEditor
先定义 parseHtml 配置
```js
const parseHtmlConf = {
selector: 'span[data-w-e-type="attachment"]', // CSS 选择器,匹配特定的 HTML 标签
parseElemHtml: parseAttachmentHtml,
}
```
然后把 `parseHtmlConf` 注册到 wangEditor ,有两种方式:
第一,如果你只想注册一个 parseElemHtml ,没有别的功能,则推荐 `registerParseElemHtml`
```ts
import { Boot } from '@wangeditor-next/editor'
Boot.registerParseElemHtml(parseHtmlConf)
```
第二,如果你除了想注册 parseElemHtml ,还想注册其他功能,则推荐 `registerModule`
```ts
import { Boot, IModuleConf } from '@wangeditor-next/editor'
const module: Partial = { // TS 语法
// const module = { // JS 语法
// menus: [menu1Conf, menu2Conf, menu3Conf], // 菜单
// editorPlugin: withBreakAndDelete, // 插件
// renderElems: [renderElemConf], // renderElem
// elemsToHtml: [elemToHtmlConf], // elemToHtml
parseElemsHtml: [parseHtmlConf, /* 其他元素... */] // parseElemHtml
}
Boot.registerModule(module)
```
:::tip
- 必须在创建编辑器之前注册
- 全局只能注册一次,不要重复注册
:::
此时,再把获取的 HTML 设置到编辑器中 `editor.setHtml(html)` 即可成功显示“附件”元素。
## 总结
一个模块常用代码文件如下,共选择参考(不一定都用到)
- render-elem.ts
- elem-to-html.ts
- parse-elem-html.ts
- plugin.ts
- menu/
- Menu1.ts
- Menu2.ts
---
## 13. i18n.md
# 多语言
## 切换语言
默认可支持中文和英文,默认为中文。
```js
import { i18nChangeLanguage } from '@wangeditor-next/editor'
// 切换语言 - 'en' 或者 'zh-CN'
i18nChangeLanguage('en')
// 创建编辑器...
```
## 获取语言
获取全部语言配置
```js
import { i18nGetResources } from '@wangeditor-next/editor'
const resources = i18nGetResources('en') // 'en' 或 'zh-CN'
```
获取单个词汇
```js
import { t } from '@wangeditor-next/editor'
console.log( t('header.title') )
```
## 增加新语言
除了中文和英文,使用其他语言,需要先添加语言的词汇,然后再切换语言。
```js
import { i18nAddResources, i18nChangeLanguage, t } from '@wangeditor-next/editor'
// 添加新语言,如日语 ja
i18nAddResources('ja', {
// 标题
header: {
title: 'ヘッダー',
text: 'テキスト',
},
// ... 其他语言词汇,下文说明 ...
})
// 切换为日语 ja
i18nChangeLanguage('ja')
// 获取单个词汇
console.log( t('header.title') )
// 创建编辑器...
```
---
## 14. theme.md
# 主题
可以通过 CSS vars 定义自己的主题,样式请参考[源码](https://github.com/wangeditor-next/wangEditor-next/blob/master/packages/editor/src/assets/index.less)。
```css
/* 暗色主题 */
html.dark {
--w-e-textarea-bg-color: #333;
--w-e-textarea-color: #fff;
/* ...其他... */
}
```
---
## 15. for-ts.md
# 用于 Typescript
将 wangEditor 用于 Typescript 的注意事项。
## 扩展类型
新建一个 `custom-types.d.ts` ,源码如下。注意,保证该文件在 `tsconfig.json` 的 `include` 中。
```ts
import { SlateDescendant, SlateElement, SlateText } from '@wangeditor-next/editor'
declare module '@wangeditor-next/editor' {
// 扩展 Text
interface SlateText {
text: string
}
// 扩展 Element
interface SlateElement {
type: string
children: SlateDescendant[]
}
}
```
---
## 16. plugins.md
# 插件
- [`@` mention 提及](https://github.com/wangeditor-next/wangEditor-next/tree/master/packages/plugin-mention)
- [formula 公式](https://github.com/wangeditor-next/wangEditor-next/tree/master/packages/plugin-formula)
- [markdown](https://github.com/wangeditor-next/wangEditor-next/tree/master/packages/plugin-markdown)
- [链接卡片](https://github.com/wangeditor-next/wangEditor-next/tree/master/packages/plugin-link-card)
- [浮动图片](https://github.com/wangeditor-next/wangEditor-next/tree/master/packages/plugin-float-image)
---
## 17. video-course.md
# 视频教程
## 视频教程
关注 wangEditor [B 站账号](https://space.bilibili.com/697803545)
- [如何选择富文本编辑器](https://www.bilibili.com/video/BV1XB4y1C7EP)
- [wangEditor5教程01-使用入门](https://www.bilibili.com/video/BV1GU4y1q7ob)
- [wangEditor5教程02-用于 Vue2](https://www.bilibili.com/video/BV1b34y1h7oj)
- [wangEditor5教程03-用于 Vue3](https://www.bilibili.com/video/BV1xR4y1A7yJ)
- [wangEditor5教程04-用于 React](https://www.bilibili.com/video/BV1E3411N7XB)
- [wangEditor5教程05-设置和获取内容](https://www.bilibili.com/video/BV1vG4y1i7pH)
- [wangEditor5教程06-展示HTML无样式怎么办](https://www.bilibili.com/video/BV15a411J7UC)
- [wangEditor5教程07-上传图片](https://www.bilibili.com/video/BV1GU4y1S7RQ)
- [wangEditor5教程08-工具栏配置](https://www.bilibili.com/video/BV18L4y1F7qA/)
- [wangEditor5教程09-菜单配置](https://www.bilibili.com/video/BV1LS4y187eC/)
- [wangEditor5教程10-编辑器配置](https://www.bilibili.com/video/BV1jF41177GD/)
- [wangEditor5教程11-API(视频较长,耐心观看)](https://www.bilibili.com/video/BV1fu411z75r/)
- [wangEditor5教程12-自定义扩展 part1(视频较长,耐心观看)](https://www.bilibili.com/video/BV17t4y1L71C)
- [wangEditor5教程13-自定义扩展 part2(视频较长,耐心观看)](https://www.bilibili.com/video/BV16Y4y1A7iM/)
如需要其他视频教程,可去提交 [issue](https://github.com/wangeditor-next/wangEditor-next/issues) 反馈。