背景
在网上找了一些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;