基于draft.js 和 braft-editor.js文档,自己总结的内容

我还没有整理,但是应该能帮助到你
* 包含一些文章
* 包含自己的总结
* 包含一些自己的实例,自定义块,自定义行内,删除元素等


分割线


1.一篇文档足矣(无数个文章综合体)
https://www.jianshu.com/p/f8a5c5492f57

2.draft.js–富文本编辑器框架的实践(一)
概念讲的还不错

https://blog.csdn.net/oak160/article/details/78130971

3.基于Draft.js自定义富文本编辑器
这篇文章带有git仓库,完全可以下载下来,然后看代码参考,功能还是很齐全的

https://www.jianshu.com/p/6a01f1f71fd2

4.这一篇新发现的也不错
https://github.com/dreamFlyingCat/draft.js/blob/master/README.md

  1. 富文本解析
    https://github.com/jpuri/draftjs-to-html

  2. 小程序渲染html组件

https://jin-yufeng.github.io/Parser/#/instructions?id=%e6%b7%bb%e5%8a%a0%e4%b8%80%e4%b8%aa%e8%87%aa%e5%ae%9a%e4%b9%89%e6%a0%87%e7%ad%be



这是一个分割线

draft还是很强大的,可操作性非常强,可以随心所欲的做自己想做的东西

富文本分为2大块

1.富文本的头,就是按钮的意思,是独立布局的。展示方面和富文本吴志杰关系,但是操作时需要按照相关规则来;

2.富文本的内容,就是按照富文本的格式展示的内容,具体又分为如下几点

主要分为行内元素和块元素
行内元素又分为:自带的行内元素和自定义行内元素
比如:加粗,倾斜等这些是自带的行内元素,直接调用即可,自定义的行内元素就多了,比如字体颜色,背景颜色等等就属于自定义行内元素;

块元素也分为自带块元素和自定义块元素
自带的块元素:比如大标题H1一直到H6等等;

自定义的块元素:就太多了,除了官方自带的都属于自定义的块元素,比如图片,视频等等;这个也是最难得

总体思想差不多就这些,更加细节的和操作方法,可以参考上面的文章

咱们现在用的是braft-editor.js 推荐看下面文章

原因是

1.因为大家都是基于draft.js封装的,所以数据结构是一样的,这就是最基础的原因。

2.因为以前基于draft.js写了一些功能,但是这次突然增加了不少功能,如果自己写,时间也需要很多,这个braft-editor正好有不少用的上的

3.braft-editor文档还是不错的,扩展性也高,就是自定义行自定义块这方面文章欠缺,只要能突破这些就好了。

推荐文章
https://braft.margox.cn/demos/basic
https://blog.csdn.net/zuggs_/article/details/80747438
https://www.yuque.com/braft-editor/be/lzwpnr
http://47.99.63.1:8090/pages/viewpage.action?pageId=2327775

–自定义块

https://www.jianshu.com/p/160610edd056

https://braft.margox.cn/demos/block

–自定义行内

请参考 下面的实操

目前发现自定义块,原则上是可以随意操作,自定义行,还是有些欠缺(可能是有些方法技巧没有找到)。

以下为实操:

增加一个 自定义行内元素
可以参考:https://braft.margox.cn/demos/entity

braft-editor 有一个 BraftEditor.use功能,目前添加自定义行内是通过它来完成的。

以艾特@举例

主页面:

import { WxAT} from "./inlineComponent"; //

BraftEditor.use([
  WxAT({
    includeEditors: ['djq-editor-id'],
  }),
  
])

然后在创建的时候

this.state = {
      editorState: BraftEditor.createEditorState(
        null,
        {
          blockImportFn: this.blockImportFn,
          blockExportFn: this.blockExportFn,
          editorId: 'djq-editor-id'
        },
      ),// 设置编辑器初始内容
    };

这样主页面的准备工作已经完成了。接下来是主要的了,就是功能页面WxAT

import * as React from 'react';
import { ContentUtils } from "braft-utils";
import WxATModel from "./wxATModel";


// 编写扩展模块
const _drawTxATEditor = (editor, editorState, value) => {
  // editor.setValue(
  //   ContentUtils.insertText(editorState, `@${value}`, null, {
  //     type: "AT",
  //     mutability: "IMMUTABLE",
  //     data: { nick_name: value }
  //   })
  // );
  editor.onChange(ContentUtils.insertText(editorState, `@${value}`, null, {
    type: "AT",
    mutability: "IMMUTABLE",
    data: { nick_name: value }
  }));
};

let controlRef = null
const bindControlRef = (ref) => controlRef = ref

