我还没有整理,但是应该能帮助到你
* 包含一些文章
* 包含自己的总结
* 包含一些自己的实例,自定义块,自定义行内,删除元素等
分割线
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
-
富文本解析
https://github.com/jpuri/draftjs-to-html -
小程序渲染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();