项目实践,一文秒懂Braft Editor编辑器扩展自定义Block

本教程通俗易懂,主要有点基础的前端react开发人员都能看得懂,如果看不懂,请自行反思。为什么要写这个遍文章,因为braft-editor的官方在扩展自定义Block的实现细节和文档提示比较缺乏,而且在Braft Editor编辑器基础上扩展自定义Block需要借助draft-js的功能。个人通过项目实践实现Braft Editor编辑器扩展自定义Block,下面给出具体的实现过程和关键代码。

1,选择Braft Editor的理由

braft-editor是一个美观易用的React富文本编辑器。这个编辑器开箱即用,不用重复造轮子。这个编辑器是基于facebook的draft-js开发的,draft-js可以抽象理解成一个webpack,是要做各种配置才能使用,像我这样智商有限,英语水平有限并且没有梦想的程序员,还是乖乖的使用 braft-editor。 下图展示的是Braft Editor编辑器。

Braft Editor编辑器.png

简单代码显示 ,这个是没有整合自定义Block的代码。

import React, { Component } from 'react';
import { ContentUtils } from 'braft-utils'#工具包,会用到
class Editor extends Component {
  constructor(props) {
    super(props);
    this.state={
      editorState:BraftEditor.createEditorState(null)
    }
  }
  render() {
      return (
      
      );
  }
  }

2,Braft Editor一般的数据结构

比如我在编辑其中输入"hello word"然后,然后通过 下面的方法得到raw格式的数据结构,

const rawJSON = this.state.editorState.toRAW(true) //返回JSON对象 
const rawJSON = this.state.editorState.toRAW(false) // 返回JSON 字符串

数据结构如下图片所示:


raw格式.png

raw格式体现在编辑器编辑状态中,形式上是一段JSON可以转换成字符串形式存储。也可以通过下面的方法到html字符串的数据格式,

const htmlString = this.state.editorState.toHTML()

Html字符串大多数用在展现中。所以,为防止反复编辑中格式丢失,比较通用的做法是同时存储raw字符串和html字符串,前者用于编辑场景,后者用于单纯内容展示。但是this.state.editorState.toHTML() 不能自动将扩展自定义Block的转换成Html,后文会继续介绍。

3,Braft Editor自定义Block的数据结构

这个Block可以这样理解,在编辑中这个Block是一个带有行为的React组件,如果是单纯显示,这个Block是Html字符串,至于是否需要带有行为,这个需要看具体项目而定,本次教程展示的Block没包含行为。如下图所示 ,在编辑中显示优惠券Block是带有一个删除的行为。删除行为是在鼠标移动到Block触发。

优惠券组件Block.png

展示的Block不带有行为。

优惠券展示Block.png

下面给出的是Braft Editor自定义Block的数据结构,这个结构对于下面的自定义Block导出导入的理解起到关键作用。注入type是atomic的数组 和下面 emtityMap(实体映射)的对应关系。


优惠券block的数据结构.png

3,Braft Editor自定义Block的插入和导入

本文展示的Block是一个自定义优惠券Block,这个Block有两个参数 ,分布是优惠券的显示文本text和优惠券的链接地址link。大致的流程是:编辑器放有一个插入优惠券的按钮,让用户输入插入优惠券的text和link,然后编辑器渲染优惠券Block。下面的这个动作是在插入阶段,最终会修改Editor 组件的state ,插入优惠券的关键代码如下

