React wangEditor5 使用说明

1、支持包安装

yarn add @wangeditor/editor
# 或者 npm install @wangeditor/editor --save

yarn add @wangeditor/editor-for-react
# 或者 npm install @wangeditor/editor-for-react --save

2、使用

import '@wangeditor/editor/dist/css/style.css' // 引入 css

import { 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('

hello

'
) // 模拟 ajax 请求,异步设置 html useEffect(() => { setTimeout(() => { setHtml('

hello world

'
) }, 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') return const { $elem } = modalOrPanel; // modal element const 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) return editor.destroy() setEditor(null) } }, [editor]) return ( <> <div style={{ border: '1px solid #ccc', zIndex: 100}}> <Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default" style={{ borderBottom: '1px solid #ccc' }} /> <Editor defaultConfig={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 = '...';
    this.tag = 'button';
    this.showModal = true;
    this.modalWidth = 320;
  }

  // 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
  isActive(): boolean {
    // 任何时候,都不用激活 menu
    return false
  }

  // 获取菜单执行时的 value ,用不到则返回空 字符串或 false
  getValue(): string | boolean {
    // 插入菜单,不需要 value
    return ''
  }

  // 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
  isDisabled(editor: IDomEditor): boolean {
    if (editor.selection == null) return true

    const 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 Element
  getModalContentElem(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, // 插入的位置,基于当前的 toolbarKeys
      keys: ['editImageSize']
    }
  }

4. 挂载到组件hover菜单

  // 编辑器配置
  const editorConfig: Partial<IEditorConfig> = {
    hoverbarKeys: {
      image: {
        menuKeys: ['editImageSize']  // 注意:要保留原有的菜单需加上之前的菜单key
      }
    }
  }

你可能感兴趣的:(react.js,okhttp,前端)