Support for drag and drop image uploads in both rte and raw editor mode
This commit is contained in:
parent
2ed2160c92
commit
75100eaa3a
@ -1,3 +1,22 @@
|
||||
.root {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dragging { }
|
||||
|
||||
.shim {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
border: 2px dashed #aaa;
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.dragging .shim {
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -24,10 +24,6 @@ function processUrl(url) {
|
||||
return `/${ url }`;
|
||||
}
|
||||
|
||||
function preventDefault(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function cleanupPaste(paste) {
|
||||
const content = html.toContent(paste);
|
||||
return markdown.toText(content);
|
||||
@ -76,11 +72,9 @@ export default class RawEditor extends React.Component {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateHeight();
|
||||
this.element.addEventListener('dragenter', preventDefault, false);
|
||||
this.element.addEventListener('dragover', preventDefault, false);
|
||||
this.element.addEventListener('drop', this.handleDrop, false);
|
||||
this.element.addEventListener('paste', this.handlePaste, false);
|
||||
}
|
||||
|
||||
@ -93,9 +87,7 @@ export default class RawEditor extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.element.removeEventListener('dragenter', preventDefault);
|
||||
this.element.removeEventListener('dragover', preventDefault);
|
||||
this.element.removeEventListener('drop', this.handleDrop);
|
||||
this.element.removeEventListener('paste', this.handlePaste);
|
||||
}
|
||||
|
||||
getSelection() {
|
||||
@ -256,8 +248,25 @@ export default class RawEditor extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ dragging: true });
|
||||
};
|
||||
|
||||
handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ dragging: false });
|
||||
};
|
||||
|
||||
handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({ dragging: false });
|
||||
|
||||
let data;
|
||||
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length) {
|
||||
@ -296,8 +305,19 @@ export default class RawEditor extends React.Component {
|
||||
|
||||
render() {
|
||||
const { onAddMedia, onRemoveMedia, getMedia } = this.props;
|
||||
const { showToolbar, showBlockMenu, plugins, selectionPosition } = this.state;
|
||||
return (<div className={styles.root}>
|
||||
const { showToolbar, showBlockMenu, plugins, selectionPosition, dragging } = this.state;
|
||||
const classNames = [styles.root];
|
||||
if (dragging) {
|
||||
classNames.push(styles.dragging);
|
||||
}
|
||||
|
||||
return (<div
|
||||
className={classNames.join(' ')}
|
||||
onDragEnter={this.handleDragEnter}
|
||||
onDragLeave={this.handleDragLeave}
|
||||
onDragOver={this.handleDragOver}
|
||||
onDrop={this.handleDrop}
|
||||
>
|
||||
<Toolbar
|
||||
isOpen={showToolbar}
|
||||
selectionPosition={selectionPosition}
|
||||
@ -324,6 +344,7 @@ export default class RawEditor extends React.Component {
|
||||
onChange={this.handleChange}
|
||||
onSelect={this.handleSelection}
|
||||
/>
|
||||
<div className={styles.shim}/>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,13 @@
|
||||
& p {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
& hr {
|
||||
border: 1px solid;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
& li > p {
|
||||
margin: 0;
|
||||
}
|
||||
& div[data-plugin] {
|
||||
background: #fff;
|
||||
border: 1px solid #aaa;
|
||||
@ -28,6 +35,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dragging { }
|
||||
|
||||
.shim {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
border: 2px dashed #aaa;
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.dragging .shim {
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:global {
|
||||
& .ProseMirror {
|
||||
position: relative;
|
||||
|
@ -11,6 +11,7 @@ import { keymap } from 'prosemirror-keymap';
|
||||
import { schema, defaultMarkdownSerializer } from 'prosemirror-markdown';
|
||||
import { baseKeymap, setBlockType, toggleMark } from 'prosemirror-commands';
|
||||
import registry from '../../../../lib/registry';
|
||||
import MediaProxy from '../../../../valueObjects/MediaProxy';
|
||||
import { buildKeymap } from './keymap';
|
||||
import createMarkdownParser from './parser';
|
||||
import Toolbar from '../Toolbar';
|
||||
@ -213,15 +214,71 @@ export default class Editor extends Component {
|
||||
this.view.props.onAction(this.view.state.tr.replaceSelectionWith(nodeType.create(data.toJS())).action());
|
||||
};
|
||||
|
||||
handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ dragging: true });
|
||||
};
|
||||
|
||||
handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ dragging: false });
|
||||
};
|
||||
|
||||
handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({ dragging: false });
|
||||
|
||||
const { schema } = this.state;
|
||||
|
||||
const nodes = [];
|
||||
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length) {
|
||||
Array.from(e.dataTransfer.files).forEach((file) => {
|
||||
const mediaProxy = new MediaProxy(file.name, file);
|
||||
this.props.onAddMedia(mediaProxy);
|
||||
if (file.type.split('/')[0] === 'image') {
|
||||
nodes.push(
|
||||
schema.nodes.image.create({ src: mediaProxy.public_path, alt: file.name })
|
||||
);
|
||||
} else {
|
||||
nodes.push(
|
||||
schema.marks.link.create({ href: mediaProxy.public_path, title: file.name })
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
nodes.push(schema.nodes.paragraph.create({}, e.dataTransfer.getData('text/plain')));
|
||||
}
|
||||
|
||||
nodes.forEach((node) => {
|
||||
this.view.props.onAction(this.view.state.tr.replaceSelectionWith(node).action());
|
||||
});
|
||||
};
|
||||
|
||||
handleToggle = () => {
|
||||
this.props.onMode('raw');
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onAddMedia, onRemoveMedia, getMedia } = this.props;
|
||||
const { plugins, showToolbar, showBlockMenu, selectionPosition } = this.state;
|
||||
const { plugins, showToolbar, showBlockMenu, selectionPosition, dragging } = this.state;
|
||||
const classNames = [styles.editor];
|
||||
if (dragging) {
|
||||
classNames.push(styles.dragging);
|
||||
}
|
||||
|
||||
return (<div className={styles.editor}>
|
||||
return (<div
|
||||
className={classNames.join(' ')}
|
||||
onDragEnter={this.handleDragEnter}
|
||||
onDragLeave={this.handleDragLeave}
|
||||
onDragOver={this.handleDragOver}
|
||||
onDrop={this.handleDrop}
|
||||
>
|
||||
<Toolbar
|
||||
isOpen={showToolbar}
|
||||
selectionPosition={selectionPosition}
|
||||
@ -242,6 +299,7 @@ export default class Editor extends Component {
|
||||
getMedia={getMedia}
|
||||
/>
|
||||
<div ref={this.handleRef} />
|
||||
<div className={styles.shim} />
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user