关于wangeditor的自定义组件和元素

需求

需求是:我要在富文本中新建一个下拉框,点击下拉框让页面中出现类似于按钮的样式

参考链接官网:www.wangeditor.com/v5/developm...

实现

注册新菜单
js 复制代码
class MyButtonMenu {
    constructor(vueInstance) {
        this.vueInstance = vueInstance;
        this.title = '按钮'; // 自定义菜单标题
        this.iconSvg = '<svg></svg>'; // 可选 菜单图标
        this.tag = 'select'; // 自定义菜单的 HTML 标签
        this.showModal = true
        this.modalWidth = 300
    }

    // 获取菜单执行时的 value,返回空字符串或 false
    getValue(editor) {
        return '1'; // 默认返回空
    }
    getOptions(editor) { 
        const options = [
            { value: '1',text: '入口核对',styleForRenderMenuList: {},},
            { value: '2', text: '出口核对', selected: false },
            // { value: 'shenzhen', text: '深圳' },
        ]
        return options
    }
    // 菜单是否需要激活(如选中加粗文本,"加粗"菜单会激活),用不到则返回 false
    isActive(editor) {
        return false; // 默认不激活
    }

    // 菜单是否需要禁用(如选中 H1,"引用"菜单被禁用),用不到则返回 false
    isDisabled(editor) {
        return false; // 默认不禁用
    }

    // 点击菜单时触发的函数
    exec(editor, value) {
        if (this.isDisabled(editor)) return;
        let name = "按钮1"
        if (value != 1) {
            name = "按钮2"
        }
        //插入元素,因为是要自定义插入样式
        editor.insertNode({
            type: "shiftButton",
            fileName: name,
            children: [{
                text: "", 
                style: {
                    display: 'inline-block', // inline
                    marginLeft: '3px',
                    marginRight: '3px',
                    fontSize: '14px',
                    color: '#409eff',
                    border: '2px solid #409eff',
                    borderRadius: '3px',
                    padding: '2px 3px',
                    backgroundColor: '#fff',
                }
            }],
        });
        //会报错
        // this.vueInstance.editHTML(value);
    }
}
// 注册菜单
const shiftButton = {
    key: "shiftButton", // 定义唯一的 menu key
    factory: () => new MyButtonMenu(this) // 使用箭头函数以避免 `this` 问题
};
export default shiftButton;
劫持编辑器事件和操作
js 复制代码
/**
 * @description shiftButton plugin
 * @author 
 */

import { DomEditor } from '@wangeditor/editor'

// 定义 inline 和 void
function withAttachment(editor) {                  
    const { isInline, isVoid } = editor
    const newEditor = editor

    newEditor.isInline = (elem) => {
        const type = DomEditor.getNodeType(elem)
        if (type === 'shiftButton') return true // 针对 type: attachment ,设置为 inline
        return isInline(elem)
    }

    newEditor.isVoid = (elem) => {
        const type = DomEditor.getNodeType(elem)
        if (type === 'shiftButton') return true // 针对 type: attachment ,设置为 void
        return isVoid(elem)
    }

    return newEditor // 返回 newEditor ,重要!!!
}

export default withAttachment
定义新元素
在编辑器里渲染新元素
js 复制代码
// 注册一个新元素到页面中
import { DomEditor, IDomEditor } from '@wangeditor/editor'
import { Boot, IModuleConf } from '@wangeditor/editor'


import { h, VNode } from 'snabbdom'
import { SlateElement } from '@wangeditor/editor'

// 定义节点结构
const resume = {
    type: 'uploadattachment',
    fileName: 'resume.pdf',
    link: 'https://xxx.com/files/resume.pdf',
    children: [{ text: '' }]  // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
}

/**
 * 渲染"附件"元素到编辑器
 * @param elem 附件元素,即上文的 myResume
 * @param children 元素子节点,void 元素可忽略
 * @param editor 编辑器实例
 * @returns vnode 节点(通过 snabbdom.js 的 h 函数生成)
 */
