有道编辑器SDK是有道开放平台提供的云服务之一,是有道编辑器接口的一种实现。拥有多年产品化实践经验,服务于有道云笔记千万用户。
通过SDK接入优势:
开始集成SDK之前开发者需要登录有道开放平台(http://ai.youdao.com),创建应用获取应用ID(或者通过运营人员获取应用ID),以便使用编辑器服务。
编辑器web sdk由如下几个文件组成:
文件 | 说明 |
---|---|
bulb.min.css | 编辑器样式文件(必选) |
bulb.min.js | 编辑器核心库(必选) |
方法一<script>
引入
<link rel="stylesheet" href="/path/to/bulb.min.css">
<script type="text/javascript" src="/path/to/bulb.min.js"></script>
方法二
Module
import '/path/to/bulb.min.css'
import BulbEditor from '/path/to/bulb.min.js'
import '/path/to/bulb.min.css'
import BulbEditor from '/path/to/bulb.min.js'
(authBulbEditor({
url: 'http://updateinfo.youdao.com/editorapi',
pkn: 'com.youdao.com',
appKey: appKey,
version: 'v1',
sdkVersion: 'v1',
appSecret: appSecret,
})).then((BulbEditor) => {
editor = new BulbEditor({
el: document.getElementById('editor-container'),
});
})
BulbEditor
可以通过在编辑器实例化时定制编辑器的特性
el
{HTML element} [必须] 设置编辑器的挂载对象disableContextMenu
{Boolean} [默认值:false
] 是否禁止编辑器默认右键菜单操作disableAttachDownload
{Boolean} [默认值:false
] 是否禁止编辑器默认附件下载操作disableImageDownload
{Boolean} [默认值:false
] 是否禁止编辑器默认图片下载操作disableOpenLink
{Boolean} [默认值:false
] 是否禁止编辑器默认打开链接操作disableInsertLink
{Boolean} [默认值:false
] 是否禁止编辑器默认插入超链接窗口disableClipError
{Boolean} [默认值:false
] 是否禁止编辑器默认剪切板错误提醒toolbarParentEl
{HTML element} [默认值:undefined
] 设置编辑器工具栏的挂载对象toolbarItems
{String[]} [默认值:undefined
] 编辑器工具条配置,见以下节详细说明1. getContent(options)
用于获取编辑器内容
参数
options <Object>
json
| xml
| plain
| html
表示要获取哪种格式的内容,默认 xml
false
返回值
当async
为true
时返回 <Promise> 其他情况返回 <String>
实例
editor
.getContent({
type: 'json',
async: true,
})
.then(json => {
console.log(json)
})
2. setContent(content, options)
用于设置编辑器内容,也会将编辑状态重置
参数
options <Object>
json
| xml
表示要设置哪种格式的内容,默认 xml
false
返回值
当async
为true
时返回 <Promise> 其他情况无返回值
实例
const xml = editor.getContent()
editor
.setContent(xml, {
async: true,
})
.then(() => {
console.log('done')
})
3. insertAttachment(options)
用于插入附件操作
参数
options <Object>
实例
editor.insertAttachment({
resource: 'http://path.ext',
src: 'http://thumbnail.jpg',
fileName: '文件名',
fileLength: 1024,
})
4. insertImage(options)
用于插入图片操作
参数
options <Object>
实例
editor.insertImage({
src: 'http://image.jpg',
})
5. insertMacro(options)
用于插入宏操作
参数
options <Object>
实例
editor.insertMacro({
name: 'test_macro',
content: 'this is content of test macro',
version: '1.0.0'
})
5. registerMacroConfig(macroConfig)
用于将宏配置实例注册到编辑器
参数
实例
editor.registerMacroConfig(new TestMacroConfig());
1. before-download-attachment
当用户点击附件下载按钮或其他请求下载附件之前触发
提供参数
result <Object>
实例
editor.on('before-download-attachment', (result) => {
console.log(result.url, result.name)
})
2. before-download-image
当用户点击下载图片按钮或其他请求下载图片之前触发
提供参数
result <Object>
实例
editor.on('before-download-image', (result) => {
console.log(result.url)
})
3. before-insert-link
当用户点击插入链接按钮或其他修改链接按钮之前触发
提供参数
实例
editor.on('before-insert-link', (originUrl, next) => {
console.log(originUrl)
var url = 'note.youdao.com';
next(url);
})
4. before-open-link
当用户点击链接或其他请求打开链接之前触发
提供参数
result <Object>
实例
editor.on('before-open-link', (result) => {
console.log(result.href)
})
5. clipboard-error
通过右键菜单或其他情况非快捷键触发粘贴、复制、剪切操作失败时触发,目前由于浏览器安全策略粘贴操作一定会失败
提供参数
cut
| copy
| paste
出错的操作类型实例
editor.on('clipboard-error', (type, next) => {
alert('复制粘贴错误类型:' + type)
next()
})
6. content-change
当文件内容发生变化时触发
提供参数
无
实例
editor.on('content-change', () => {
console.log('Content change!')
})
7. context-menu
当需要显示右键菜单时触发
提供参数
options <Object>
items <Array> 菜单列表, 内含多个菜单信息的<Object>
<Object>
next(items[0].index)
实例
editor.on('before-context-menu', (options, next) => {
// do something...
next(item[0].index)
})
8. require-attachment
请求插入附件时触发
提供参数
next <Function> 作为回调函数接收<Object>类型附件信息
<Object>
实例
editor.on('require-attachment', (next) => {
// do something...
next({
resource: 'http://path.ext',
fileName: '文件名',
fileLength: 1024,
src: 'http://thumbnail.jpg',
})
})
9. require-image
请求插入图片操作时触发
提供参数
<Function> 作为回调函数接收<Object>类型图片信息
<Object>
实例
editor.on('require-image', (next) => {
// do something...
next({
src: 'http://image.jpg',
})
})
10. preview-image
双击图片触发图片预览
提供参数
options <Object>
items <Object> 所有图片的信息
实例
editor.on('preview-image', (options) => {
// do something...
console.log('preview-image', options)
})
11. insert-macro-clicked
点击工具栏上的插入宏按钮
提供参数
macroOptions <Object> 需要放到插入宏弹窗中显示的宏配置的数组
<Function> 作为回调函数接收<String>类型MacroName信息
实例
editor.on('yne-insert-macro-clicked', (macroOptions, next) => {
this.trigger('insert-macro-clicked', macroOptions, (macroName) => {
next(macroName);
});
});
符号 ^ 表示将一个item置顶
toolbarItems:['^bold', '^color'] // 显示默认所有图标,置顶bold和color
符号 ! 表示将一个item从默认列表移除
toolbarItems:['!bold', '!color'] // 显示默认所有图标,移除bold和color
符号 | 表示插入一个分割线
toolbarItems:['^bold', '|', '|', '^color'] // 显示默认所有图标,置顶bold和color,并在bold和color间插入两个分割线
不使用默认的所有图标
toolbarItems: ['undo', 'redo', 'bold', 'color'] // 只显示自定的4个图标
同时使用置顶和移除
toolbarItems:['^bold', '^underline', '!color'] // 显示默认所有图标,移除color,置顶bold和underline
其余特例及显示结果
toolbarItems:['color', 'undo', '^redo'] // **不推荐的写法**,效果等同写['redo', 'color', 'undo']
toolbarItems:['color', 'undo', '!redo'] // **不推荐的写法**,效果等同写[color', 'undo']
toolbarItems:['!undo', '|', '!redo'] // **不推荐的写法**,显示默认所有图标,移除了undo和redo
toolbarItems:['|', '|', '|'] // **不推荐的写法**
附录A: 工具栏项
| | 分割线(仅UI)
undo | 撤销
redo | 重做
remove-format | 清除格式
format-painter | 格式刷
heading | 标题
font-family | 字体
font-size | 字号
bold | 加粗
italic | 斜体
underline | 下划线
strike | 删除线
color | 文字颜色
back-color | 背景色
unordered-list | 无序列表
ordered-list | 项目编号
todo | 待办
indent | 增加缩进
outdent | 减少缩进
line-height | 行高
table-justify | 对齐(仅表格)
table-merge | 合并单元格(仅表格)
table-split | 分割单元格(仅表格)
table-operate | 操作表格(仅表格)
table-wrap | 自动换行(仅表格)
table | 插入表格
hr | 插入水平线
link | 插入链接
img | 插入图片
attachment | 插入附件
catalogue | 插入目录
宏段落
宏段落为存储格式,生成的XML格式如下所示
<macro>
<name>mymacro</name>
<content>this is a macro</content>
<version>1.0.0</version>
</macro>
宏段落数据定义name
: 宏名称字段,具有唯一性
content
: 宏内容,可以是任意格式的数据,根据宏配置自行做消费
version
: 宏版本,用于同名称宏的新旧版本的兼容与转换
宏配置
宏配置为宏的配置文件,包含宏的显示与修改逻辑。宏段落与宏配置通过唯一的名称字段做绑定。宏配置的接口如下所示:
interface IMacroToolbarOptions {
label: string;
icon: string;
}
interface IMacroConfig {
macroName: string;
version: string;
toolbarOptions?: IMacroToolbarOptions;
customOptions?: any;
getReadView(macroContent: string, macroVersion?: string): Promise<any>;
getEditView(macroContent: string, macroVersion?: string): Promise<any>;
getEditDialog(macroContent: string, macroVersion?: string): Promise<string>;
getInsertDialog(): Promise<string>;
}
宏配置接口定义macroName
: 宏配置名称,与宏段落的名称一致
version
: 宏配置的版本信息,当出现宏段落的版本与宏配置的版本不一致时,需要做兼容性处理。
toolbarOptions
: 渲染编辑器内的工具栏时,所需要的配置,遵循IMacroToolbarOptions
格式:
base64
的图片字符串,支持图片格式及svg。customOptions
: 用户自定义配置,编辑器不做处理,会在insert-macro-clicked
等事件中,返回给宏开发者
getReadView
: 获取阅读模式下的视图渲染,返回值为Promise
对象。相关参数为:
getEditView
: 获取编辑模式下的视图渲染,返回值为Promise
对象。相关参数为:
getInertDialog
: 获取插入时的弹框视图,返回值为Promise
对象。相关参数为:
宏配置例子
import $ = require('jquery');
import csjs = require('csjs');
import { IMacroConfig } from 'common/core/MacroConfigInterface';
import AttachmentParse = require('common/util/attachmentParse');
class AttachmentMacro implements IMacroConfig {
macroName = 'attachment_macro';
version = '1.0.0';
toolbarOptions = {
label: '宏附件',
// tslint:disable-next-line
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHdpZHRoPSIxOHB4IiBoZWlnaHQ9IjE4cHgiIHZpZXdCb3g9IjAgMCAxOCAxOCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpza2V0Y2g9Imh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaC9ucyI+DQogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjUuMSAoMjUyMzQpIC0gaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoIC0tPg0KICAgIDx0aXRsZT7pmYTku7Y8L3RpdGxlPg0KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPg0KICAgIDxkZWZzPjwvZGVmcz4NCiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4NCiAgICAgICAgPGcgaWQ9InN2ZyIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTY0LjAwMDAwMCwgLTc4LjAwMDAwMCkiPg0KICAgICAgICAgICAgPHBhdGggZD0iTTcyLjEwOTM3MzEsOTUuODM0MDgzMSBMNzIuMTAwNzEwNSw5NS44MzQwODMxIEM3MC40MDQ3NTY3LDk1LjgzMDIzMyA2OS4wMjQ1MDgzLDk0LjQ0NjEzNDUgNjkuMDI0NTA4Myw5Mi43NDkyMTgyIEw2OS4wMjQ1MDgzLDg0LjU5MDkyOTcgQzY5LjAyNDUwODMsODIuMjM4NTM5OCA3MC44NzYzODk3LDgwLjMyNDA5NDggNzMuMTUyNzQwOSw4MC4zMjQwOTQ4IEM3NS40MDMxMDQxLDgwLjMyNDA5NDggNzcuMjMzODEwMSw4Mi4yMzg1Mzk4IDc3LjIzMzgxMDEsODQuNTkwOTI5NyBMNzcuMjMzODEwMSw5MC41MzI1NDMxIEM3Ny4yMzM4MTAxLDkwLjc5OTE2MDEgNzcuMDE4MjA2NSw5MS4wMTM4MDEzIDc2Ljc1MjU1Miw5MS4wMTM4MDEzIEM3Ni40ODU5MzQ5LDkxLjAxMzgwMTMgNzYuMjcxMjkzOCw5MC43OTkxNjAxIDc2LjI3MTI5MzgsOTAuNTMyNTQzMSBMNzYuMjcxMjkzOCw4NC41OTA5Mjk3IEM3Ni4yNzEyOTM4LDgyLjczODA4NTggNzQuOTAxNjMzLDgxLjI4NjYxMTEgNzMuMTUyNzQwOSw4MS4yODY2MTExIEM3MS40MDc2OTg3LDgxLjI4NjYxMTEgNjkuOTg3MDI0Niw4Mi43Njg4ODYzIDY5Ljk4NzAyNDYsODQuNTkwOTI5NyBMNjkuOTg3MDI0Niw5Mi43NDkyMTgyIEM2OS45ODcwMjQ2LDkzLjkxNjc1MDYgNzAuOTM3MDI4Miw5NC44Njg2NzkyIDcyLjEwMzU5OCw5NC44NzE1NjY4IEw3Mi4xMDkzNzMxLDk0Ljg3MTU2NjggQzcyLjY3OTE4MjgsOTQuODcxNTY2OCA3My4yMTcyMjk0LDk0LjY0ODI2MyA3My42MjM0MTEzLDk0LjI0MzA0MzYgQzc0LjAzMTUxODMsOTMuODM1ODk5MiA3NC4yNTU3ODQ2LDkzLjI5Njg5IDc0LjI1NTc4NDYsOTIuNzI1MTU1MyBMNzQuMjU1Nzg0Niw4NC41OTg2Mjk5IEM3NC4yNTU3ODQ2LDg0LjI4OTY2MjEgNzQuMTM1NDcsODMuOTk5OTQ0NyA3My45MTY5Nzg4LDgzLjc4MjQxNiBDNzMuNjk5NDUwMSw4My41NjM5MjQ4IDczLjQxMDY5NTIsODMuNDQ0NTcyOCA3My4xMDI2OSw4My40NDQ1NzI4IEw3My4wOTk4MDI1LDgzLjQ0NDU3MjggQzcyLjc5MTc5NzIsODMuNDQ1NTM1MyA3Mi41MDMwNDIzLDgzLjU2NjgxMjMgNzIuMjg1NTEzNiw4My43ODUzMDM1IEM3Mi4wNjc5ODQ5LDg0LjAwMzc5NDggNzEuOTQ4NjMyOSw4NC4yOTM1MTIyIDcxLjk0OTU5NTQsODQuNjAyNDc5OSBMNzEuOTY5ODA4Myw5MS4xNTQzMjg3IEM3MS45NzA3NzA4LDkxLjQyMDk0NTcgNzEuNzU0MjA0Niw5MS42MzE3MzY4IDcxLjQ4ODU1MDEsOTEuNjM2NTQ5MyBDNzEuMjIzODU4MSw5MS42MzY1NDkzIDcxLjAwODI1NDQsOTEuNDIxOTA4MiA3MS4wMDcyOTE5LDkxLjE1NzIxNjIgTDcwLjk4NzA3OTEsODQuNjA0NDA1IEM3MC45ODUxNTQxLDg0LjAzOTQwNzkgNzEuMjAzNjQ1Myw4My41MDcxMzYzIDcxLjYwMzA4OTUsODMuMTA1NzY3IEM3Mi4wMDE1NzEzLDgyLjcwNTM2MDIgNzIuNTMxOTE3OCw4Mi40ODM5ODE1IDczLjA5Nzg3NzQsODIuNDgyMDU2NCBMNzMuMTAyNjksODIuNDgyMDU2NCBDNzMuNjY2NzI0Niw4Mi40ODIwNTY0IDc0LjE5NzA3MTEsODIuNzAxNTEwMSA3NC41OTY1MTU0LDgzLjEwMDk1NDQgQzc0Ljk5Nzg4NDcsODMuNTAwMzk4NyA3NS4yMTgzMDA5LDg0LjAzMjY3MDIgNzUuMjE4MzAwOSw4NC41OTg2Mjk5IEw3NS4yMTgzMDA5LDkyLjcyNTE1NTMgQzc1LjIxODMwMDksOTMuNTUzODgxOSA3NC44OTM5MzI5LDk0LjMzNTQ0NTIgNzQuMzAyOTQ3OSw5NC45MjQ1MDUyIEM3My43MTQ4NTA0LDk1LjUxMTY0MDEgNzIuOTM2MTc0Nyw5NS44MzQwODMxIDcyLjEwOTM3MzEsOTUuODM0MDgzMSIgaWQ9IumZhOS7tiIgZmlsbD0iIzUyNUM2RiIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzMuMTI5MTU5LCA4OC4wNzkwODkpIHJvdGF0ZSgtMzIwLjAwMDAwMCkgdHJhbnNsYXRlKC03My4xMjkxNTksIC04OC4wNzkwODkpICI+PC9wYXRoPg0KICAgICAgICAgICAgPGcgaWQ9IuWIh+WbviIgc2tldGNoOnR5cGU9Ik1TTGF5ZXJHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzIuMDAwMDAwLCAxNi4wMDAwMDApIj48L2c+DQogICAgICAgIDwvZz4NCiAgICA8L2c+DQo8L3N2Zz4='
};
private styles = csjs`
.container {
display: inline-block;
position: relative;
outline: none;
width: 280px;
height: 64px;
border-radius: 4px;
background-color: #ffffff !important;
border: solid 1px #e0e1e5;
vertical-align: middle;
}
.download {
position: absolute;
right: 0;
z-index: 100;
margin-top: -26px;
margin-right: 22px;
cursor: pointer;
display: none;
}
.history {
position: absolute;
right: 0;
z-index: 100;
margin-top: -26px;
margin-right: 44px;
cursor: pointer;
display: none;
}
.container:hover .download,
.container:hover .history{
display: block;
}
.icon {
margin: 13px 17px 15px 15px;
float: left;
-webkit-print-color-adjust: exact;
}
.title {
height: 18px;
font-size: 12px;
font-weight: 500;
font-style: normal;
font-stretch: normal;
text-align: justify;
color: #393939;
margin-top: 16px;
width: 166px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
cursor: default;
}
.name {
max-width: 115px;
display: inline-block;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin: 0;
}
.type {
display: inline-block;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin: 0;
}
.size {
font-size: 12px;
font-weight: normal;
font-style: normal;
font-stretch: normal;
text-align: justify;
color: #797979;
cursor: default;
height: 14px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
`;
private resource = 'http://note.youdao.com/styles/images/02e7cce7.appstore.png';
private name = '我是新版附件';
constructor() {
this.downloadAttachment = this.downloadAttachment.bind(this);
}
renderView(macroContent: string): Promise<HTMLElement> {
return new Promise((resolve, reject) => {
let attachment;
try {
attachment = JSON.parse(macroContent);
} catch (e) {
reject('parse macro content failed');
}
if (!attachment) {
reject('macro content is empty');
}
// let src = attachment.source;
const fileName = attachment.fileName;
const fileSize = attachment.fileLength;
const fileType = AttachmentParse.getFileType(fileName);
let fileIcon;
if (!fileType) {
fileIcon = 'bulb__svg--type_other_large';
} else {
fileIcon = 'bulb__svg--type_' + fileType + '_large';
}
const size = typeof(fileSize) === 'undefined' ?
'未知大小' : AttachmentParse.getSizeString(fileSize);
const splitFileName = AttachmentParse.splitFileFullName(fileName);
const template = $(
`
<div class="${this.styles.container}" title="${fileName}">
<style>
$\{csjs.getCss(this.styles)\}
</style>
<div class="${this.styles.icon} bulb__svg-common ${fileIcon}"></div>
<div class="${this.styles.title}">
<p class="${this.styles.name}">${splitFileName.fileName}</p>
<p class="${this.styles.type}">${splitFileName.fileType}</p>
</div>
<div class="${this.styles.size}">${size}</div>
<div class="${this.styles.download} bulb__svg--attacthment_download bulb__svg-common"></div>
<div class="${this.styles.history} bulb__svg--attacthment_download bulb__svg-common"></div>
</div>
`);
if (template) {
const $download: JQuery = template.find('.' + this.styles.download);
$download.click\(this.downloadAttachment\);
}
if (template && template.get(0)) {
resolve(template.get(0));
} else {
reject('render html failed');
}
});
}
getReadView(macroContent: string): Promise<HTMLElement> {
return this.renderView(macroContent);
}
getEditView(macroContent: string): Promise<HTMLElement> {
return this.renderView(macroContent);
}
getEditDialog(macroContent: string): Promise<string> {
return new Promise((resolve, reject) => {
let attachment;
try {
attachment = JSON.parse(macroContent);
} catch (e) {
reject('parse macro content failed');
}
if (!attachment) {
reject('macro content is empty');
}
const fileName = window.prompt('请输入附件名称', attachment.fileName);
if (fileName !== null) {
attachment.fileName = fileName;
}
resolve(JSON.stringify(attachment));
});
}
getInsertDialog(): Promise<string> {
return Promise.resolve(JSON.stringify({
fileName: '附件',
fileLength: '20',
source: 'http://www.baidu.com',
}));
}
private downloadAttachment() {
const node = document.createElement('a');
node.setAttribute('href', this.resource);
node.setAttribute('download', this.name);
node.click();
}
}
export = AttachmentMacro;
1. 编辑器无法加载?
检查下编辑器授权接口回调的错误信息,保证申请的appKey是有效且提前绑定了。
应用没有绑定服务实例,可以新建服务实例,绑定服务实例。
appKey无效,注册账号, 登录后台创建应用和实例并完成绑定, 可获得应用ID和密钥等信息,其中应用ID就是appKey( 注意不是应用密钥)
错误码 | 含义 |
---|---|
101 | 缺少必填的参数,出现这个情况还可能是et的值和实际加密方式不对应 |
102 | 不支持的语言类型 |
103 | 翻译文本过长 |
104 | 不支持的API类型 |
105 | 不支持的签名类型 |
106 | 不支持的响应类型 |
107 | 不支持的传输加密类型 |
108 | appKey无效,注册账号, 登录后台创建应用和实例并完成绑定, 可获得应用ID和密钥等信息,其中应用ID就是appKey( 注意不是应用密钥) |
109 | batchLog格式不正确 |
110 | 无相关服务的有效实例 |
111 | 开发者账号无效 |
113 | q不能为空 |
201 | 解密失败,可能为DES,BASE64,URLDecode的错误 |
202 | 签名检验失败 |
203 | 访问IP地址不在可访问IP列表 |
301 | 辞典查询失败 |
302 | 翻译查询失败 |
303 | 服务端的其它异常 |
401 | 账户已经欠费停 |
上线日期 | 版本号 | 更新内容 |
---|---|---|
20180209 | v1.0.0 | 有道编辑器SDK Web版上线,支持富文本编辑 |
20181109 | v1.1.0 | 添加html和纯文本格式输出,添加宏配置,添加工具栏定制 |