wangEditor修改默认图标和自定义菜单(React)

       最近完成了我们的社区模块,想要发送帖子,必须要有个富文本编辑器,经过选型发现wangeditor比较简单易用。不过也发现它的菜单封装有点过度,图标没法自定义,很多功能也不是想要的。那么就只能重写一部分,封装一下。

基本原理

       富文本编辑器通过contenteditable来使得dom像文本那样可以编辑。通过dom的range来判断位置,插入dom。通过document.execCommand执行各种range的操作。

代码

       wangeditor的菜单想要重写,可以通过继承它的基本菜单类,再注册,当前版本 “wangeditor”: “^4.0.8”,代码如下:

import React, {useState,useEffect} from 'react';
import {constance} from "./publicFunctions"
import E from 'wangeditor';
import store from '../store/store';
const { $ } = E;
const { BtnMenu, DropListMenu, PanelMenu, DropList, Panel, Tooltip } = E;
let editor = null;
let fontColorList=["#000000","#FF3D1F","#28AA55","#FFA319","#D76ED2","#BF4F17","#A5B95A","#867BD2","#39A1E6"];//文字颜色配置
let mimadaoEmoji=[//配置表情包
    require("../static/emoji/AiMa-好哦表情包!.gif"),
   ...//图片地址
];
let fontSizeList=[{//字体大小配置1-7
    name:"极小",
    iconSize:"13px",
    value:'2'
},{
    name:"小",
    iconSize:"16px",
    value:'3'
},{
    name:"中等",
    iconSize:"18px",
    value:'4'
},{
    name:"大",
    iconSize:"24px",
    value:'5'
},{
    name:"特大",
    iconSize:"32px",
    value:'6'
},{
    name:"极大",
    iconSize:"48px",
    value:'7'
},];
let emojiList=[//配置表情包切换
    {
        // tab 的标题
        title:"密码岛",
        // type -> 'emoji' / 'image'
        type:"image",
        // content -> 数组
        content:mimadaoEmoji.map(val=>{return {alt:"",src:val}})
    },{
        title: 'emoji',
        type: 'emoji',
        content: '😀 😃 😄 😁 😆 😅 😂 😊 😇 🙂 🙃 😉 😓 😪 😴 🙄 🤔 😬 🤐'.split(/\s/),
    },
]
let configEditorMenu=[{//解析器模式自定义菜单
    key:"alignLeft",//唯一标识菜单
    type:BtnMenu,//被继承的菜单类型
    elem:`<div class="w-e-menu">
            <i class="iconfont iconzuoduiqi"></i>
        </div>`,
    command:"justifyLeft",
    eventEnum:{
        tryChangeActive:function(){
            const editor = this.editor
            if (editor.cmd.queryCommandState('justifyLeft')) {
                this.active()
            } else {
                this.unActive()
            }
        }
    }
},{
    key:"aligncenter",
    type:BtnMenu,
    elem:`<div class="w-e-menu">
           <i class="iconfont iconshiliangzhinengduixiang"></i>
        </div>`,
    command:"justifyCenter",
    eventEnum:{
        tryChangeActive:function(){
            const editor = this.editor
            if (editor.cmd.queryCommandState('justifyCenter')) {
                this.active()
            } else {
                this.unActive()
            }
        }
    }
},{
    key:"alignRight",
    type:BtnMenu,
    elem:`<div class="w-e-menu">
            <i class="iconfont iconyouduiqi"></i>
        </div>`,
    command:"justifyRight",
    eventEnum:{
        tryChangeActive:function(){
            const editor = this.editor
            if (editor.cmd.queryCommandState('justifyRight')) {
                this.active()
            } else {
                this.unActive()
            }
        }
    }
},{//文字颜色
    key:"exFontColor",
    type:DropListMenu,
    elem:`<div class="w-e-menu">
            <i class="iconfont iconzitiyanse"></i>
        </div>`,
    exConfig:{
        width: 100,
        title: '文字颜色',
        type: 'inline-block',
        list: fontColorList.map(color => {
            return {
                $elem: $(`<i style="background:${color};" class="mmd-noteedit-font-color-nail"></i>`),
                value: color,
            }
        }),
        // droplist 每个 item 的点击事件
        clickHandler: function(value){
            console.log(value,this);
            // value 参数即 exConfig.list 中配置的 value
            this.cmd.do("foreColor", value)
        },
    },
    eventEnum:{}
},{//文字大小
    key:"exFontSize",
    type:DropListMenu,
    elem:`<div class="w-e-menu">
            <i class="iconfont iconzitidaxiao"></i>
        </div>`,
    exConfig:{
        width: 100,
        title: '文字大小',
        type: 'list',
        list: fontSizeList.map(size => {
            return {
                $elem: $(`<i style="font-size:${size.iconSize};" class="mmd-noteedit-font-size-nail">${size.name}</i>`),
                value: size.value,
            }
        }),
        // droplist 每个 item 的点击事件
        clickHandler: function(value){
            console.log(value,this);
            // value 参数即 exConfig.list 中配置的 value
            this.cmd.do("fontSize", value)
        },
    },
    eventEnum:{}
},{//密码岛表情包
    key:"exEmoji",
    type:PanelMenu,
    elem:`<div class="w-e-menu">
            <img class="mmd-editor-menu-icon" alt="" src="${require("../static/icon-emoji.png")}" />
        </div>`,
    eventEnum:{
        clickHandler:function(){
            let emojiCfg={//配置弹出框
                width:400,
                height:230,
                tabs:emojiList.map((val,key)=>{
                    return {
                    title:`${val.title}`,
                    // 判断type类型如果是image则以img的形式插入否则以内容
                    tpl: `<div>${GenerateExpressionStructure(val)}</div>`,
                    events: [
                        {
                            selector: '.eleImg',
                            type: 'click',
                            fn: (e) => {
                                // e为事件对象
                                const $target = $(e.target)
                                const nodeName = $target.getNodeName()
                                let insertHtml
                                if (nodeName === 'IMG') {
                                    // 插入图片
                                    insertHtml = $target.parent().html().trim()
                                } else {
                                    // 插入 emoji
                                    insertHtml = '<span>' + $target.html() + '</span>'
                                }
                                editor.cmd.do('insertHTML', insertHtml)
                                // 示函数执行结束之后关闭 panel
                                return true
                            },
                        },
                    ],}
                })
            };
            const panel = new Panel(this, emojiCfg)
            panel.create()
        }
    }
}];
function GenerateExpressionStructure(ele){//判断生成的emoji类型
    let res = []
    // 如果type是image类型则生成一个img标签
    if (ele.type == 'image') {
        res = ele.content.map((con) => {
            if (typeof con == 'string') return ''
            return `<span class="mmd-emoji-item" title="${con.alt}">
                <img class="eleImg" style="width:80px;height:80px;" src="${con.src}" alt="[${con.alt}]">
            </span>`
        })
        res = res.filter((s) => s !== '')
    }
    //否则直接当内容处理
    else {
        res = ele.content.map((con) => {
            return `<span class="icon-emoji eleImg" title="${con}">${con}</span>`
        })
    }
    return res.join('').replace(/&nbsp;/g, '')
}
function myExtendBtn(config){//继承默认的菜单类
    if(!config.type){throw new ReferenceError("unspecific extend type!");}
    return class extendBtn extends config.type{
        constructor(editor) {
            const $elem = E.$(config.elem);
            if(config?.exConfig?.clickHandler){//下拉的菜单绑定点击事件,必须在构造之前绑定到editor实例
                config.exConfig.clickHandler=config.exConfig.clickHandler.bind(editor);
            }
            super($elem, editor,config.exConfig);
            for(let events in config.eventEnum){//绑定配置的事件
                this[events]=config.eventEnum[events].bind(this);
            }
        }
        command(value){
            const editor = this.editor
            const $selectionElem = editor.selection.getSelectionContainerElem()
            if ($selectionElem && editor.$textElem.equal($selectionElem)) {
                return
            }
            editor.cmd.do(value, value)
        }
        // 菜单点击事件
        clickHandler(e) {
            config.command && this.command(config.command);
            e.stopPropagation();
        }
        // 什么时候执行这个函数?每次编辑器区域的选区变化(如鼠标操作、键盘操作等),都会触发各个菜单的 tryChangeActive 函数,重新计算菜单的激活状态
        tryChangeActive() {
            
        }
    }
}
function NoteEditor(props){//论坛帖子的富文本编辑器
    const  defaultMenu= ['bold','underline','image','code',].concat(configEditorMenu.map(val=>val.key));
   let unsubscribe=store.subscribe(() =>{//订阅用户登录状态
        let status=store.getState().login;
        editor && editor.$textElem.elems[0].setAttribute('contenteditable',status);
    });
    useEffect(() => {
        console.log(props);
        editor = new E("#mmd-noteeditor-warpcontainer");
        editor.config.onchange = (newHtml) => {
            props.onchange && props.onchange(newHtml);
        }
        for(let i=0;i<configEditorMenu.length;i++){//注册自定义菜单
            let obj=configEditorMenu[i];
            editor.menus.extend(obj.key,myExtendBtn(obj));   
        }
       // 配置菜单栏,删减菜单,调整顺序
        editor.config.menus = props.menuList.concat([])||defaultMenu;
         // 配置全屏功能按钮是否展示
        editor.config.showFullScreen = false;
        editor.config.uploadImgServer =`${constance.vm_domain}/api/shequ/post/uploadBatch`;
        editor.config.uploadImgShowBase64 = false;
        editor.create();
        return () => {// 组件销毁时销毁编辑器
            unsubscribe();
            editor.destroy()
        }
    },[]);
    typeof props.setContent ==="function" && props.setContent((content)=>{
        editor.txt.html(content||""); // 重新设置编辑器内容
    });
    return <div id="mmd-noteeditor-warpcontainer"></div>
}
export default NoteEditor;


上传图片的返回格式要后端固定哦,不然会认为上传失败。

{
    // errno 即错误代码,0 表示没有错误。
    //       如果有错误,errno != 0,可通过下文中的监听函数 fail 拿到该错误码进行自定义处理
    "errno": 0,

    // data 是一个数组,返回图片的线上地址
    "data": [
        "图片1地址",
        "图片2地址",
        "……"
    ]
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值