wangEditor官网https://www.wangeditor.com/v5/toolbar-config.html
yarn add @wangeditor/editor yarn add @wangeditor/editor-for-react
1、创建工具栏类
import { IButtonMenu, IDomEditor } from '@wangeditor/editor';
import { source } from '../icon';
class SourceMenu implements IButtonMenu {
title: string; // 按钮标题,没有图标的情况则显示该标题,有图标则为tips
tag: string; // 自定义菜单类型
active: boolean; // 选中状态
/**
* 图标这里我的图标是在阿里妈妈适量图标库获取的https://www.iconfont.cn/
* 搜索对应的图标赋值svg代码放到方法返回即可
* icon/index.ts源码
* export const source = () => `<svg>...</svg>`
*/
iconSvg: string;
constructor() {
this.title = '源码' // 自定义菜单标题
this.iconSvg = source() // 图标
this.tag = 'button'
this.active = false
}
/**
* 获取编辑器内容源码
* @param editor
*/
getValue(editor: IDomEditor): string | boolean {
return editor.getHtml();
}
/**
* 菜单是否需要激活,当切换为源码时菜单激活
* @param editor
* @param active 激活状态
*/
isActive(editor: IDomEditor, active?: boolean): boolean {
return this.active;
}
/**
* 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
* @param editor
*/
isDisabled(editor: IDomEditor): boolean {
return false;
}
/**
* 点击菜单时触发的函数
* @param editor
* @param value
*/
exec(editor: IDomEditor, value: string | boolean) {
this.active = !this.active;
if (this.isDisabled(editor)) return
editor.emit('clickSource', this.active);
}
}
export default SourceMenu;
2、工具栏方法实现
import SourceMenu from "./sources";
// @ts-ignore 代码格式化工具
import prettier from 'prettier/standalone';
// @ts-ignore 格式化html
import parserHtml from 'prettier/parser-html';
/**
* 在编辑器中得到的html源码是没有格式的html字符串
* 所以需要格式化展示代码
* 格式化html代码
* @param code
*/
export const parserHtmlCode = (code: string): string => {
try {
return prettier.format(code, {
parser: 'html',
plugins: [parserHtml],
// 格式化的标签不换行 例如span标签等>格式化后会换行
htmlWhitespaceSensitivity: 'ignore'
});
} catch (e) {
console.error('格式化代码错误', e);
return code;
}
}
/**
* 将编辑器html转换为代码块内容
* @param html
*/
export const parseEditorCode = (html: string) => {
let code = html
.replace(/ /g, '')
.replace(new RegExp('<p><br></p>', 'g'), '');
let data = parserHtmlCode(code).trim();
let textCode = data
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/ /g, " ");
return `<pre><code class="language-html">${textCode}</code></pre>`;
}
/**
* 将代码块转换为编辑器html
* @param preCode 代码块
*/
export const parseCodeEditor = (preCode: string) => {
// 转码
let data = encodeURI(preCode);
// 将 转换为空格
data = data.replace(/%C2%A0/g,'%20');
// 解码
data = decodeURI(data);
let htmlStr = data
.replace('<pre><code class="language-html">', '')
.replace('</code></pre>', '')
.replace(/</ig, "<")
.replace(/>/ig, ">");
return htmlStr
.replace(new RegExp('\\n', 'g'), '')
.replace(new RegExp('<p><br></p>', 'g'), '')
.trim();
}
export const sourceConf = {
// 工具栏中的唯一key
key: 'source',
// 组件
factory: () => new SourceMenu()
};
3、页面具体代码
在组件初始化时注册
import { IDomEditor, Boot } from '@wangeditor/editor';
import { Editor, Toolbar } from '@wangeditor/editor-for-react';
import '@wangeditor/editor/dist/css/style.css';
import React, {useEffect, useState} from 'react';
import {IWangEditorProps, parseCodeEditor, parseEditorCode, sourceConf} from "./props";
const WangEditor = ({ value = '', mode = 'default', onChange, toolbarConfig = {}, showCode = false }: any) => {
// 如果组件引用时加了源码模式showCode={true},则添加工具栏
showCode && arr.push('source');
/***********自定义变量-------开始*************/
// editor 实例
const [editor, setEditor] = useState<IDomEditor | null>(null);
// 编辑器内容
const [html, setHtml] = useState<string>(value || '');
/***********自定义变量-------结束*************/
/***********生命周期函数-------开始*************/
// 及时销毁 editor
useEffect(() => {
if (editor?.isFocused()) {
editor?.blur();
}
return () => {
// eslint-disable-next-line eqeqeq
if (editor == null) return;
editor.destroy();
setEditor(null);
};
}, [editor]);
const onCreated = (editor: IDomEditor) => {
/*
* 在组件创建时初始化注册菜单,注意菜单不可以重复注册否则会报异常
* Unhandled Rejection (Error): Duplicated key 'source' in menu items
*/
if (!editor.getAllMenuKeys().includes('source')) {
Boot.registerMenu(sourceConf);
}
setEditor(editor);
// 源码菜单点击监听事件
editor?.on('clickSource', (active) => clickSource(active, editor));
}
/**
* 源码点击
* @param active 菜单是否点击
* @param editor 编辑器内容
*/
const clickSource = (active: boolean, editor: IDomEditor) => {
let value = editor.getHtml();
// 先将编辑器内容清空
editor.clear();
if (active) {
// 将html代码转换为html代码块 dangerouslyInsertHtml是插入html不是重置html,如果使用setHtml也会报节点异常
editor.dangerouslyInsertHtml(parseEditorCode(value));
} else {
// 将html代码块转换为editor的html
editor.dangerouslyInsertHtml(parseCodeEditor(value));
value = parseCodeEditor(value);
}
}
/***********生命周期函数-------结束*************/
return (
<div>
<Toolbar
editor={editor}
defaultConfig={{
...toolbarConfig,
// 添加自定义菜单
insertKeys: {
index: editor?.getAllMenuKeys().length || 0,
keys: arr,
}
}}
mode={mode}
/>
<Editor value={html} onCreated={onCreated} />
</div>
);
};
export default WangEditor;