Allow images to be dragged anywhere on document editor
This commit is contained in:
@ -6,6 +6,8 @@ import { Editor, Plain } from 'slate';
|
|||||||
import keydown from 'react-keydown';
|
import keydown from 'react-keydown';
|
||||||
import classnames from 'classnames/bind';
|
import classnames from 'classnames/bind';
|
||||||
import type { Document, State, Editor as EditorType } from './types';
|
import type { Document, State, Editor as EditorType } from './types';
|
||||||
|
import getDataTransferFiles from 'utils/getDataTransferFiles';
|
||||||
|
import uploadFile from 'utils/uploadFile';
|
||||||
import Flex from 'components/Flex';
|
import Flex from 'components/Flex';
|
||||||
import ClickablePadding from './components/ClickablePadding';
|
import ClickablePadding from './components/ClickablePadding';
|
||||||
import Toolbar from './components/Toolbar';
|
import Toolbar from './components/Toolbar';
|
||||||
@ -78,6 +80,39 @@ type Props = {
|
|||||||
this.props.onChange(Markdown.serialize(state));
|
this.props.onChange(Markdown.serialize(state));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleDrop = async (ev: SyntheticEvent) => {
|
||||||
|
// check if this event was already handled by the Editor
|
||||||
|
if (ev.isDefaultPrevented()) return;
|
||||||
|
|
||||||
|
// otherwise we'll handle this
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
const files = getDataTransferFiles(ev);
|
||||||
|
for (const file of files) {
|
||||||
|
await this.insertFile(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
insertFile = async (file: Object) => {
|
||||||
|
this.props.onImageUploadStart();
|
||||||
|
const asset = await uploadFile(file);
|
||||||
|
const state = this.editor.getState();
|
||||||
|
const transform = state.transform();
|
||||||
|
transform.collapseToEndOf(state.document);
|
||||||
|
transform.insertBlock({
|
||||||
|
type: 'image',
|
||||||
|
isVoid: true,
|
||||||
|
data: { src: asset.url, alt: file.name },
|
||||||
|
});
|
||||||
|
this.props.onImageUploadStop();
|
||||||
|
this.setState({ state: transform.apply() });
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelEvent = (ev: SyntheticEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
@keydown('meta+s')
|
@keydown('meta+s')
|
||||||
onSaveAndContinue(ev: SyntheticKeyboardEvent) {
|
onSaveAndContinue(ev: SyntheticKeyboardEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@ -115,7 +150,14 @@ type Props = {
|
|||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
return (
|
return (
|
||||||
<Container auto column>
|
<Flex
|
||||||
|
onDrop={this.handleDrop}
|
||||||
|
onDragOver={this.cancelEvent}
|
||||||
|
onDragEnter={this.cancelEvent}
|
||||||
|
auto
|
||||||
|
column
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
<HeaderContainer
|
<HeaderContainer
|
||||||
onClick={this.focusAtStart}
|
onClick={this.focusAtStart}
|
||||||
readOnly={this.props.readOnly}
|
readOnly={this.props.readOnly}
|
||||||
@ -138,7 +180,7 @@ type Props = {
|
|||||||
/>
|
/>
|
||||||
{!this.props.readOnly &&
|
{!this.props.readOnly &&
|
||||||
<ClickablePadding onClick={this.focusAtEnd} grow />}
|
<ClickablePadding onClick={this.focusAtEnd} grow />}
|
||||||
</Container>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -147,10 +189,6 @@ MarkdownEditor.childContextTypes = {
|
|||||||
starred: PropTypes.bool,
|
starred: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Container = styled(Flex)`
|
|
||||||
height: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const HeaderContainer = styled(Flex).attrs({
|
const HeaderContainer = styled(Flex).attrs({
|
||||||
align: 'flex-end',
|
align: 'flex-end',
|
||||||
})`
|
})`
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
.editor {
|
.editor {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
padding: 0 3em;
|
||||||
|
width: 50em;
|
||||||
color: #1b2631;
|
color: #1b2631;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
|
@ -197,22 +197,20 @@ type Props = {
|
|||||||
when={this.document.hasPendingChanges}
|
when={this.document.hasPendingChanges}
|
||||||
message={DISCARD_CHANGES}
|
message={DISCARD_CHANGES}
|
||||||
/>
|
/>
|
||||||
<DocumentContainer>
|
<Editor
|
||||||
<Editor
|
key={this.document.id}
|
||||||
key={this.document.id}
|
text={this.document.text}
|
||||||
text={this.document.text}
|
onImageUploadStart={this.onImageUploadStart}
|
||||||
onImageUploadStart={this.onImageUploadStart}
|
onImageUploadStop={this.onImageUploadStop}
|
||||||
onImageUploadStop={this.onImageUploadStop}
|
onChange={this.onChange}
|
||||||
onChange={this.onChange}
|
onSave={this.onSave}
|
||||||
onSave={this.onSave}
|
onCancel={this.onCancel}
|
||||||
onCancel={this.onCancel}
|
onStar={this.document.star}
|
||||||
onStar={this.document.star}
|
onUnstar={this.document.unstar}
|
||||||
onUnstar={this.document.unstar}
|
starred={this.document.starred}
|
||||||
starred={this.document.starred}
|
heading={this.renderHeading(!!isEditing)}
|
||||||
heading={this.renderHeading(!!isEditing)}
|
readOnly={!isEditing}
|
||||||
readOnly={!isEditing}
|
/>
|
||||||
/>
|
|
||||||
</DocumentContainer>
|
|
||||||
<Meta align="center" justify="flex-end" readOnly={!isEditing}>
|
<Meta align="center" justify="flex-end" readOnly={!isEditing}>
|
||||||
<Flex align="center">
|
<Flex align="center">
|
||||||
<HeaderAction>
|
<HeaderAction>
|
||||||
@ -270,11 +268,7 @@ const LoadingState = styled(PreviewLoading)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const DocumentContainer = styled.div`
|
const DocumentContainer = styled.div`
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
padding: 0 3em;
|
|
||||||
width: 50em;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledDropToImport = styled(DropToImport)`
|
const StyledDropToImport = styled(DropToImport)`
|
||||||
|
18
frontend/utils/getDataTransferFiles.js
Normal file
18
frontend/utils/getDataTransferFiles.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// @flow
|
||||||
|
export default function getDataTransferFiles(event: SyntheticEvent) {
|
||||||
|
let dataTransferItemsList = [];
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
const dt = event.dataTransfer;
|
||||||
|
if (dt.files && dt.files.length) {
|
||||||
|
dataTransferItemsList = dt.files;
|
||||||
|
} else if (dt.items && dt.items.length) {
|
||||||
|
// During the drag even the dataTransfer.files is null
|
||||||
|
// but Chrome implements some drag store, which is accesible via dataTransfer.items
|
||||||
|
dataTransferItemsList = dt.items;
|
||||||
|
}
|
||||||
|
} else if (event.target && event.target.files) {
|
||||||
|
dataTransferItemsList = event.target.files;
|
||||||
|
}
|
||||||
|
// Convert from DataTransferItemsList to the native Array
|
||||||
|
return Array.prototype.slice.call(dataTransferItemsList);
|
||||||
|
}
|
Reference in New Issue
Block a user