function renderAttachment(elem, children, editor) {                                                // JS 语法
  // 当前节点是否选中
  const selected = DomEditor.isNodeSelected(editor, elem)
  const isDisabled = editor.isDisabled()
    // 获取"附件"的数据,参考上文 myResume 数据结构
    const { fileName = '', link = '' } = elem

    // icon 图标 vnode
    const iconVnode = h(
        // HTML tag
        'img',
        // HTML 属性
        {
            props: {
                src: ''//如果需要的话,传入base64格式,参考官网
            },
            style: {
                width: '1em',
                marginRight: '0.1em',
                minWidth: '0',
                minHeight: '0',
            },
        }
        // img 没有子节点,所以第三个参数不用写
    )

    // 元素 vnode
    const attachVnode = h(
        // HTML tag
        'span',
        {
            props: {
                contentEditable: false, // 不可编辑
            },
            style: {
                display: 'inline-block', // inline
                marginLeft: '3px',
                marginRight: '3px',
                fontSize:'14px',
                color:'#409eff',
                border:
                    selected && !isDisabled
                        ? '2px solid var(--w-e-textarea-selected-border-color)' // wangEditor 提供了 css var https://www.wangeditor.com/v5/theme.html
                        : '2px solid #409eff',
                borderRadius: '3px',
                padding: '2px 3px',
                backgroundColor: '#fff',
                cursor: isDisabled ? 'pointer' : 'inherit',
            },
            on: {
                click() {
                    if (!isDisabled) return
                },
            },
        },
        // 子节点
        [ fileName]
    )

    return attachVnode
}

export const renderElemConf = {
    type: 'shiftButton', // 新元素 type ,重要!!!
    renderElem: renderAttachment,
}
把新元素转换为 HTML
js 复制代码
import { SlateElement } from '@wangeditor/editor'

/**
 * 生成"附件"元素的 HTML
 * @param elem 附件元素,即上文的 myResume
 * @param childrenHtml 子节点的 HTML 代码,void 元素可忽略
 * @returns "附件"元素的 HTML 字符串
 */
function attachmentToHtml(elem, childrenHtml) {                             // JS 语法

  // 获取附件元素的数据
  const { link = '', fileName = '', children } = elem
  const style = JSON.stringify(children[0].style);
  // let newStr = style.replace(/\"/g, "'");
  // 生成 HTML 代码
  const html = `<span
        data-w-e-type="shiftButton"
        data-w-e-is-void
        style=" 
            display: inline-block;
            margin-left: 3px;
            margin-right: 3px;
            font-size:14px;
            color:#3e6df1;border:
            2px solid #3e6df1;
          border-radius: 3px;
          padding: 2px 3px;
          cursor: pointer;"
        class="clickable"
        data-fileName="${fileName}"
    >
    ${fileName}</span>`
  return html
}

const elemToHtmlConf = {
  type: 'shiftButton', // 新元素的 type ,重要!!!
  elemToHtml: attachmentToHtml,
}
export default elemToHtmlConf;

解析新元素到页面

js 复制代码
/**
 * @description 解析新元素到html中
 * @author 
 */

function parseHtml(elem, children, editor) {
    const buttonName = elem.getAttribute('data-filename') || '';
    return {
        type: 'shiftButton',
        fileName:buttonName,
        children: [{ text: '' }], // void node 必须有一个空白 text
    }
}

const parseHtmlConf = {
    selector: 'span[data-w-e-type="shiftButton"]',
    parseElemHtml: parseHtml,
}

export default parseHtmlConf
相关推荐
无奈何杨33 分钟前
CoolGuard增加枚举字段支持,条件编辑优化,展望指标取值不同
前端·后端
掘金安东尼35 分钟前
工具过多:如何管理前端工具泛滥?
前端
江城开朗的豌豆1 小时前
从生命周期到useEffect:我的React函数组件进化之旅
前端·javascript·react.js
brzhang1 小时前
当AI接管80%的执行,你“不可替代”的价值,藏在这20%里
前端·后端·架构
江城开朗的豌豆1 小时前
React组件传值:轻松掌握React组件通信秘籍
前端·javascript·react.js
Sailing1 小时前
别再放任用户乱填 IP 了!一套前端 IP 与 CIDR 校验的高效方案
前端·javascript·面试
程序员爱钓鱼4 小时前
Go语言实战案例 — 项目实战篇:简易博客系统(支持评论)
前端·后端·go
excel11 小时前
ES6 中函数的双重调用方式:fn() 与 fn\...``
前端
可乐爱宅着11 小时前
全栈框架next.js入手指南
前端·next.js
你的人类朋友12 小时前
什么是API签名?
前端·后端·安全