export default options => {
  let TxATRef = null;
  options = {
    closeOnSelect: false,
    closeOnBlur: false,
    ...options
  };

  const { includeEditors } = options;
  return {
    // 指定扩展类型
    type: "entity",
    // 指定该扩展对哪些编辑器生效,不指定includeEditors则对所有编辑器生效
    includeEditors: includeEditors || ['djq-editor-id'],
    // 指定扩展样式名,推荐使用全大写
    name: "AT",
    // 在编辑器工具栏中增加一个样式控制按钮,text可以为一个react组件
    // control: {
    //   text: '按键'
    // },
    control: (props) => {
      return {
        key: "wxAT",
        type: "component",
        ref: bindControlRef,
        component: (
          <span onMouseDown={(e) => { e.stopPropagation() }}>
            <button
              className="control-item button"
              data-title="艾特"
              onClick={
                (e) => {
                  e.stopPropagation()
                  TxATRef()
                }
              }
            >
              艾特@
            </button>
            <WxATModel
              TxATRef={ref => (TxATRef = ref)}
              drawTxATEditor={(value) => {
                _drawTxATEditor(props.editor, props.editorState, value)
              }}
            />
          </span>
        )
      }
    },
    // control: props => ({
    //   text: "艾特",
    //   key: "wxAT",
    //   // replace: '测试',
    //   type: "modal",
    //   title: "自定义的组件",
    //   ref: null,
    //   onClick: () => {}, // 指定触发按钮点击后的回调函数
    //   modal: {
    //     id: "my-modal1", // 必选属性,传入一个唯一字符串即可
    //     title: "我的弹窗", // 指定弹窗组件的顶部标题
    //     className: "my-modal", // 指定弹窗组件样式名
    //     width: 300, // 指定弹窗组件的宽度
    //     height: 250, // 指定弹窗组件的高度
    //     showFooter: true, // 指定是否显示弹窗组件底栏
    //     showCancel: true, // 指定是否显示取消按钮
    //     showConfirm: true, // 指定是否显示确认按钮
    //     confirmable: true, // 指定确认按钮是否可用
    //     showClose: true, // 指定是否显示右上角关闭按钮
    //     closeOnBlur: false, // 指定是否在点击蒙层后关闭弹窗(v2.1.24)
    //     closeOnConfirm: true, // 指定是否在点击确认按钮后关闭弹窗(v2.1.26)
    //     closeOnCancel: true, // 指定是否在点击取消按钮后关闭弹窗(v2.1.26)
    //     cancelText: "取消", // 指定取消按钮文字
    //     confirmText: "确定", // 指定确认按钮文字
    //     bottomText: null, // 指定弹窗组件底栏左侧的文字,可传入jsx
    //     onConfirm: () => {
    //       _drawTxATEditor(props.editor, props.editorState,'11111');
    //     }, // 指定点击确认按钮后的回调函数
    //     onCancel: () => {}, // 指定点击取消按钮后的回调函数
    //     onClose: () => {}, // 指定弹窗被关闭后的回调函数
    //     onBlur: () => {}, // 指定蒙层被点击时的回调函数
    //     // children: <span>
    //     // <input value={value} onChange={_onChange}/>
    //     // </span>, // 指定弹窗组件的内容组件
    //     children: <WxATmodel TxATRef={ref => (TxATRef = ref)} /> // 指定弹窗组件的内容组件
    //   }
    // }),
    // 指定该扩展样式的CSS规则,请注意,IE/EDGE浏览器暂时不支持textEmphasis
    style: {
      // textEmphasis: 'circle',
      // textEmphasisPosition: 'under',
      // WebkitTextEmphasis: 'circle',
      // WebkitTextEmphasisPosition: 'under'
    },
    // 指定entity的mutability属性,可选值为MUTABLE和IMMUTABLE,表明该entity是否可编辑,默认为MUTABLE
    mutability: "IMMUTABLE",
    // 指定通过上面新增的按钮创建entity时的默认附加数据
    // data: {
    //   text: '111'
    // },
    // 指定entity在编辑器中的渲染组件
    component: props => {
      // 通过entityKey获取entity实例,关于entity实例请参考https://github.com/facebook/draft-js/blob/master/src/model/entity/DraftEntityInstance.js
      const entity = props.contentState.getEntity(props.entityKey);
      // 通过entity.getData()获取该entity的附加数据
      return (
        <span>
          <a className="keyboard-item keyboard-item-wxat">
            {props.children}
          </a>
        </span>
      );
    }
    // importer: (nodeName, node) => {
    //   // 指定html转换为editorState时,何种规则的内容将会附加上该扩展样式
    //   // 如果编辑器在createEditorState时使用的是RAW数据,并且开启了stripPastedStyles,则可以不指定importer,因为不存在html转editorState的场景
    //   return nodeName === 'span' && [].find.call(node.style, (styleName) => styleName.indexOf('text-emphasis') !== -1)
    // },
    // exporter: () => {
    //   // 指定该样式在输出的html中如何呈现,对于inline-style类型的扩展可以不指定exporter,输出样式即为该扩展的style
    //   return (
    //     <span style={{
    //       textEmphasis: 'circle',
    //       textEmphasisPosition: 'under',
    //       WebkitTextEmphasis: 'circle',
    //       WebkitTextEmphasisPosition: 'under'
    //     }} />
    //   )
    // }
  };
};

wxATModel 页面

