偷个懒先贴上两个文档
1.block-render主要用来自定义渲染的,一般是通过css,或者更换标签,当然也可以用自定义组件
在处理粘贴进来的内容时,有着映射,粘贴这块我没试过,render的实验在第2篇中最后那个例子中有。
2.block-component 用来做一些复杂的组件比如多媒体组件和一些有交互事件的组件。
这个就不想洗赘述了,贴上一个多媒体组件的,在2中例子基础上加的,添加了
左中右
对齐(但是有点bug,左中右通过替换标签方式实现的,不是叠加样式,所以标题和对齐冲突),还添加了个图片粘贴功能(图片不能和文本一起粘贴,分开粘贴没问题,一起粘贴图片就被忽略了,直接粘贴文件不知道为何出错,如果有人解决了可以告诉我下,拖放文件没测试成功
###components/MyEditor.jsx
import React from 'react';
import { Editor, EditorState, RichUtils, DefaultDraftBlockRenderMap, AtomicBlockUtils, convertToRaw, Entity, } from 'draft-js';
import Immutable from 'immutable';
import "draft-js/dist/Draft.css";
import styles from './Rich.less'
const blockRenderMap = Immutable.Map({
'header-two': {
element: 'h2',
aliasedElements: ['p'],
},
'left': {
element: 'div',
},
'right': {
element: 'div',
},
'center': {
element: 'div',
},
});
class MyEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
showURLInput: false,
url: '',
urlType: '',
};
this.focus = () => this.refs.editor.focus();
this.logState = () => {
const content = this.state.editorState.getCurrentContent();
console.log(convertToRaw(content));
};
this.onURLChange = (e) => this.setState({ urlValue: e.target.value });
this.addAudio = this._addAudio.bind(this);
this.addImage = this._addImage.bind(this);
this.addVideo = this._addVideo.bind(this);
this.confirmMedia = this._confirmMedia.bind(this);
this.pasteMedia = this._pasteImage.bind(this);
this.onChange = (editorState) => this.setState({ editorState });
this.handleKeyCommand = (command) => this._handleKeyCommand(command);
this.onURLInputKeyDown = this._onURLInputKeyDown.bind(this);
this.onTab = (e) => this._onTab(e);
this.toggleBlockType = (type) => this._toggleBlockType(type);
this.toggleInlineStyle = (style) => this._toggleInlineStyle(style);
}
_handleKeyCommand(command) {
const {editorState} = this.state;
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
}
_onTab(e) {
const maxDepth = 4;
this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth));
}
_toggleBlockType(blockType) {
this.onChange(
RichUtils.toggleBlockType(
this.state.editorState,
blockType
)
);
}
_toggleInlineStyle(inlineStyle) {
this.onChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
inlineStyle
)
);
}
_confirmMedia(e) {
e.preventDefault();
const {editorState, urlValue, urlType} = this.state;
const entityKey = Entity.create(urlType, 'IMMUTABLE', { src: urlValue })
this.setState({
editorState: AtomicBlockUtils.insertAtomicBlock(
editorState,
entityKey,
' '
),
showURLInput: false,
urlValue: '',
}, () => {
setTimeout(() => this.focus(), 0);
});
}
_pasteImage(files) {
console.log('files',files);
const {editorState} = this.state;
let _self = this;
for (var i = 0; i < files.length; i++) {
if (files[i].type.indexOf("image") !== -1) {
// We need to represent the image as a file,
var blob = files[i];
let reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
let base64data = reader.result;
const entityKey = Entity.create('image', 'IMMUTABLE', { src: base64data })
_self.setState({
editorState: AtomicBlockUtils.insertAtomicBlock(
editorState,
entityKey,
' '
),
showURLInput: false,
urlValue: '',
}, () => {
setTimeout(() => _self.focus(), 0);
});
}
}
}
}
_onURLInputKeyDown(e) {
if (e.which === 13) {
this._confirmMedia(e);
}
}
_promptForMedia(type) {
const {editorState} = this.state;
this.setState({
showURLInput: true,
urlValue: '',
urlType: type,
}, () => {
setTimeout(() => this.refs.url.focus(), 0);
});
}
_addAudio() {
this._promptForMedia('audio');
}
_addImage() {
this._promptForMedia('image');
}
_addVideo() {
this._promptForMedia('video');
}
render() {
const {editorState} = this.state;
// If the user changes block type before entering any text, we can
// either style the placeholder or hide it. Let's just hide it now.
let className = styles['RichEditor-editor'];
var contentState = editorState.getCurrentContent();
if (!contentState.hasText()) {
if (contentState.getBlockMap().first().getType() !== 'unstyled') {
className += ' ' + styles['RichEditor-hidePlaceholder'];
}
}
let urlInput;
if (this.state.showURLInput) {
urlInput =
;
}
return (
Use the buttons to add audio, image, or video.
Here are some local examples that can be entered as a URL:
- media.mp3
- media.png
- media.mp4
{urlInput}
(console.log('paste', value))}
handlePastedFiles={this.pasteMedia}
handleDroppedFiles={this.pasteMedia}
onChange={this.onChange}
onTab={this.onTab}
placeholder="Tell a story..."
ref='editor'
spellCheck={true}
onPaste={(value) => (console.log('paste', value))}
/>
);
}
}
const styleMap = {
CODE: {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
fontSize: 16,
padding: 2,
},
};
function getBlockStyle(block) {
switch (block.getType()) {
case 'blockquote':
return styles['RichEditor-blockquote'];
case 'left':
return styles['align-left'];
case 'center':
return styles['align-center'];
case 'right':
return styles['align-right'];
default: return null;
}
}
class StyleButton extends React.Component {
constructor() {
super();
this.onToggle = (e) => {
e.preventDefault();
this.props.onToggle(this.props.style);
};
}
render() {
let className = styles['RichEditor-styleButton'];
if (this.props.active) {
className += ' ' + styles['RichEditor-activeButton'];
}
return (
{this.props.label}
);
}
}
const BLOCK_TYPES = [
{ label: 'H1', style: 'header-one' },
{ label: 'H2', style: 'header-two' },
{ label: 'H3', style: 'header-three' },
{ label: 'H4', style: 'header-four' },
{ label: 'H5', style: 'header-five' },
{ label: 'H6', style: 'header-six' },
{ label: 'Blockquote', style: 'blockquote' },
{ label: 'left', style: 'left' },
{ label: 'right', style: 'right' },
{ label: 'center', style: 'center' },
{ label: 'UL', style: 'unordered-list-item' },
{ label: 'OL', style: 'ordered-list-item' },
{ label: 'Code Block', style: 'code-block' },
];
const BlockStyleControls = (props) => {
const {editorState} = props;
const selection = editorState.getSelection();
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType();
return (
{BLOCK_TYPES.map((type) =>
)}
);
};
var INLINE_STYLES = [
{ label: 'Bold', style: 'BOLD' },
{ label: 'Italic', style: 'ITALIC' },
{ label: 'Underline', style: 'UNDERLINE' },
{ label: 'Monospace', style: 'CODE' },
];
const InlineStyleControls = (props) => {
var currentStyle = props.editorState.getCurrentInlineStyle();
return (
{INLINE_STYLES.map(type =>
)}
);
};
function mediaBlockRenderer(block) {
if (block.getType() === 'atomic') {
return {
component: Media,
editable: false,
};
}
return null;
}
const Audio = (props) => {
return ;
};
const Image = (props) => {
return ;
};
const Video = (props) => {
return ;
};
const Media = (props) => {
const entity = Entity.get(props.block.getEntityAt(0));
const {src} = entity.getData();
const type = entity.getType();
let media;
if (type === 'audio') {
media = ;
} else if (type === 'image') {
media = ;
} else if (type === 'video') {
media = ;
}
return media;
};
const styles2 = {
root: {
fontFamily: '\'Georgia\', serif',
padding: 20,
width: 600,
},
buttons: {
marginBottom: 10,
},
urlInputContainer: {
marginBottom: 10,
},
urlInput: {
fontFamily: '\'Georgia\', serif',
marginRight: 10,
padding: 3,
},
editor: {
border: '1px solid #ccc',
cursor: 'text',
minHeight: 80,
padding: 10,
},
button: {
marginTop: 10,
textAlign: 'center',
},
media: {
width: '100%',
},
};
MyEditor.propTypes = {
};
export default MyEditor;
###样式文件 components/Rich.less
.RichEditor-root {
background: #fff;
border: 1px solid #ddd;
font-family: 'Georgia', serif;
font-size: 14px;
padding: 15px;
}
.RichEditor-editor {
border-top: 1px solid #ddd;
cursor: text;
font-size: 16px;
margin-top: 10px;
}
.RichEditor-editor .public-DraftEditorPlaceholder-root,
.RichEditor-editor .public-DraftEditor-content {
margin: 0 -15px -15px;
padding: 15px;
}
.RichEditor-editor .public-DraftEditor-content {
min-height: 100px;
}
.RichEditor-hidePlaceholder .public-DraftEditorPlaceholder-root {
display: none;
}
.RichEditor-editor .RichEditor-blockquote {
border-left: 5px solid #eee;
color: #666;
font-family: 'Hoefler Text', 'Georgia', serif;
font-style: italic;
margin: 16px 0;
padding: 10px 20px;
}
.RichEditor-editor .public-DraftStyleDefault-pre {
background-color: rgba(0, 0, 0, 0.05);
font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
font-size: 16px;
padding: 20px;
}
.RichEditor-controls {
font-family: 'Helvetica', sans-serif;
font-size: 14px;
margin-bottom: 5px;
user-select: none;
}
.RichEditor-styleButton {
color: #999;
cursor: pointer;
margin-right: 16px;
padding: 2px 0;
display: inline-block;
}
.RichEditor-activeButton {
color: #5890ff;
}
.align-right div {
text-align: right;
}
.align-center div {
text-align: center;
}
.align-left div {
text-align: left;
}