braft-editor富文本编辑器实用功能开发记录

1,938 阅读3分钟

概览

本文是关于最近使用braft-editor富文本编辑器的一些较为常见、实用的功能开发的记录,分为以下三部分:

  1. "格式刷"的实现
  2. 链接插入自动带上协议
  3. 插入自定义内容的实现

1、“格式刷”的实现

  • 效果展示 屏幕录制.gif

  • 核心代码

  // 定义扩展控件
  const extendControls = [
    {
      key: 'copy-style',
      type: 'component',
      component: (
        <button
          onClick={() => {
            if (!copyStyles) {
              const style = ContentUtils.getSelectionInlineStyle(editorState)
              // 获得样式列表,如下方截图所示
              copyStyles = style._map._list?._tail?.array || null
              console.log(copyStyles)
            } else {
              copyStyles = null
              alert('复制的样式已清除')
            }
          }}
          className='control-item button '
          style={{ color: copyStyles ? '#3498db' : '#6a6f7b' }}
        >
          复制样式
        </button>
      )
    },
    {
      key: 'paste-style',
      type: 'button',
      text: '粘贴样式',
      onClick: () => {
        if (copyStyles) {
          // 样式拷贝到选中区域
          const nextEditorState = copyStyles.reduce((editorState, styleArray) => {
            const style = styleArray[0]
            return ContentUtils.toggleSelectionInlineStyle(editorState, style, '')
          }, editorState)
          setEditorState(nextEditorState)
        } else {
          alert('请先获得样式')
        }
      }
    }
  ]
  • 备注
    • 增加扩展控件,详见官方文档
    • 获得的样式列表实例截图

Xnip2022-06-13_14-55-28.png

2、链接插入自动带上协议

  • 效果展示 4.gif

  • 核心代码

const hooks = {
    'toggle-link': ({ href, target }) => {
        href = href.indexOf('http') === 0 ? href : `https://${href}`
        return { href, target }
    }
}
<BraftEditor hooks={hooks}/>

3. 插入自定义内容的实现

  • 展示效果 3.gif

  • 核心代码

import React, { useState } from 'react'
import 'braft-editor/dist/index.css'
import 'braft-extensions/dist/table.css'
import BraftEditor from 'braft-editor'
import { ContentUtils } from 'braft-utils'
import './index.scss'

// 自定义组件,可加入自定义功能
const BlockColorComponent = (props: any) => {
  const blockData = props.block.getData()
  const text = blockData.get('text') // 获得捕获的插入文本
  // 注意:通过blockRendererFn定义的block,无法在编辑器中直接删除,需要在组件中增加删除按钮
  const removeBarBlock = () => {
    props.blockProps.editor.setValue(
      ContentUtils.removeBlock(props.blockProps.editorState, props.block)
    )
  }
  return (
    <div className='block-color'>
      <i className='height_light_line_icon'></i> {text}
      {/* 删除按钮,悬浮出现,定义输出时去掉 */}
      <button className='button-remove' onClick={removeBarBlock}>
        <i className='bfi-bin'></i>
      </button>
    </div>
  )
}

// 声明blockRendererFn
const blockRendererFn = (block: any, { editor, editorState }: any) => {
  // 对指定类型的捕获
  if (block.getType() === 'block-color') {
    return {
      component: BlockColorComponent, // 自定义组件
      editable: false, // 此处的editable并不代表组件内容实际可编辑,强烈建议设置为false
      props: { editor, editorState } // 传入自定义组件BlockColorComponent的数据,通过props.blockProps获取到
    }
  }
  // 不满足block.getType() === '***'的情况时请勿return任何内容以免造成其他类型的block显示异常
}

// 自定义block输入转换器,用于将符合规则的html内容转换成相应的block,通常与blockExportFn中定义的输出转换规则相对应
const blockImportFn = (nodeName: any, node: any) => {
  if (nodeName === 'div' && node.classList.contains('block-color')) {
    // 捕获标签内部文本
    const text = node.innerText
    return {
      type: 'block-color',
      data: { text } // 把文本传入后续的自定义组件
    }
  }
}

// 自定义block输出转换器,用于将不同的block转换成不同的html内容,通常与blockImportFn中定义的输入转换规则相对应
const blockExportFn = (contentState: any, block: any) => {
  if (block.type === 'block-color') {
    return {
      start: `<div class='block-color'><i class='height_light_line_icon'></i>`,
      end: '</div>'
    }
  }
}

const initContent = `<p><div class='block-color'>自定义高亮内容</div></p>`
export const CourseText = (props: any) => {
  // 使用createEditorState时,需要将上文定义的blockImportFn和blockExportFn作为第二个对象参数的成员传入
  const [editorState, setEditorState] = useState(
    BraftEditor.createEditorState('<p></p>', {
      blockImportFn,
      blockExportFn
    })
  )
  const handleChange = (editorState: any) => {
    setEditorState(editorState)
  }
  const extendControls = [
    {
      key: 'insert-text',
      type: 'button',
      text: '插入高亮内容',
      onClick: () => {
        setEditorState(
          ContentUtils.insertHTML(editorState, initContent, {
            blockImportFn,
            blockExportFn
          })
        )
      }
    }
  ]
  // 在组件中传入上文定义的blockRendererFn
  // 并将blockImportFn和blockExportFn传入组件的converts属性
  return (
    <div>
      <div className='editor-wrapper'>
        <BraftEditor
          id='editor-1'
          value={editorState}
          onChange={handleChange}
          placeholder={'请输入内容'}
          blockRendererFn={blockRendererFn}
          converts={{ blockImportFn, blockExportFn }}
          extendControls={extendControls}
        />
      </div>
    </div>
  )
}
  • 备注
    • 使用blockRendererFn和blockRenderMap两种方式扩展block
    • 使用blockImportFn和blockExportFn定义block的输入和输出转换
    • 详见可参考,官方实例