import * as React from 'react';
import { Modal, message, Input, Row, Col } from 'antd';
import { getLength } from "djq-approach/lib/base";
interface Props {
  TxATRef?: Function, //ref 父组件调用子组件
  drawTxATEditor?: Function, // 子组件 调用父组件
}


interface State {
  nick_name?: string,
  ATVisible?: boolean
}

export default class extends React.Component<Props, State> {
  state = {
    nick_name: '', //艾特的人
    ATVisible:false,
  };
  constructor(props) {
    super(props);
    props.TxATRef && props.TxATRef(this._showATVisible)
  }
  // 父组件 调用子组件的地方
  _showATVisible = () => {
    this._changeATVisible(true)
  }
  getValue = () => {
    return this.state.nick_name;
  }

  _changeATVisible = (ATVisible) =>{
    if(ATVisible){
      this.setState({
        ATVisible
      })
    }else{
      this.setState({
        ATVisible,
        nick_name:"",
      })
    }
    
  }

  _changeNickName = (e) => {
    let value = e.target.value ? e.target.value.trim() : "";
    this.setState({
      nick_name: value,
    })
  }

  _ATOk = () =>{
    let {nick_name} = this.state;
    if(getLength(nick_name)){
      this.props.drawTxATEditor(nick_name);
      this.setState({
        nick_name:"",
        ATVisible:false,
      })
    }else{
      message.error("昵称不能为空")
    }
    
  }

  render() {
    return this._renderModel()
  }

  _renderModel = () => {
    return <Modal
    title="添加艾特"
    visible={this.state.ATVisible}
    onOk={this._ATOk}
    onCancel={() => { this._changeATVisible(false) }}
    okText={'确认'}
    cancelText={'取消'}
  >
    <Row align="middle" justify="center" type="flex">
      <Col span={5}>艾特名称:</Col>
      <Col span={18} >
        <Input value={this.state.nick_name} placeholder="请输入昵称" onChange={this._changeNickName} />
      </Col>
    </Row>
  </Modal>

  }

}

以上就是全部内容

事例:

以删除图片为例。

方法1:

1.先获取当前元素的block的key;

2.然后把当前数据转换成能够看得懂得raw;

3.在循环block,删除block和对应的 entityMap;

4.重新赋值,并且把raw转换成富文本的格式

_deleteImage = (e) => {
    e.stopPropagation();
    const { contentState, block, blockProps } = this.props;
    
    const key = block.getKey(); //当前选中block的key值
    let obj = convertToRaw(contentState);  //等于 blockProps.editorState.toRAW(true)
    obj.blocks.forEach((item, i) => {
      if (item.key === key) {
        let entityMapKey = item.entityRanges[0].key;
        delete obj.entityMap[entityMapKey];
        obj.blocks.splice(i, 1);
      }
    });
    // BraftEditor.createEditorState   可以将raw或者html格式的数据转换成editorState数据
    blockProps.editor.onChange(BraftEditor.createEditorState(obj));
  };

方法2:

this.props.blockProps.editor.onChange(ContentUtils.removeBlock(this.props.blockProps.editorState, this.props.block))

区别是 方法一删除的彻底,方法二会留下一个空白数据

事例2:当修改富文本里的东西,有输入框时,

这个时候要适时 readOnly 富文本,防止输入出问题

事例:比如标题叠加在一起,我们要写一个内部已经有的大标题块 设置 Block
// 设置 大标题
  _insertHeaderOne = () =>{
    // editorState  直接设置 自由块
    this.setState({
      editorState: RichUtils.toggleBlockType(this.state.editorState,"header-one")
    });
  }
  //toggleInlineStyle
设置 行内内联
_insertEntity = (type,obj?:CustomizeEntity) =>{
    // let entityArrays = ['WXLINK','TXVIDEO','DJQIMAGE','AT','TAG','HR','EMOTICOM'];
    const entityArrays = ['AT','EMOTICOM'];
    const editorState = this.state.editorState;
    const {src,nick_name,size,width,height,upload_id,file_name,} = obj;
    if(entityArrays.includes(type)){
      // 暂无实现
      if(type === 'EMOTICOM'){
        this._handleChange(
          ContentUtils.insertText(editorState, ' ', null, {
            type: 'EMOTICON',
            mutability: 'IMMUTABLE',
            data: { src }
          })
        )
      }else if(type === 'AT'){
        this._handleChange(
          ContentUtils.insertText(this.state.editorState, `@${nick_name}`, null, {
            type: "AT",
            mutability: "IMMUTABLE",
            data: { nick_name}
          })
        )
      }else if(type === 'DJQIMAGE'){
        this._handleChange(
          ContentUtils.insertAtomicBlock(this.state.editorState, "DJQIMAGE", "IMMUTABLE", {
            src,
            size,
            width,
            height,
            upload_id,
            file_name,
            description: "",
            zoom:"big"
          })
        )
      }
    }else{
      console.log('无此参数')
    }
  }
失焦,聚焦
this.editorInstanceRef.draftInstance.blur();
this.editorInstanceRef.draftInstance.focus();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值