本教程通俗易懂,主要有点基础的前端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编辑器。
简单代码显示 ,这个是没有整合自定义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格式体现在编辑器编辑状态中,形式上是一段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不带有行为。
下面给出的是Braft Editor自定义Block的数据结构,这个结构对于下面的自定义Block导出导入的理解起到关键作用。注入type是atomic的数组 和下面 emtityMap(实体映射)的对应关系。
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从富文本中分离出来。但是过去的技术探索,已经完成实现上述的编辑。祝大家越来越牛逼。