背景

在网上找了一些wangEditor编辑器实现格局刷的代码,然而都是基于v4版本的,齐全不适用于v5版本,所以只能依据文档和源码,本人实现了一个繁难版本的

实现

第一步

注册新菜单(https://www.wangeditor.com/v5...)
格局刷外部次要实现,点击格局刷时保留选中文字的款式

// FormatterBtn.tsx import type { IButtonMenu, IDomEditor } from '@wangeditor/editor';import { SlateEditor } from '@wangeditor/editor';class FormatterBtn implements IButtonMenu {  title = '格局刷';  tag = 'button'; // JS 语法  iconSvg = '<svg t="1665739261235" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6325" width="200" height="200"><path d="M1024 1012.48a2119.68 2119.68 0 0 1-45.312-409.6v-228.864a145.92 145.92 0 0 0-146.944-146.176h-128v-128a100.096 100.096 0 0 0-99.84-99.84h-182.016a100.608 100.608 0 0 0-100.352 99.84v128h-128a145.92 145.92 0 0 0-145.664 146.176v228.608a2139.904 2139.904 0 0 1-45.312 409.6L0 1024h1024zM421.888 99.84h182.016v128h-182.272v-128z m274.432 742.4l3.328-146.944a51.2 51.2 0 0 0-25.6-43.52 48.896 48.896 0 0 0-51.2 0 51.2 51.2 0 0 0-25.6 43.52l-2.304 121.344-4.352 69.12L588.8 921.6h-185.856a532.992 532.992 0 0 0 23.296-160.256 51.2 51.2 0 1 0-100.096 0A420.352 420.352 0 0 1 296.448 921.6H120.832l2.304-13.824a2033.92 2033.92 0 0 0 25.6-307.2v-39.168h728.576v67.072a2063.104 2063.104 0 0 0 25.6 281.6l2.304 13.824h-216.064zM877.312 460.8H148.48v-91.392a45.568 45.568 0 0 1 45.312-40.96h642.56a45.312 45.312 0 0 1 40.96 45.312z" p-id="6326"></path></svg>';  // 获取菜单执行时的 value ,用不到则返回空 字符串或 false  getValue(): string | boolean {    return '';  }  // 菜单是否须要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false  isActive(editor: IDomEditor & { copyStyleObject: any }): boolean {    return !!editor?.copyStyleObject;  }  // 菜单是否须要禁用(如选中 H1 ,“援用”菜单被禁用),用不到则返回 false  isDisabled(editor: IDomEditor): boolean {    return !editor.getSelectionText();  }  // 点击菜单时触发的函数  exec(editor: IDomEditor & { copyStyleObject: any }) {    if (this.isDisabled(editor)) {      return;    }    editor.copyStyleObject = SlateEditor.marks(editor);  }}export default FormatterBtn;

第二步

格局刷插入到菜单中,文档中应用insertKeys能够插入对应地位,我在代码中尝试没失效,只能采纳笨办法,重写编辑器的菜单,你们能够再尝试下

第三步

监听编辑器外部鼠标按下和抬起来给选中内容赋值

import React, { useCallback, useEffect, useState, useMemo } from 'react';import type { IButtonMenu, IDomEditor } from '@wangeditor/editor';import { SlateEditor, Boot, SlateText } from '@wangeditor/editor';import { Editor, Toolbar } from '@wangeditor/editor-for-react';import FormatterBtn from './FormatterBtn';import '@wangeditor/editor/dist/css/style.css'; // 引入 cssconst defaultToolbarConfig = {  // 和编辑器默认的配置一样,只多加了格局刷  toolbarKeys: [    'headerSelect',    'blockquote',    '|',    'bold',    'underline',    'italic',    {      key: 'group-more-style', // 以 group 结尾      title: '更多',      iconSvg:        '<svg viewBox="0 0 1024 1024"><path d="M204.8 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M505.6 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M806.4 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path></svg>',      menuKeys: ['through', 'code', 'sup', 'sub', 'clearStyle'],    },    'color',    'bgColor',    '|',    'fontSize',    'fontFamily',    'lineHeight',    '|',    'bulletedList',    'numberedList',    'todo',    {      key: 'group-justify', // 以 group 结尾      title: '两端对齐',      iconSvg:        '<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>',      menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'],    },    {      key: 'group-indent', // 以 group 结尾      title: '缩进',      iconSvg:        '<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>',      menuKeys: ['indent', 'delIndent'],    },    '|',    'emotion',    'insertLink',    {      key: 'group-image', // 以 group 结尾      title: '图片',      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>',      menuKeys: ['insertImage', 'uploadImage'],    },    {      key: 'group-video', // 以 group 结尾      title: '视频',      iconSvg:        '<svg viewBox="0 0 1024 1024"><path d="M981.184 160.096C837.568 139.456 678.848 128 512 128S186.432 139.456 42.816 160.096C15.296 267.808 0 386.848 0 512s15.264 244.16 42.816 351.904C186.464 884.544 345.152 896 512 896s325.568-11.456 469.184-32.096C1008.704 756.192 1024 637.152 1024 512s-15.264-244.16-42.816-351.904zM384 704V320l320 192-320 192z"></path></svg>',      menuKeys: ['insertVideo', 'uploadVideo'],    },    'insertTable',    'codeBlock',    'divider',    '|',    'undo',    'redo',    'formatBrush',    '|',    'fullScreen',  ],};const menu1Conf = {  key: 'formatBrush', // 定义 menu key :要保障惟一、不反复(重要)  factory() {    return new FormatterBtn() as IButtonMenu;  },};// 注册格局刷菜单Boot.registerMenu(menu1Conf);let isMouseDown = false;const MyEditor = () => {  // editor 实例  const [editor, setEditor] = useState<(IDomEditor & { copyStyleObject?: any }) | null>(null); // TS 语法  const changeMouseDown = () => {    isMouseDown = true;  };  const changeMouseup = useCallback(() => {    const selectText = editor.getSelectionText();    if (isMouseDown && editor?.copyStyleObject && selectText) {      // 先革除选中节点的款式      // 获取所有 text node      const nodeEntries = SlateEditor.nodes(editor, {        match: (n) => SlateText.isText(n),        universal: true,      });      for (const nodeEntry of nodeEntries) {        // 单个 text node        const n = nodeEntry[0];        const keys = Object.keys(n as object);        keys.forEach((key) => {          if (key === 'text') {            // 保留 text 属性,text node 必须的            return;          }          // 其余属性,全副革除          SlateEditor.removeMark(editor, key);        });      }      // 再赋值新款式      Object.entries(editor.copyStyleObject).forEach(([key, value]) => {        if (key === 'text') {          // 保留 text 属性,text node 必须的          return;        }        editor.addMark(key, value);      });      editor.copyStyleObject = undefined;      isMouseDown = false;    }  }, [editor]);// 因为本次需要编辑器在抽屉里 开不同的抽屉会注册多个编辑器,导致编辑器的id不是一个 监听时要监听对应的编辑器,如果只有一个编辑器,这块能够删除,监听间接用w-e-textarea-1  const domId = useMemo(() => {    return editor?.id?.split('-')?.[1] ? `w-e-textarea-${editor?.id?.split('-')?.[1]}` : undefined;  }, [editor]);  useEffect(() => {    if (domId) {      document.getElementById(domId)?.addEventListener('mousedown', changeMouseDown);      document.getElementById(domId)?.addEventListener('mouseup', changeMouseup);    }  }, [domId, changeMouseup]);  useEffect(() => {    return () => {      document.getElementById(domId)?.removeEventListener('mousedown', changeMouseDown);      document.getElementById(domId)?.removeEventListener('mouseup', changeMouseup);    };    // eslint-disable-next-line react-hooks/exhaustive-deps  }, []);  return (    <div style={{ border: '1px solid #ccc', zIndex: 100 }}>      <Toolbar        editor={editor}        defaultConfig={defaultToolbarConfig}        style={{ borderBottom: '1px solid #ccc' }}      />      <Editor onCreated={setEditor} />    </div>  );};export default MyEditor;