React wangEditor5 使用说明
1、支持包安装
yarn add @wangeditor/editor
# 或者 npm install @wangeditor/editor --saveyarn add @wangeditor/editor-for-react
# 或者 npm install @wangeditor/editor-for-react --save
2、使用
import '@wangeditor/editor/dist/css/style.css' // 引入 cssimport { useState, useEffect } from 'react'
import { Editor, Toolbar } from '@wangeditor/editor-for-react'
import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor'type InsertImgType = (url: string, alt: string, href: string) => void;
type InsertVideoType = (url: string, poster?: string) => void;const MyEditor: FunctionComponent = () => {// editor 实例const [editor, setEditor] = useState<IDomEditor | null>(null);// 编辑器内容const [html, setHtml] = useState('<p>hello</p>')// 模拟 ajax 请求,异步设置 htmluseEffect(() => {setTimeout(() => {setHtml('<p>hello world</p>')}, 1500)}, [])// 工具栏配置const toolbarConfig: Partial<IToolbarConfig> = {excludeKeys: ['group-video']};// 编辑器配置const editorConfig: Partial<IEditorConfig> = {placeholder: '请输入内容...',readOnly: false,MENU_CONF: {uploadImage: {// 自定义上传 -- 图片customUpload: (file: File, insertFn: InsertImgType) => {if(file.type.startsWith('image/')) {// file 即选中的文件// 自己实现上传,并得到图片 url alt href// 最后插入图片insertFn(url, alt, href)} else {// 错误提示}}},uploadVideo: {// 自定义上传 -- 视频customUpload: (file: File, insertFn: InsertVideoType) => {// file 即选中的文件// 自己实现上传,并得到视频 url poster// 最后插入视频insertFn(url, poster)}}}}useEffect(() => {// 修改弹窗位置为编译器居中editor?.on('modalOrPanelShow', modalOrPanel => {if (modalOrPanel.type !== 'modal') returnconst { $elem } = modalOrPanel; // modal elementconst width = $elem.width();const height = $elem.height();// set modal position z-index$elem.css({left: '50%',top: '50%',bottom: 'auto', // 需要修改底部间距,不然会受组件自身计算影响marginLeft: `-${width / 2}px`,marginTop: `-${height / 2}px`,zIndex: 1000});});// 及时销毁 editor ,重要!return () => {if (editor == null) returneditor.destroy()setEditor(null)}}, [editor])return (<><div style={{ border: '1px solid #ccc', zIndex: 100}}><Toolbareditor={editor}defaultConfig={toolbarConfig}mode="default"style={{ borderBottom: '1px solid #ccc' }}/><EditordefaultConfig={editorConfig}value={html}onCreated={setEditor}onChange={editor => setHtml(editor.getHtml())}mode="default"style={{ height: '500px', overflowY: 'hidden' }}/></div></>)
}export default MyEditor;
3、自定义菜单
1. 添加自定义菜单弹窗
import { DomEditor, IDomEditor, IModalMenu, SlateNode, SlateTransforms, t } from '@wangeditor/editor';
import { DOMElement } from '@wangeditor/editor/dist/editor/src/utils/dom';
import { genModalButtonElems, genModalInputElems } from './utils';class EditImageSize implements IModalMenu {showModal: boolean;modalWidth: number;title: string;iconSvg?: string;hotkey?: string;alwaysEnable?: boolean;tag: string;width?: number;private $content: DOMElement | null = null;private getSelectedImageNode(editor: IDomEditor): SlateNode | null {return DomEditor.getSelectedNodeByType(editor, 'image')}constructor() {this.title = t('videoModule.editSize');// this.iconSvg = '<svg >...</svg>';this.tag = 'button';this.showModal = true;this.modalWidth = 320;}// 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 falseisActive(): boolean {// 任何时候,都不用激活 menureturn false}// 获取菜单执行时的 value ,用不到则返回空 字符串或 falsegetValue(): string | boolean {// 插入菜单,不需要 valuereturn ''}// 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 falseisDisabled(editor: IDomEditor): boolean {if (editor.selection == null) return trueconst videoNode = this.getSelectedImageNode(editor)if (videoNode == null) {// 选区未处于 image node ,则禁用return true}return false}// 点击菜单时触发的函数exec() {// 点击菜单时,弹出 modal 之前,不需要执行其他代码// 此处空着即可}// 弹出框 modal 的定位:1. 返回某一个 SlateNode; 2. 返回 null (根据当前选区自动定位)getModalPositionNode(editor: IDomEditor): SlateNode | null {return this.getSelectedImageNode(editor);}// 定义 modal 内部的 DOM ElementgetModalContentElem(editor: IDomEditor): DOMElement {const $content = this.$content || document.createElement('div');const [inputWidthContainerElem, inputWidthElem] = genModalInputElems(t('videoModule.width'),`input-width-${Math.random().toString(36).slice(2)}`,'auto');const [inputHeightContainerElem, inputHeightElem] = genModalInputElems(t('videoModule.height'),`input-height-${Math.random().toString(36).slice(2)}`,'auto');const buttonContainerElem = genModalButtonElems(`button-${Math.random().toString(36).slice(2)}`,t('videoModule.ok'));$content.append(inputWidthContainerElem);$content.append(inputHeightContainerElem);$content.append(buttonContainerElem);const imageNode = this.getSelectedImageNode(editor) as unknown as HTMLElement;// 绑定事件(第一次渲染时绑定,不要重复绑定)if (this.$content == null) {buttonContainerElem.onclick = () => {const width = Number(inputWidthElem.value);const height = Number(inputHeightElem.value);console.log(editor, isNaN(width) ? inputWidthElem.value : width ? width +'px' : 'auto', isNaN(height) ? inputHeightElem.value : height ? height +'px' : 'auto')editor.restoreSelection();// 修改尺寸SlateTransforms.setNodes(editor,{style: {width: isNaN(width) ? inputWidthElem.value : width ? width +'px' : 'auto',height: isNaN(height) ? inputHeightElem.value : height ? height +'px' : 'auto',}} as any,{match: n => DomEditor.checkNodeType(n, 'image'),})editor.hidePanelOrModal(); // 隐藏 modal}}if (imageNode == null) return $content;// 初始化 input 值const { width = 'auto', height = 'auto' } = imageNode.style;inputWidthElem.value = width || 'auto';inputHeightElem.value = height || 'auto';setTimeout(() => {inputWidthElem.focus()});return $content // 返回 DOM Element 类型// PS:也可以把 $content 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能}
}export const EditImageSizeConf = {key: 'editImageSize', // 定义 menu key :要保证唯一、不重复(重要)factory() {return new EditImageSize() // 把 `YourMenuClass` 替换为你菜单的 class},
}
公用工具utils
// 生成输入框
export const genModalInputElems = (label: string, id: string, val: string): [HTMLLabelElement, HTMLInputElement] => {const $label = document.createElement('label');$label.className = 'babel-container';const $span = document.createElement('span');$span.textContent = label;const $input = document.createElement('input');$input.type = 'text';$input.id = id;$input.value = val;$label.append($span);$label.append($input);return [$label, $input];
};// 生成按钮
export const genModalButtonElems = (id: string, text: string) => {const $content = document.createElement('div');$content.className = 'button-container';const $button = document.createElement('button');$button.id = id;$button.textContent = text;$content.append($button);return $content;
};
2. 注册自定义菜单
// 注册自定义菜单useEffect(() => {try {Boot.registerMenu(EditImageSizeConf);} catch (e) {}}, [])
3. 挂载到工具栏
// 工具栏配置const toolbarConfig: Partial<IToolbarConfig> = {insertKeys: {index: 5, // 插入的位置,基于当前的 toolbarKeyskeys: ['editImageSize']}}
4. 挂载到组件hover菜单
// 编辑器配置const editorConfig: Partial<IEditorConfig> = {hoverbarKeys: {image: {menuKeys: ['editImageSize'] // 注意:要保留原有的菜单需加上之前的菜单key}}}
相关文章:
React wangEditor5 使用说明
1、支持包安装 yarn add wangeditor/editor # 或者 npm install wangeditor/editor --saveyarn add wangeditor/editor-for-react # 或者 npm install wangeditor/editor-for-react --save2、使用 import wangeditor/editor/dist/css/style.css // 引入 cssimport { useState…...
vue 实现数字验证码功能
需求:写了一个 手机发送验证码后 输入固定验证码的功能 封装成一个组件,如下: <template><div class"conts"><div class"box"><div class"code_list"><div :class"[ code_item, hideIndex 0 ? co…...
【计算机网络】HTTP协议详解(举例解释,超级详细)
文章目录 一、HTTP协议简单介绍 1、1 什么是HTTP协议 1、2 再次理解“协议” 二、HTTP请求 2、1 HTTP的工作过程 2、1、1 demo代码 2、2 URL 介绍 2、2、1 urlencode 和 urldecode 2、3 HTTP 请求格式 三、HTTP响应 3、1 响应demo 3、2 HTTP 响应格式 四、HTTP 请求和响应中的…...
PCB放置过孔技巧
合理的放置过孔能有效的节约面积。 我们根据嘉立创的pcb工艺能力中写出单双面板最小过孔为0.3mm(内径)/0.5mm(外径) 设置过孔尺寸外直径为24mil(0.61mm))内直径为12mil(0.305mm) 嘉立创PCB工艺加工能力范围说明-嘉立…...
淘宝商品详情接口数据采集用于上货,无货源选品上货,采集淘宝天猫商品详情数据
淘宝商品详情接口数据采集可用于上货。先通过关键字搜索接口,抓取到批量的商品ID,再将商品ID传入商品详情数据采集接口的请求参数中,从而达到批量抓取商品详情数据的功能。 接口名称:item_get,获取商品详情数据&#…...
DoS和DDos攻攻击
介绍 DDoS 和 DoS 攻击是我们最常见的网络攻击之一,而且历史相当悠久,算是很经典的两种攻击方式,但它们实际上是如何运作的呢? 虽然两者基本上都能够让工作停摆,但其中有很大的差异,接下来我们将逐一说明&a…...
Python实时采集Windows CPU\MEMORY\HDD使用率
文章目录 安装psutil库在Python脚本中导入psutil库获取CPU当前使用率,并打印结果获取内存当前使用率,并打印结果获取磁盘当前使用情况,并打印结果推荐阅读 要通过Python实时采集Windows性能计数器的数据,你可以使用psutil库。psut…...
【改造中序遍历算法】1038. 从二叉搜索树到更大和树
1038. 从二叉搜索树到更大和树 解题思路 改造中序遍历算法先遍历右子树 然后累加当前节点的值 再遍历左子树 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode…...
克服网络安全压力:如何掌控无限的云数据
管理云中的数字风险比以往任何时候都更加重要。数字化转型引发的云数据呈指数级增长,为安全分析师创造了一个更大的威胁环境。随着威胁行为者继续危害组织最敏感的数据,这一挑战将会加剧。 预计未来五年全球网络犯罪成本将激增,从 2022 年的…...
【数据结构和算法】--N叉树中,返回某些目标节点到根节点的所有路径
目录 一、前言二、具体实现及拓展2.1、递归-目标节点到根节点的路径数据2.2、list转换为tree结构2.3、tree转换为list结构 一、前言 这么多年工作经历中,“数据结构和算法”真的是超重要,工作中很多业务都能抽象成某种数据结构问题。下面是项目中遇到的…...
进程和线程的区别 线程之间共享的资源
线程和进程都是操作系统中的执行单位,但它们在以下几个方面存在区别: 相同处: 1.执行环境:线程和进程都有自己的执行上下文,包括程序计数器、寄存器和栈,可以独立执行指令。 2.并发性:线程和进…...
基于Matlab实现logistic方法(源码+数据)
Logistic回归是一种常用的分类算法,适用于二分类问题。本文将介绍如何使用Matlab实现Logistic回归方法,并通过一个示例演示其应用。 文章目录 引言实现步骤1. 数据准备2. 特征缩放3. 模型训练4. 模型评估 源码数据下载 引言 Logistic回归是一种广泛应用…...
leetCode 121. 买卖股票的最佳时机 贪心算法
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。…...
《Oracle系列》Oracle 索引使用情况查看
查询用户的索引 select index_name,table_name,tablespace_name,index_type,uniqueness,statusfrom dba_indexeswhere owner <用户名>;查询用户的索引列 select index_name,table_name,column_name,index_owner,table_ownerfrom dba_ind_columnswhere table_owner &l…...
解决Invalid bound statement (not found)错误~
报错如下所示: 找了好久,刚开始以为是名称哪里写的有问题,但仔细检查了好多遍都不是 最后发现了问题如下所示: UserMapper里面的内容被我修改了,但classes中的内容还是原来的内容,所以才导致了编译器报错n…...
基于SpringBoot的反诈宣传平台设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…...
【改进哈里鹰算法(NCHHO)】使用混沌和非线性控制参数来提高哈里鹰算法的优化性能,解决车联网相关的路由问题(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
【C语言】宏定义
🚩 WRITE IN FRONT🚩 🔎 介绍:"謓泽"正在路上朝着"攻城狮"方向"前进四"🔎🏅 荣誉:2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评百大博…...
库存三层模型概述
库存分层 (1)电商库存体系分为三层:销售层、调度层、仓库层。 库存三层模型:销售库存,调度层属于订单领域-履约。实物库存属于库存领域 WMS的库存跟调度层是一致的。 但是销售库存跟调度层可能不一致,因为…...
SNERT预备队招新CTF体验赛-Web(SWCTF)
目录 1、F12 2、robots 3、game1-喂青蛙 4、game 2 - flap bird 5、game 3 - Clash 6、Get&Post 7、sql (1)手工注入 (2)工具注入 8、命令执行漏洞 9、文件上传漏洞 10、文件泄露 11、php反序列化漏洞 12、PHP绕…...
告别本地卡顿!用Pycharm 2023.3远程连接Spark集群,5步搞定开发环境
告别本地卡顿!用Pycharm 2023.3远程连接Spark集群,5步搞定开发环境 当你的笔记本风扇开始像喷气发动机一样轰鸣,而PySpark脚本才处理到第3万条数据时,就该考虑换个战场了。去年我用一台16GB内存的MacBook Pro分析800万条电商日志&…...
C#上位机开发入门:手把手教你用PowerPMAC SDK实现第一个通讯Demo
C#上位机开发入门:从零构建PowerPMAC通讯Demo的实战指南 引言 当你第一次打开PowerPMAC开发套件时,面对密密麻麻的库文件和数百页的技术手册,是否感到无从下手?作为工业自动化领域的核心控制器,PowerPMAC与上位机的通讯…...
如何在无GPU群晖设备上开启完整AI相册功能:Synology Photos面部识别终极指南
如何在无GPU群晖设备上开启完整AI相册功能:Synology Photos面部识别终极指南 【免费下载链接】Synology_Photos_Face_Patch Synology Photos Facial Recognition Patch 项目地址: https://gitcode.com/gh_mirrors/sy/Synology_Photos_Face_Patch 还在为DS918…...
sndcpy音频转发工具:Android设备音频镜像的完整指南
sndcpy音频转发工具:Android设备音频镜像的完整指南 【免费下载链接】sndcpy Android audio forwarding PoC (scrcpy, but for audio) 项目地址: https://gitcode.com/gh_mirrors/sn/sndcpy 想要在电脑上实时收听Android设备的音频内容吗?sndcpy音…...
Bebas Neue字体技术深度解析:开源无衬线显示字体的现代排版解决方案
Bebas Neue字体技术深度解析:开源无衬线显示字体的现代排版解决方案 【免费下载链接】Bebas-Neue Bebas Neue font 项目地址: https://gitcode.com/gh_mirrors/be/Bebas-Neue Bebas Neue作为一款采用SIL Open Font License 1.1许可证的开源显示字体ÿ…...
终极免费跨平台方案:3步将知网CAJ论文转换为可编辑PDF的完整指南
终极免费跨平台方案:3步将知网CAJ论文转换为可编辑PDF的完整指南 【免费下载链接】caj2pdf Convert CAJ (China Academic Journals) files to PDF. 转换中国知网 CAJ 格式文献为 PDF。佛系转换,成功与否,皆是玄学。 项目地址: https://gitc…...
当FanControl风扇集体“罢工“:从系统诊断到完美修复的技术探险
当FanControl风扇集体"罢工":从系统诊断到完美修复的技术探险 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/G…...
二分查找算法:选择开区间还是闭区间?
如大家所熟悉的,在二分查找算法的实现过程中,通常会选择左闭右开区间 [st, ed) 或是全闭区间 [st, ed] 这两种搜索区间的表示方式。左闭右开区间比较符合大家的编程习惯,而全闭区间在解决某些问题上更加方便。首先看一下不同区间的选择对 主循…...
如何用Rusted PackFile Manager彻底重构全面战争模组开发工作流?
如何用Rusted PackFile Manager彻底重构全面战争模组开发工作流? 【免费下载链接】rpfm Rusted PackFile Manager (RPFM) is a... reimplementation in Rust and Qt6 of PackFile Manager (PFM), one of the best modding tools for Total War Games. 项目地址: h…...
Proteus仿真新手必看:从电容单位到LCD1602,这份常用元器件清单帮你快速上手
Proteus仿真实战指南:从零搭建你的第一个电子电路 刚接触Proteus的电子爱好者们,面对软件里密密麻麻的元器件库,是不是有种"大海捞针"的感觉?别担心,这份指南将带你快速锁定核心元器件,用最直接的…...
