简体中文
Tiptap 编辑器
约 5910 字大约 20 分钟
读者: 小程序业务开发。
状态: 当前公共组件接入说明。
阅读顺序: 先看本文的注册、参数、方法和事件;需要自定义节点时再看
customBlocks和扩展生命周期;需要图片、主题或本地联调时再打开对应指南。
简介
avaui-sub/tiptap-editor 是小程序端唯一对外提供的 Tiptap 富文本编辑器组件。 组件只负责把小程序属性、命令和事件转发到共享编辑器运行时,不包含业务工具栏、Slash 面板、业务数据或业务接口。
编辑器 SDK 和可选扩展由独立的共享运行时构建和发布,业务只需要通过本组件的公开属性、方法和事件接入。
文档职责边界
本 README 是小程序公共组件的使用说明,负责组件注册、属性、方法、事件和业务接入示例;它不是 tiptap-core 或 SDK 内部运行时的实现规范。共享运行时的能力边界、内置扩展、命令、内容格式、L2/L3 扩展规则和样式 Token 以 fe-dev-libs/docs/tiptap/ 为准,业务按需阅读:
共享运行时仓库:fe-dev-libs。
core-capabilities.md:当前tiptap-core的实际能力和不负责的事项;contracts.md:双端字段、命令、事件、错误契约和完整 Style Token API;extension-lifecycle.md:简单静态节点、业务私有插件和公共插件的放置规则;guides/image-extension.md:图片节点的默认行为和扩展方式;style-governance.md:字号、颜色、行高、Token 和节点样式归属。
_example 是生成 pages/avaui-sub/tiptap-editor 的唯一示例源。不要直接编辑 pages/ 或站点文档的复制结果。测试联调使用发版说明中的版本化 CDN 地址, SDK 本地构建与诊断由 fe-dev-libs/docs/tiptap/development/local-cdn.md 维护。
使用方法
局部引入,在需要使用的页面或组件的 index.json 中配置:
{
"usingComponents": {
"tiptap-editor": "/avaui-sub/tiptap-editor/index"
}
}<tiptap-editor
id="editor"
src="{{sdkUrl}}"
extensions="{{extensions}}"
pluginUrls="{{pluginUrls}}"
extensionConfigs="{{extensionConfigs}}"
value="{{editorValue}}"
readonly="{{readonly}}"
placeholder="{{placeholder}}"
theme="{{theme}}"
styleVars="{{styleVars}}"
viewportStyle="{{viewportStyle}}"
autoGrow="{{autoGrow}}"
bind:ready="onEditorReady"
bind:update="onEditorUpdate"
bind:focus="onEditorFocus"
bind:blur="onEditorBlur"
bind:selection-change="onEditorSelectionChange"
bind:trigger="onEditorTrigger"
bind:height-change="onEditorHeightChange"
bind:error="onEditorError"
/>组件目录下的 _example 是完整的演示页面源文件。执行 copy-docs 时,_example 会被复制为 pages/avaui-sub/tiptap-editor,因此业务页面不应再维护另一份副本。
更完整的业务 demo 写法和示例结构见 _example/README.md。
完整 Demo
_example 包含 index.js、index.json、index.wxml 和 index.wxss,展示公共编辑器组件、Composer、Slash 面板、配置型 customBlocks、内置图片节点和可选 Markdown 插件的完整联调场景。这是完整业务页面演示,不是业务必须复制的最小结构。
参数
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
src | 必传;Tiptap Hera IIFE 地址,由宿主页面提供 | String | ''(仅属性占位,不可用于运行) |
extensions | SDK 要启用的扩展名称;每项必须为 String | Array | ['starter-kit'] |
extensionConfigs | 扩展配置,必须可序列化 | Object | {} |
pluginUrls | L2 插件 IIFE 地址列表;每项必须是非空绝对 http(s) URL | Array | [] |
pluginPolicy | 可选插件安全策略:requireHttps、allowedOrigins | Object | {} |
pluginIntegrity | 以插件绝对 URL 为 key 的 SRI integrity 值 | Object | {} |
customBlocks | 配置型静态节点;由 SDK 根据参数创建最小 atom Node | Array | [] |
contentType | 输入内容类型:json、html 或可选的 markdown | String | json |
emitMarkdown | 加载 Markdown adapter 后是否在 update 中返回 markdown | Boolean | false |
triggers | 触发器定义,传空时使用 SDK 默认行为 | Array | null |
value | 初始编辑器内容 | JSONContent、JSONContent[]、String 或 null | null |
readonly | 是否只读,内部转换为 editable: !readonly | Boolean | false |
placeholder | 初始占位文案 | String | '' |
theme | 编辑器主题名称 | String | 'default' |
styleVars | CSS 变量覆盖项 | Object | {} |
viewportStyle | 可选的 H5 编辑视口高度与滚动策略;由业务决定 | Object | {}(不设置高度和滚动) |
autoGrow | 可选的内容自增高配置 { minHeight?, maxHeight? } | Object 或 null | null(宿主控高) |
readyTimeoutMs | 等待 Hera ready 的超时毫秒数;超时发 SDK_LOAD_FAILED | Number | 15000 |
src 是业务运行时的必传参数。组件属性中的空字符串仅作为声明默认值,不会提供内置 SDK,也无法创建编辑器。空 src 会立即触发 error:SDK_LOAD_FAILED(fatal: true)。
生产环境建议为插件同时配置 pluginPolicy: { requireHttps: true, allowedOrigins: [...] } 和每个 URL 对应的真实 pluginIntegrity SRI 摘要;本地联调可保留 HTTP LAN URL, 但未配置 policy 时不会自动获得 HTTPS 或完整性保护。组件只转发这些可序列化字段, 最终校验和 script 属性设置由 Hera 完成。
线上 CDN 约束: Ava 移动端正式环境的 Hera 与插件 URL 必须使用
//a9.fspage.com/FSR/...前缀,否则 WebView 会遇到跨域问题。www.ceshi112.com仅用于测试联调,不得随 ava_ui 正式版本发布。实际FSR子路径以发布平台返回的线上 URL 为准,并在切换前校验 HTTP 200、JavaScript Content-Type 和release/manifest.json摘要;不要根据测试路径自行推导。
组件以 Hera 上行 ready 作为 SDK 加载成功信号。在 readyTimeoutMs 内未收到 ready 时,组件只发一次 SDK_LOAD_FAILED(fatal: true),不会自动重试;业务决定降级、提示或重新挂载组件。Hera 已发出的 INIT_FAILED / PLUGIN_LOAD_FAILED 仍原样透传。
src 从哪里来
业务不要自己构建 tiptap-hera,也不要到 fe-dev-libs 仓库里找 dist。 src 是 SDK 维护方发版后上传到 CDN 的完整 URL,业务从接入文档或发版说明复制即可。
| 场景 | 业务去哪里拿 | 地址形态 |
|---|---|---|
| 正式环境 | 本文档「推荐 CDN」小节、发版公告 | https://{CDN}/tiptap-hera/{semver}/tiptap-hera.iife.js |
| 测试联调 | _example、当次发版说明 | https://www.ceshi112.com/fsh5/fe-dev-libs/tiptap-hera/{semver}/tiptap-hera.iife.js |
| 升级版本 | 新版本发版说明 | 只替换路径中的 {semver},例如 0.1.0 → 0.1.1 |
约定路径:
/tiptap-hera/{semver}/tiptap-hera.iife.js
/tiptap-markdown/{semver}/tiptap-markdown.iife.js- 带版本号的目录不可变:同一
{semver}发布后不再覆盖。 - 组件不内置默认 CDN,也不走云控;由宿主页面显式传入
src。 - 业务页面只维护自己使用的 URL;构建、上传 CDN、写发版说明属于 SDK 维护方。
推荐 CDN
测试环境使用版本化
www.ceshi112.com地址;Ava 移动端正式环境必须使用发版平台返回的//a9.fspage.com/FSR/...地址。
主 SDK:
https://www.ceshi112.com/fsh5/fe-dev-libs/tiptap-hera/0.1.0/tiptap-hera.iife.js
可选 Markdown 插件:
https://www.ceshi112.com/fsh5/fe-dev-libs/tiptap-markdown/0.1.0/tiptap-markdown.iife.js业务应直接复制当次发版说明中的完整地址,并在切换前校验 HTTP 200、JavaScript Content-Type 和发布摘要;不要从测试地址推导正式环境路径。
业务最小接入示例
Page({
data: {
// 从本节「推荐 CDN」或发版说明复制,不要手写无版本号的临时路径
sdkUrl: 'https://www.ceshi112.com/fsh5/fe-dev-libs/tiptap-hera/0.1.0/tiptap-hera.iife.js',
// extensions 可省略,默认 ['starter-kit']
},
})<tiptap-editor src="{{sdkUrl}}" bind:ready="onReady" bind:update="onUpdate" bind:error="onError" />需要 Markdown 时,再增加独立插件 URL(不要塞进主 src):
Page({
data: {
sdkUrl: 'https://www.ceshi112.com/fsh5/fe-dev-libs/tiptap-hera/0.1.0/tiptap-hera.iife.js',
extensions: ['starter-kit', 'markdown'],
pluginUrls: [
'https://www.ceshi112.com/fsh5/fe-dev-libs/tiptap-markdown/0.1.0/tiptap-markdown.iife.js',
],
contentType: 'markdown', // 仅当初始 value 是 Markdown 字符串时
emitMarkdown: true, // 仅当需要 update/getContent 输出 markdown 时
},
})src、extensions、pluginUrls、extensionConfigs、value 等初始化参数在组件 attached 时读取并传给 SDK。 当前组件不负责运行期间重新创建编辑器,因此业务页面应在渲染组件前准备好这些值;内容更新请使用 setContent。 这些参数会跨 WebView 传给 Hera,因此必须可序列化。已声明字段类型错误会触发 INIT_FAILED;复杂扩展不能直接放进 extensions,应通过 pluginUrls 加载后 再用字符串名称启用。
内置扩展与默认能力
当业务不传 extensions,或者传入空数组时,编辑器默认启用:
extensions: ['starter-kit']当前 starter-kit 基于 @tiptap/starter-kit@3.30.0,默认包含以下能力:
| 分类 | 默认能力 |
|---|---|
| 文档结构 | doc、paragraph、text |
| 标题 | heading,支持 h1 到 h6 |
| 块级节点 | blockquote、codeBlock、horizontalRule |
| 列表 | bulletList、orderedList、listItem |
| 文本格式 | bold、italic、strike、code、underline |
| 链接 | link |
| 编辑行为 | hardBreak、dropcursor、gapcursor、trailingNode |
| 历史和快捷键 | undoRedo、listKeymap |
因此默认配置已经支持普通文本、标题、粗体、斜体、删除线、行内代码、下划线、 列表、引用、代码块、分割线、链接、撤销和重做。
默认代码块背景使用公共 Token --tt-code-bg,默认值为 #f2f4fb。公共组件只 提供代码块的基础可编辑容器;语言选择、复制、行号、折叠、语法高亮和顶部工具条 由业务页面或 L2 插件负责。
当前 core 额外提供以下扩展名称:
| 名称 | 默认是否启用 | 说明 |
|---|---|---|
link | 否(已包含在 starter-kit) | 独立组合时使用;独立入口默认 openOnClick: false、autolink: true、markdownLinks: true |
underline | 否(已包含在 starter-kit) | 独立组合时使用;默认配置由官方扩展提供 |
image | 否 | 图片节点,默认块级、不允许 Base64、不启用缩放 |
placeholder | 否 | 空文档占位提示;传入顶层 placeholder 时自动追加 |
默认 starter-kit 已经包含 link 和 underline,业务不需要重复传入:
// 不要这样重复声明
extensions: ['starter-kit', 'link', 'underline']需要调整默认 StarterKit 内部的链接行为时,使用嵌套配置:
extensions: ['starter-kit'],
extensionConfigs: {
'starter-kit': {
link: {
openOnClick: false,
autolink: true,
markdownLinks: true,
},
},
}图片不属于默认 starter-kit,需要显式启用:
extensions: ['starter-kit', 'image'],
extensionConfigs: {
image: {
inline: false,
allowBase64: false,
},
}图片默认是块级节点。core 只提供图片节点、属性和序列化,不负责选图、上传、 压缩、鉴权、预览、重试和业务错误提示。table、mention、文件预览和复杂 NodeView 也不是当前内置能力,需要通过 pluginUrls 接入 L2 插件。
更完整的内置节点清单、默认值和配置边界见 fe-dev-libs/docs/tiptap/core-capabilities.md 和 fe-dev-libs/docs/tiptap/contracts.md。
自定义块 class
组件已启用 addGlobalClass: true。简单静态节点可以在 customBlocks.schema 中声明 class、icon 等实例属性,并在模板安全的 class 属性中插值;不需要修改 SDK 或新建插件包:
customBlocks: [{
type: 'businessIconInline',
inline: true,
template: '<span class="{{class}}"><span class="{{icon}}"></span><span>{{label}}</span></span>',
schema: { class: 'string', icon: 'string', label: 'string' },
className: 'business-icon-inline',
}]
editor.execCommand('insertContent', {
type: 'businessIconInline',
attrs: {
class: 'business-icon-inline--sortable',
icon: 'fxui_all tuodongpaixu',
label: 'Sortable inline',
},
})className 是定义级固定根 class,模板中的 {{class}} 是实例级 class;两者会合并并去重。Core 只负责 class 的安全传递和序列化,不内置具体 FXUI 图标样式。块级写法只需将 inline 设为 false,并使用单根 <div> 模板。完整的块级和行级示例见 _example 中的 Class block、Class inline 按钮。
图片节点
普通图片使用内置 image 扩展,不需要创建业务插件。图片默认是块级节点;业务 确实需要图片和文字位于同一段落时,可以将 inline 改为 true。同一份文档的 Web 和小程序必须使用一致的 inline 配置。
Page({
data: {
extensions: ['starter-kit', 'image'],
extensionConfigs: {
image: {
inline: false,
allowBase64: false,
},
},
},
})选图、压缩、鉴权、上传、重试和业务错误提示都由业务负责。获得稳定 URL 后, 通过公共命令插入图片:
this.editor.execCommand('insertContent', {
type: 'image',
attrs: {
src: uploadedFile.url,
alt: uploadedFile.name || '',
title: uploadedFile.name || null,
width: uploadedFile.width || null,
height: uploadedFile.height || null,
},
})只调整行内/块级、Base64、默认 HTML 属性或官方缩放配置时,使用 extensionConfigs.image。需要新增业务属性、NodeView、预览、裁剪或重试交互时, 再通过 pluginUrls 接入 L2 业务插件,不在公共组件中实现。完整的图片 schema、 跨端扩展和兼容规则由 fe-dev-libs/docs/tiptap/guides/image-extension.md 统一维护。
高度与滚动
组件默认采用宿主控高模式:不传 autoGrow 时,不测量内容高度,也不改变业务 声明的组件高度。小程序需要“初始高度随业务设置,之后随内容增长”时,显式传入:
Page({
data: {
editorAutoGrow: {
minHeight: 140,
maxHeight: 480,
},
},
})<tiptap-editor
id="editor"
style="display:block;width:100%;"
src="{{sdkUrl}}"
autoGrow="{{editorAutoGrow}}"
bind:height-change="onEditorHeightChange"
/>| 配置 | 行为 |
|---|---|
不传 autoGrow | 宿主业务控制高度;现有固定高度模式不变 |
autoGrow: {} | 从内容自然高度开始,无上限增长 |
{ minHeight: 140 } | 不低于 140px,随内容无限增长和缩小 |
{ minHeight: 140, maxHeight: 480 } | 在 140px 至 480px 内增长;超过后 Lego H5 内部滚动 |
minHeight / maxHeight 使用 H5 CSS px 数值,不传单位字符串。组件会自动应用 Hera 上报的高度,业务不需要监听 height-change 后再 setData;该事件只用于 页面联动、埋点或调试。
达到 maxHeight 后,Hera 会同时限制编辑器根节点高度并启用纵向滚动,内容不会 越过组件边界覆盖后续业务 UI。删除内容并回到上限以下后,组件继续随内容缩小。
固定高度并在编辑器内部滚动时,不传 autoGrow,由业务同时设置组件高度并显式 传入 viewportStyle:
<view class="editor-wrap">
<tiptap-editor
id="editor"
style="display:block;width:100%;height:140px;"
src="{{sdkUrl}}"
viewportStyle="{{editorViewportStyle}}"
/>
</view>.editor-wrap {
width: 100%;
height: 140px;
overflow: hidden;
}Page({
data: {
editorViewportStyle: {
height: '100%',
minHeight: '0',
overflowX: 'hidden',
overflowY: 'auto',
webkitOverflowScrolling: 'touch',
},
},
})autoGrow 与非空 viewportStyle 不能同时使用;两套高度策略并存会触发致命 INIT_FAILED,不会静默选择优先级。启用 autoGrow 时也不要在组件 style 或 业务 wrapper 上声明固定 height。Web 端在同一 DOM 中不需要该桥接字段:不设 固定高度即可自然撑开,固定视口仍由业务 CSS 决定。
内容格式语义
contentType 只表示输入内容的解析格式,不控制输出字段;emitMarkdown 只表示是否在 update 和 getContent() 中生成 markdown 字段。 两者互不自动开启:传入 contentType: 'markdown' 不会自动等同于 emitMarkdown: true。
Page({
data: {
sdkUrl: 'https://cdn.example.com/tiptap-hera/0.1.0/tiptap-hera.iife.js',
extensions: ['starter-kit', 'markdown'],
pluginUrls: [
'https://cdn.example.com/tiptap-markdown/0.1.0/tiptap-markdown.iife.js',
],
editorValue: '# Markdown content',
contentType: 'markdown',
emitMarkdown: true,
},
})<tiptap-editor
id="editor"
src="{{sdkUrl}}"
extensions="{{extensions}}"
pluginUrls="{{pluginUrls}}"
value="{{editorValue}}"
contentType="{{contentType}}"
emitMarkdown="{{emitMarkdown}}"
/>初始化时的 contentType 只作用于 value/content 的初始化解析。运行时命令中显式传入的 contentType 只作用于本次调用,并优先于初始化配置:
editor.execCommand('setContent', {
content: '<p>HTML content</p>',
contentType: 'html',
})如果运行时命令未传 contentType,SDK 不会自动继承初始化的 contentType。 结构化对象可省略 contentType;字符串输入必须显式声明,SDK 不猜测格式。
业务主题应通过 styleVars 覆盖已声明的 --tt-* Token,不应在业务 WXSS 中直接重写 .ProseMirror 的内部结构样式:
Style Token API
下表是业务可直接使用的公共 Token。默认值来自共享 tiptap-core,Web 和小程序使用同一套名称; 业务只需要传入要修改的项目,不需要重复传默认值。
| 分类 | Token | 默认值 | 作用 |
|---|---|---|---|
| 字体 | --tt-font-size-body | 13px | 正文字号 |
| 字体 | --tt-font-size-h1 | 22px | h1 字号 |
| 字体 | --tt-font-size-h2 | 20px | h2 字号 |
| 字体 | --tt-font-size-h3 | 18px | h3 字号 |
| 字体 | --tt-font-size-h4 | 16px | h4 字号 |
| 字体 | --tt-font-size-h5 | 15px | h5 字号 |
| 字体 | --tt-font-size-h6 | 14px | h6 字号 |
| 颜色 | --tt-color-text | #181c25 | 正文颜色 |
| 颜色 | --tt-color-text-secondary | #545861 | 引用和占位符颜色 |
| 颜色 | --tt-color-link | #0c6cff | 链接颜色 |
| 颜色 | --tt-code-bg | #f2f4fb | 行内代码和代码块背景 |
| 颜色 | --tt-blockquote-border | #d0d3d9 | 引用块左边框颜色 |
| 行高 | --tt-line-height | 1.6 | 正文和标题默认行高 |
| 行高 | --tt-line-height-h1 | var(--tt-line-height) | h1 行高 |
| 行高 | --tt-line-height-h2 | var(--tt-line-height) | h2 行高 |
| 行高 | --tt-line-height-h3 | var(--tt-line-height) | h3 行高 |
| 行高 | --tt-line-height-h4 | var(--tt-line-height) | h4 行高 |
| 行高 | --tt-line-height-h5 | var(--tt-line-height) | h5 行高 |
| 行高 | --tt-line-height-h6 | var(--tt-line-height) | h6 行高 |
| 间距 | --tt-editor-padding | 8px 0 | 编辑器内容区域内边距 |
| 间距 | --tt-paragraph-spacing | 8px | 段落底部间距 |
| 间距 | --tt-heading-spacing | 0.6em | 标题顶部间距 |
| 间距 | --tt-code-block-margin | 8px 0 | 代码块外部间距 |
| 间距 | --tt-code-block-padding | 12px | 代码块内部内边距 |
插件私有 Token(例如 --tt-chip-*)不属于公共 Token,应以对应插件的文档和配置说明为准; 未列入上表的 styleVars 名称也不构成跨 Web/小程序的公共契约。
编辑器外部 margin、业务卡片 padding、边框、宽高、overflow 和滚动容器由业务 wrapper 或 viewportStyle 负责,不通过 WXSS 重写 .ProseMirror 内部结构。完整表格和维护规则见 fe-dev-libs/docs/tiptap/contracts.md 的“公共 Style Token API”。
使用示例
editorStyleVars: {
'--tt-font-size-body': '14px',
'--tt-font-size-h4': '16px',
'--tt-font-size-h5': '15px',
'--tt-font-size-h6': '14px',
'--tt-line-height': '1.5',
'--tt-line-height-h1': '1.2',
'--tt-line-height-h2': '1.3',
'--tt-line-height-h3': '1.4',
'--tt-paragraph-spacing': '4px',
'--tt-code-block-margin': '8px 0',
'--tt-code-block-padding': '12px',
}从 h1 到 h6 的标题级行高未传时都会回退到 --tt-line-height,默认均为 1.6。 业务仅在需要区分标题级别时覆盖对应的 --tt-line-height-h1 至 --tt-line-height-h6 Token。 平台默认的 h4、h5、h6 字号分别为 16px、15px、14px;业务可通过对应的 --tt-font-size-h4/h5/h6 Token 覆盖。
--tt-editor-padding、--tt-paragraph-spacing、--tt-heading-spacing、 --tt-code-block-margin 和 --tt-code-block-padding 只控制编辑器内容区域及标准 节点的基础间距。编辑器外部 margin、业务卡片 padding、宽高和滚动容器由业务页面 或外层 wrapper 控制,不应通过 WXSS 重写 .ProseMirror 内部结构。
内容读取 API
组件不会分别提供 getText()、getJSON()、getHTML() 或 getMarkdown()。 所有内容格式统一通过 getContent() 返回,业务按需读取对应字段:
const content = this.editor.getContent()
const plainText = content && content.text
const json = content && content.json
const html = content && content.html
const markdown = content && content.markdown返回结构为:
{
text: string,
json: JSONContent,
html: string,
markdown?: string,
}text、json 和 html 默认存在。markdown 只有在 Markdown adapter 加载成功且 emitMarkdown 为 true 时才会返回。update 事件和 getContent() 使用相同的结构。
小程序端的 getContent() 返回最近一次 update 事件缓存的内容。首次 update 到达前可能返回 null,业务应在 ready 后等待 update,或直接在 update 事件中读取内容。
onEditorUpdate(event) {
const { text, json, html, markdown } = event.detail
// 业务按持久化、搜索或接口要求选择对应字段。
}方法
通过 selectComponent 获取组件实例。推荐在 ready 事件中保存实例:
onEditorReady() {
this.editor = this.selectComponent('#editor')
}
onToggleBold() {
this.editor.execCommand('toggleBold')
}| 方法 | 说明 |
|---|---|
execCommand(action, data) | 向 SDK 发送命令 |
setContent(content, contentType?) | 替换内容;结构化对象可省略 contentType,字符串必须显式声明 |
getContent() | 返回最近一次 update 事件收到的内容 |
focus() | 聚焦编辑器 |
blur() | 取消聚焦 |
结构化对象可省略 contentType;字符串输入必须显式声明:
this.editor.setContent({ type: 'doc', content: [{ type: 'paragraph' }] })
this.editor.setContent(JSON.stringify(doc), 'json')
this.editor.setContent('<p>HTML content</p>', 'html')
this.editor.setContent('# Markdown content', 'markdown')setContent 的第二个参数只作用于本次调用,不会改变初始化的 contentType。需要传递完整命令数据时,也可以使用:
this.editor.execCommand('setContent', {
content: '# Markdown content',
contentType: 'markdown',
})也可以直接传入原始 Tiptap JSON 文档或节点。带字符串 type 的对象始终按 原始节点处理,不会把文档自身的 content 数组误当成命令包裹字段:
this.editor.execCommand('setContent', {
type: 'doc',
attrs: { source: 'business' },
content: [{ type: 'paragraph' }],
})需要同时指定输入格式时,使用 { content, contentType } 包裹形式。该规则同样 适用于 insertContent。
命令
execCommand(action, data) 当前支持以下公共命令:
| action | data | 说明 |
|---|---|---|
setContent | { content, contentType? } 或内容本身 | 设置文档内容 |
focus / blur | 无 | 聚焦或取消聚焦 |
undo / redo | 无 | 撤销或重做 |
toggleBold / bold | 无 | 切换粗体 |
toggleItalic / italic | 无 | 切换斜体 |
toggleStrike / strike | 无 | 切换删除线 |
toggleUnderline / underline | 无 | 切换下划线 |
toggleHeading | { level?: 1..6 } | 切换标题,未传时使用一级标题 |
toggleBulletList / bulletList | 无 | 切换无序列表 |
toggleOrderedList / orderedList | 无 | 切换有序列表 |
toggleBlockquote | 无 | 切换引用块 |
toggleCode | 无 | 切换行内代码 |
insertContent | { content, contentType? } 或内容本身 | 插入 JSON、HTML 或 Markdown 内容 |
insertCustomBlock | { type, data } | 插入自定义块 |
setIsReadonly | { isReadonly: boolean } | 动态切换只读状态 |
applyTrigger | ApplyTriggerPayload | 应用 trigger 选区并回填内容 |
applySlash | ApplyTriggerPayload | applyTrigger 的命令层快捷别名 |
新业务应优先使用表中的主命令名称。完整字段定义、触发器语义和错误码见 共享运行时的 公共契约。
启用 L2 插件后,业务也可以通过同一个公共组件入口调用插件命令:
this.selectComponent('#editor').execCommand('toggleBadgeSetActive', {
id: 'account-badge',
active: true,
})Core 稳定 action 会先进入 Core 分支,不会落入插件 fallback;未匹配时,运行时 只在当前编辑器实例已启用的插件命令中查找同名 action。插件负责自己的命令名、 payload 类型和运行时校验,且不得声明与 Core 稳定命令同名的命令;组件不会维护 第二套业务命令表。插件命令不存在或抛错时会收到非致命 COMMAND_FAILED;返回 false 表示已处理但未产生业务变更,不发出错误。
事件
| 事件 | 参数 | 说明 |
|---|---|---|
ready | SDK/core 版本和协议元信息 | 编辑器运行时已就绪 |
update | { text, json, html },启用 Markdown 时可包含 markdown | 编辑器内容发生变化 |
focus | { source: 'editor' } | 用户聚焦编辑器 |
blur | { source: 'editor' } | 用户离开编辑器 |
activeMarks | 当前激活的 mark 状态 | 用于页面工具栏状态同步 |
selection-change | { hasSelection, marks, rect } | 选区变化;rect 是相对编辑器根节点的 CSS px 矩形,可用于业务浮动菜单定位 |
trigger | 触发器名称、阶段、查询词、范围和会话 ID | 无界面触发协议,业务页面负责展示和处理 |
height-change | { height, contentHeight, constrained } | 启用 autoGrow 后的高度变化;组件已自动应用 height |
error | { code, message, action?, pluginUrl?, sessionId?, fatal? } | 宿主 SDK 加载失败、运行时初始化失败或插件加载错误 |
trigger 是唯一的输入触发事件。trigger.name 可以是 slash、mention 或 业务自定义名称;业务不需要为不同触发器绑定不同的小程序事件。
业务自有浮动菜单
组件不内置浮动菜单。业务页面可以监听 selection-change,在自己的 WXML 中显示 菜单,并通过组件实例的 execCommand 执行操作:
<view class="editor-wrap">
<tiptap-editor
id="editor"
src="{{sdkUrl}}"
bind:selection-change="onEditorSelectionChange"
/>
<view
wx:if="{{bubbleMenu.visible}}"
class="business-bubble-menu"
style="left: {{bubbleMenu.left}}px; top: {{bubbleMenu.top}}px;"
>
<view data-action="toggleBold" bindtap="onBubbleAction">B</view>
</view>
</view>e.detail.rect 的坐标原点是编辑器根节点左上角,菜单的定位和样式完全由业务决定。 没有选区时 e.detail.hasSelection 为 false 且 rect 为 null。按钮点击时调用 selectComponent('#editor').execCommand(action);组件不会替业务保存菜单状态。
自定义扩展
自定义 Tiptap Node/Extension 属于 L2 扩展,包含节点 schema、NodeView、交互或自定义序列化时,必须作为独立插件维护,不得写入本组件目录,也不得硬编码进 SDK。
当前演示页面中的 businessChip、businessBadge 和图标节点属于配置型 customBlocks,不需要额外插件。只有节点包含独立 schema、NodeView、交互或 自定义序列化时,业务才应发布 L2 插件,并通过版本化 pluginUrls 加载;页面 负责业务数据和插入交互,本组件只转发公开配置与命令。
架构边界
- 业务页面只能使用
avaui-sub/tiptap-editor,不得直接依赖fs-lego-h5或 SDK 内部协议。 - 本组件不放置业务面板、业务接口、chip 数据、NodeView 或其他业务状态。
- 不在其他
avaui-sub目录复制第二套 Tiptap wrapper。 - 简单静态业务节点使用
customBlocks;具有 schema、NodeView、交互或自定义序列化的复杂节点使用 L2 插件;只有跨业务稳定、协议明确并完成评审后,才考虑上升为共享能力。 - Tiptap 的 MIT 许可说明统一维护在
ava_ui/THIRD_PARTY_NOTICES.md。