let discount_text = "优惠券"
let  link_address = "优惠券地址"
this.setState({
    editorState: ContentUtils.insertAtomicBlock (this.state.editorState,'discount_coupon_render',true,{
    discount_text: discount_text,
    discount_address: link_address
  })

上文ContentUtils.insertAtomicBlock会触发下面Block的导入函数,导入函数作用是渲染新插入的自定义Block到编辑器,并且根据实体的类型找对应的React组件。

// 定义一个新的block类型:discount_coupon_render
const  blockRenderFn = (contentBlock, { editor, editorState }) => {

  if (contentBlock.getType() === 'atomic'  ) {
    const  entity = editorState.getCurrentContent().getEntity(contentBlock.getEntityAt(0))
    if(entity.getType() ===  "discount_coupon_render"){
      return {
        component: DiscountBlockComponent,
        editable: false, // editable并不代表组件内容实际可编辑,强烈建议设置为false
        props: { editor, editorState } // 传入的内容可以在组件中通过this.props.blockProps获取到
      }
    }
  }
  }

正如上文所说,扩展自定义Block在Braft Editor编辑器是一个React组件,下面最关键就是DiscountBlockComponent组件的处理,请注意DiscountBlockComponent组件删除行为 ,代码中blockData 就是插入阶段中传入的discount_text和discount_link,这个过程就完成传参了。代码如下。

class DiscountBlockComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      showDelButton: false
    }
  }
  onShowDelButton = ()=>{
    this.setState({
      showDelButton: true
    })
  }

  onHideDelButton = ()=>{
    this.setState({
      showDelButton: false
    })
  }
  // 注意:通过blockRendererFn定义的block,无法在编辑器中直接删除,需要在组件中增加删除按钮
  removeBarBlock = () => {
    this.props.blockProps.editor.setValue(ContentUtils.removeBlock(this.props.blockProps.editorState, this.props.block))
  }

  render () {
    const  blockData  = this.props.contentState.getEntity(this.props.block.getEntityAt(0)).getData()
    // console.log("blockData",blockData )
    return (
      
this.onHideDelButton()} onMouseEnter={() =>this.onShowDelButton()} >
{blockData.discount_text}

点击

领券

{ this.state.showDelButton ?
this.removeBarBlock()} > 删除
:null }
) } }

4,Braft Editor自定义Block的导出

自定义Block导出的是Html字符串, 下面函数主要是输入优惠券自定义Block的html字符串。 传入两个变量优惠券地址 链接address和优惠券显示文本text。

const discountBlockHtml = (address,text) =>{
   return ` `

}

下面自定义block的html导出函数,用于将不同的block转换成不同的html内容。下面函数blockExportFn 在开发者调用this.state.editorState.toHTML() 会触发。

const blockExportFn = (contentState, block) => {
if (block.type === 'atomic') {
 let  ranges  = block.entityRanges.length  >  0 ? block.entityRanges[0] : -1;
 if(ranges  !== -1 ){
   let   entity = contentState.getEntity(contentState.getBlockForKey(block.key).getEntityAt(0))
   // console.log(contentState.getLastCreatedEntityKey())
   if(entity.getType() === "discount_coupon_render"){
     let  blockData = entity.getData()
     return  discountBlockHtml(blockData.discount_address,blockData.discount_text)
   }
 }
}
// 导入空格
if(block.type === "unstyled" &&  !block.text.length){
  return `


` } }

5,总结

Braft Editor编辑器还提供了一个blockImportFn 函数,这个函数主要是将符合规则的html内容转换成相应的block,也就是html字符串转变raw的数据格式。在项目中没有用到,实际上不需要用到的,因为raw格式的数据直接转成html字符串,不需要符合规则的html内容转换成相应的block。

// 自定义 html 转block的输入转换器,用于将符合规则的html内容转换成相应的block
const blockImportFn = (nodeName, node) => {
    
}

最后代码总结如下,解决了Braft Editor编辑器扩展自定义Block的问题。

import React, { Component } from 'react';
import { ContentUtils } from 'braft-utils'#工具包,会用到

#定义上文提到的 blockRenderFn  blockImportFn 
#blockExportFn  discountBlockHtml  (见上文)
#定义 DiscountBlockComponent  组件 (见上文)  
#定义
class Editor extends Component {
  constructor(props) {
    super(props);
    this.state={
editorState:BraftEditor.createEditorState(null,
  { blockImportFn ,blockExportFn}), // 设置编辑器初始内容
    }
  }
  render() {
      return (
      
      );
  }
  }

后面应为实现多端数据显示,整个优惠券block与整个富文本是耦合的,无法方便实现在多个平台展示,所有后面改版直接把优惠券block从富文本中分离出来。但是过去的技术探索,已经完成实现上述的编辑。祝大家越来越牛逼。

你可能感兴趣的:(项目实践,一文秒懂Braft Editor编辑器扩展自定义Block)