WangEditor 自定义图片插件开发

WangEditor 插件开发

官网上有完整的流程 自定义扩展新功能 | 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-... 大概意思是把编辑器传入弹窗组件

劫持编辑器事件和操作

自定义扩展新功能 | wangEditor

定义新元素

自定义扩展新功能 | 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;

注意点

  1. void 元素必须插入一个 { text: ''}
  2. 插入元素到原光标位置需要**restoreSelection**() 。为了书写体验, 可以用editor.move(2) 移动到下一个光标
  3. 插入非inline 元素, 必须判断下一行是不是有可编辑区域,不然无法编辑, 参考github.com/wangeditor-...

本文使用 markdown.com.cn 排版

相关推荐
Want5951 分钟前
HTML音乐圣诞树
前端·html
老前端的功夫25 分钟前
前端浏览器缓存深度解析:从网络请求到极致性能优化
前端·javascript·网络·缓存·性能优化
Running_slave1 小时前
你应该了解的TCP滑窗
前端·网络协议·tcp/ip
程序员小寒2 小时前
前端高频面试题之CSS篇(一)
前端·css·面试·css3
颜酱2 小时前
Monorepo 架构以及工具选型、搭建
前端·javascript·node.js
oden2 小时前
ChatGPT不推荐你?7个GEO技巧让AI主动引用你的内容
前端
李游Leo3 小时前
前端安全攻防指南:XSS / CSRF / 点击劫持与常见防护实践(含真实案例拆解)
前端·安全·xss
我命由我123453 小时前
微信开发者工具 - 模拟器分离窗口与关闭分离窗口
前端·javascript·学习·微信小程序·前端框架·html·js
E***q5393 小时前
Vue增强现实开发
前端·vue.js·ar
S***42803 小时前
JavaScript在Web中的Angular
前端·javascript·angular.js