WangEditor 插件开发
官网上有完整的流程 自定义扩展新功能 | wangEditor
我这里开发的是一个图片组件。图片需要从资源库里面选择
注册新菜单
typescript
import type { IButtonMenu, IDomEditor } from '@wangeditor/editor';
export default class ImageMenu implements IButtonMenu {
title = '图片'
tag = 'button'
// eslint-disable-next-line max-len
iconSvg = '<svg viewBox="0 0 1024 1024" > <path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z" > </path></svg >';
getValue() {
return false;
}
isActive() {
return false; // or true
}
isDisabled() {
return false; // or true
}
exec(editor: IDomEditor) {
editor.emit('imageMenuClick');
}
}
exec 为什么用emit 事件。
图片是从资源库选择的, 但是选择的时候会动态的改变可选择的图片类型, 所以需要在封装的编辑器组件里面读取props 里面的值。那exec 方法要获取这个值的话, 就只能在组件的mounted 里面注册。 这就会产生重复注册的问题。所有通过emit事件触发。 然后编辑器组件监听这个事件,唤起资源库选择
typescript
this.editor!.on('imageMenuClick', () => {
this.dialogVisible.imageSourceChoose = true;
});
exec 非emit 处理参考 github.com/wangeditor-... 大概意思是把编辑器传入弹窗组件
劫持编辑器事件和操作
定义新元素
typescript
import { IDomEditor } from '@wangeditor/editor';
function withCustomImage<T extends IDomEditor>(editor: T): T {
const { isInline, isVoid } = editor;
const newEditor = editor;
newEditor.isInline = (elem: any) => {
const { type } = elem;
if (type === 'img') {
return true;
}
return isInline(elem);
};
newEditor.isVoid = (elem: any) => {
const { type } = elem;
if (type === 'img') {
return true;
}
return isVoid(elem);
};
return newEditor;
}
export default withCustomImage;
上面没什么好说的, 就是定义这个元素是不是inline节点数据结构 | wangEditor , void节点数据结构 | wangEditor
typescript
import { IEditorImageElement } from '@/models/WangEditor/Image';
import { SlateElement, DomEditor, IDomEditor } from '@wangeditor/editor';
import { h, VNode } from 'snabbdom';
function renderCustomImage(elem: SlateElement, children: VNode[] | null, editor: IDomEditor) {
const { src, cssClass } = elem as IEditorImageElement;
const isSelected = DomEditor.isNodeSelected(editor, elem);
const classObj: Record<string, boolean> = {};
cssClass.forEach((className) => {
classObj[className] = true;
});
const vnode = h('img', {
props: {
src,
contentEditable: false,
},
style: {
marginLeft: '3px',
marginRight: '3px',
border: isSelected
? '2px solid var(--w-e-textarea-selected-border-color)' // wangEditor 提供了 css var https://www.wangeditor.com/v5/theme.html
: '2px solid transparent',
},
className: cssClass,
});
return vnode;
}
const renderElemConf = {
type: 'img',
renderElem: renderCustomImage,
};
export default renderElemConf;
必须要加上margin,不然inline元素插入以后 光标无法选择到元素的后面。
把新元素转换为 HTML
typescript
import { IEditorImageElement } from '@/models/WangEditor/Image';
import { SlateElement } from '@wangeditor/editor';
function customImageToHtml(elem: SlateElement): string {
const { src, cssClass } = elem as IEditorImageElement;
return `<img data-w-e-type="img" data-w-e-is-void data-w-e-is-inline class="${cssClass.join(' ')}" src="${src}"/>`;
}
const elemToHtmlConf = {
type: 'img', // 节点 type ,重要!!!
elemToHtml: customImageToHtml,
};
export default elemToHtmlConf;
html 上的 attr 是必须的, 用来判断的
解析新元素 HTML 到编辑器
typescript
import { IEditorImageElement } from '@/models/WangEditor/Image';
import { DOMElement } from '@wangeditor/editor/dist/editor/src/utils/dom';
function parseHtml(elem: DOMElement) {
const src = elem.getAttribute('src');
const classNames = elem.getAttribute('class');
return {
type: 'img',
src,
cssClass: classNames ? classNames.split(' ') : [],
children: [{ text: '' }],
} as IEditorImageElement;
}
const parseHtmlConf = {
selector: 'img[data-w-e-type="img"]',
parseElemHtml: parseHtml,
};
export default parseHtmlConf;
注意点
- void 元素必须插入一个 { text: ''}
- 插入元素到原光标位置需要
**restoreSelection**()
。为了书写体验, 可以用editor.move(2) 移动到下一个光标 - 插入非inline 元素, 必须判断下一行是不是有可编辑区域,不然无法编辑, 参考github.com/wangeditor-...
本文使用 markdown.com.cn 排版