Skip to content

Commit 8f99f68

Browse files
author
Benjamin Kniffler
committed
Refresh docs (complex now, will try and outsource as much as possible to plugins)
1 parent cfdfd88 commit 8f99f68

File tree

7 files changed

+285
-45
lines changed

7 files changed

+285
-45
lines changed
Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,47 @@
11
import React, { Component } from 'react';
2-
import { EditorState } from 'draft-js';
2+
import { EditorState, Entity } from 'draft-js';
33
import Editor from 'draft-js-plugins-editor';
4-
import createUploadPlugin from 'draft-js-dnd-plugin';
4+
import createDndPlugin from 'draft-js-dnd-plugin';
55
import styles from './styles.css';
66
import mockUpload from '../utils/mockUpload';
7-
import DndWrapper from 'draft-js-dnd-plugin/components/dnd-wrapper';
7+
import addBlock from 'draft-js-dnd-plugin/modifiers/addBlock';
88

9-
const uploadPlugin = createUploadPlugin({
10-
upload: (data, success, failed, progress) =>
11-
mockUpload(data, success, failed, progress),
9+
import PreviewGithub from '../components/preview-github';
10+
import BlockImage from '../components/block-image';
11+
import BlockText from '../components/block-text';
12+
import cleanupEmpty from '../utils/cleanupEmpty';
1213

13-
// This would be a real file upload to server
14+
const dndPlugin = createDndPlugin({
15+
allowDrop: true,
16+
handleUpload: (data, success, failed, progress) =>
17+
mockUpload(data, success, failed, progress),
18+
handlePreview: (state, selection, data) => {
19+
const { type } = data;
20+
if (type.indexOf('image/') === 0) {
21+
return addBlock(state, state.getSelection(), 'block-image', data);
22+
} else if (type.indexOf('text/') === 0 || type === 'application/json') {
23+
return addBlock(state, state.getSelection(), 'preview-github', data);
24+
} return state;
25+
}, handleBlock: (state, selection, data) => {
26+
const { type } = data;
27+
if (type.indexOf('image/') === 0) {
28+
return addBlock(state, state.getSelection(), 'block-image', data);
29+
} else if (type.indexOf('text/') === 0 || type === 'application/json') {
30+
return addBlock(state, state.getSelection(), 'block-text', data);
31+
} return state;
32+
}, // This would be a real file upload to server
1433
/* superagent.post('/upload')
15-
.accept('application/json')
16-
.send(data.formData)
17-
.on('progress', ({ percent }) => {
18-
progress(percent);
19-
})
20-
.end((err, res) => {
21-
if (err) {
22-
return failed(err);
23-
}
24-
success(res.body.files, 'image');
25-
});*/
34+
.accept('application/json')
35+
.send(data.formData)
36+
.on('progress', ({ percent }) => {
37+
progress(percent);
38+
})
39+
.end((err, res) => {
40+
if (err) {
41+
return failed(err);
42+
}
43+
success(res.body.files, 'image');
44+
});*/
2645
});
2746

2847
class SimpleDndEditor extends Component {
@@ -34,14 +53,46 @@ class SimpleDndEditor extends Component {
3453
onChange = (editorState) => {
3554
// console.log(convertToRaw(editorState.getCurrentContent()));
3655
this.setState({
37-
editorState,
56+
editorState: cleanupEmpty(editorState, ['block-image', 'block-text']),
3857
});
3958
};
4059

4160
focus = () => {
4261
this.refs.editor.focus();
4362
};
4463

64+
blockRendererFn = (contentBlock) => {
65+
const type = contentBlock.getType();
66+
if (type === 'preview-github') {
67+
const entityKey = contentBlock.getEntityAt(0);
68+
const data = entityKey ? Entity.get(entityKey).data : {};
69+
return {
70+
component: PreviewGithub,
71+
props: { ...data },
72+
};
73+
} else if (type === 'block-text') {
74+
const entityKey = contentBlock.getEntityAt(0);
75+
const data = entityKey ? Entity.get(entityKey).data : {};
76+
return {
77+
component: BlockText,
78+
props: { ...data },
79+
};
80+
} else if (type === 'block-image') {
81+
const entityKey = contentBlock.getEntityAt(0);
82+
const data = entityKey ? Entity.get(entityKey).data : {};
83+
return {
84+
component: BlockImage,
85+
props: {
86+
...data,
87+
refreshEditorState: () => {
88+
const { editorState } = this.state;
89+
this.onChange(EditorState.forceSelection(editorState, editorState.getCurrentContent().getSelectionAfter()));
90+
},
91+
},
92+
};
93+
} return undefined;
94+
}
95+
4596
render() {
4697
const { editorState } = this.state;
4798
const { isDragging, progress } = this.props;
@@ -50,11 +101,16 @@ class SimpleDndEditor extends Component {
50101
if (progress) classNames.push(styles.uploading);
51102

52103
return (
53-
<div className={classNames.join(' ')} onClick={this.focus}>
54-
<Editor editorState={editorState} onChange={this.onChange} plugins={[uploadPlugin]} ref="editor" />
55-
</div>
56-
);
104+
<div className={classNames.join(' ')} onClick={this.focus}>
105+
<Editor editorState={editorState}
106+
onChange={this.onChange}
107+
blockRendererFn={this.blockRendererFn}
108+
plugins={[dndPlugin]}
109+
ref="editor"
110+
/>
111+
</div>
112+
);
57113
}
58114
}
59115

60-
export default DndWrapper(SimpleDndEditor, uploadPlugin);
116+
export default SimpleDndEditor;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, { Component } from 'react';
2+
import Draggable from 'draft-js-dnd-plugin/components/block-draggable-wrapper';
3+
import Alignment from 'draft-js-dnd-plugin/components/block-alignment-wrapper';
4+
import imageStyles from './image.css';
5+
6+
class BlockImage extends Component {
7+
remove = (event) => {
8+
event.preventDefault();
9+
event.stopPropagation();
10+
11+
this.props.blockProps.onRemove(this.props.block.getKey());
12+
};
13+
14+
alignLeft = () => {
15+
this.props.align('left');
16+
};
17+
18+
alignCenter = () => {
19+
this.props.align('center');
20+
};
21+
22+
alignRight = () => {
23+
this.props.align('right');
24+
};
25+
26+
render() {
27+
const { blockProps, block, alignment, onDragStart, draggable } = this.props;
28+
29+
const buttons = [
30+
<span className={ imageStyles.imageButton }
31+
onClick={this.alignLeft}
32+
style={{ marginLeft: '-2.4em' }}
33+
role="button" key={'left'}
34+
>
35+
L
36+
</span>,
37+
<span className={ imageStyles.imageButton }
38+
onClick={this.alignCenter}
39+
role="button" key={'center'}
40+
>
41+
C
42+
</span>,
43+
<span className={ imageStyles.imageButton }
44+
onClick={this.alignRight}
45+
style={{ marginLeft: '0.9em' }}
46+
role="button" key={'right'}
47+
>
48+
R
49+
</span>,
50+
];
51+
52+
const className = `${imageStyles.imageWrapper} ${imageStyles[alignment || 'center']}`;
53+
54+
return (
55+
<figure className={ className }
56+
contentEditable={false}
57+
data-offset-key={ `${block.get('key')}-0-0` }
58+
onDragStart={onDragStart} draggable={draggable}
59+
>
60+
<img src={blockProps.src || blockProps.url} width="100%" height="auto" className={ imageStyles.image } />
61+
{blockProps.progress >= 0 ? <div className={imageStyles.imageLoader} style={{ width: `${100 - blockProps.progress}%` }} /> : null}
62+
{buttons}
63+
</figure>
64+
);
65+
}
66+
}
67+
68+
export default Draggable(Alignment(BlockImage));
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, { Component } from 'react';
2+
import Draggable from 'draft-js-dnd-plugin/components/block-draggable-wrapper';
3+
4+
class BlockText extends Component {
5+
render() {
6+
const { blockProps } = this.props;
7+
const style = {
8+
backgroundColor: '#f3f3f3',
9+
border: '1px solid rgba(220,220,220,1)',
10+
padding: '20px',
11+
}
12+
return (
13+
<pre contentEditable={false} style={style}>
14+
{blockProps.src}
15+
</pre>
16+
);
17+
}
18+
}
19+
20+
export default Draggable(BlockText, {
21+
useDiv: true
22+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
.imageWrapper {
2+
height: auto;
3+
position: relative;
4+
display: block;
5+
width: 200px;
6+
margin: 0;
7+
z-index: 1;
8+
}
9+
10+
.center {
11+
margin: 0px auto;
12+
}
13+
14+
.left {
15+
float: left;
16+
margin-right: 10px;
17+
}
18+
19+
.right {
20+
float: right;
21+
margin-left: 10px;
22+
}
23+
24+
.imageButton {
25+
background: #D9D9D9;
26+
color: #FFF;
27+
margin: 0;
28+
padding: 0.5em;
29+
border: none;
30+
border-radius: 50%;
31+
line-height: 80%;
32+
position: absolute;
33+
font-size: 0.62em;
34+
margin-left: -0.825em;
35+
cursor: pointer;
36+
z-index: 1000;
37+
}
38+
39+
.imageButton:hover {
40+
background: #E4E4E4;
41+
}
42+
43+
.imageButton:active {
44+
background: #CECECE;
45+
color: #EFEFEF;
46+
}
47+
48+
.image {
49+
}
50+
51+
.imageLoader{
52+
position: absolute;
53+
right: 0;
54+
top: 0;
55+
height: 100%;
56+
background-color: white;
57+
transition: width 0.5s ease 0s;
58+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React, { Component } from 'react';
2+
3+
class PreviewGithub extends Component {
4+
render() {
5+
const { blockProps, block } = this.props;
6+
7+
return (
8+
<span contentEditable={false} data-offset-key={ `${block.get('key')}-0-0` }>
9+
![Uploading {blockProps.name}...](){' '}
10+
</span>
11+
);
12+
}
13+
}
14+
15+
export default PreviewGithub;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {
2+
EditorState,
3+
Modifier,
4+
SelectionState,
5+
} from 'draft-js';
6+
7+
const cleanup = (editorState, blockKey, type) => {
8+
const content = editorState.getCurrentContent();
9+
10+
// get range of the broken block
11+
const targetRange = new SelectionState({
12+
anchorKey: blockKey,
13+
anchorOffset: 0,
14+
focusKey: blockKey,
15+
focusOffset: 0,
16+
});
17+
18+
// convert the block to a unstyled block to make text editing work
19+
const without = Modifier.setBlockType(
20+
content,
21+
targetRange,
22+
'unstyled'
23+
);
24+
const newState = EditorState.push(editorState, without, 'remove-'+type);
25+
return EditorState.forceSelection(newState, without.getSelectionAfter());
26+
};
27+
28+
export default (editorState, types) => {
29+
let newEditorState = editorState;
30+
31+
// If there is an empty block we remove it.
32+
// This can happen if a user hits the backspace button and removes the block.
33+
// In this case the block will still be of type block.
34+
editorState.getCurrentContent().get('blockMap').forEach((block) => {
35+
if (types.indexOf(block.get('type')) !== -1 && block.getEntityAt(0) === null) {
36+
newEditorState = cleanup(editorState, block.get('key'), block.get('type'));
37+
}
38+
});
39+
return newEditorState;
40+
};

docs/client/components/pages/Dnd/utils/mockUpload.js

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,11 @@
1-
1+
import { readFile } from 'draft-js-dnd-plugin/utils/file';
22

33
export default function mockUpload(data, success, failed, progress) {
4-
// Mock file upload, actually only happens client side
5-
const reader = new FileReader();
6-
7-
// This is called when finished reading
8-
reader.onload = e => {
9-
// Return an array with one image
10-
const x = {
11-
// These are attributes like size, name, type, ...
12-
...data.files[0],
13-
14-
// This is the files content as base64
15-
src: e.target.result,
16-
17-
// No URL, since nothing on server
18-
url: null,
19-
};
20-
success([x]);
21-
};
22-
234
function doProgress(percent) {
245
progress(percent || 1);
256
if (percent === 100) {
267
// Start reading the file
27-
reader.readAsDataURL(data.files[0]);
8+
Promise.all(data.files.map(readFile)).then(success);
289
} else {
2910
setTimeout(doProgress, 250, (percent || 0) + 10);
3011
}

0 commit comments

Comments
 (0)