diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 946064ff..25803128 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -6,13 +6,13 @@ import keydown from 'react-keydown'; import classnames from 'classnames/bind'; 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 ClickablePadding from './components/ClickablePadding'; import Toolbar from './components/Toolbar'; import Markdown from './serializer'; import createSchema from './schema'; import createPlugins from './plugins'; +import insertImage from './insertImage'; import styled from 'styled-components'; import styles from './Editor.scss'; @@ -95,23 +95,22 @@ type KeyData = { const files = getDataTransferFiles(ev); for (const file of files) { - await this.insertFile(file); + await this.insertImageFile(file); } }; - insertFile = async (file: Object) => { - this.props.onImageUploadStart(); - const asset = await uploadFile(file); + insertImageFile = async (file: window.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() }); + let transform = state.transform(); + + transform = await insertImage( + transform, + file, + this.editor, + this.props.onImageUploadStart, + this.props.onImageUploadStop + ); + this.editor.onChange(transform.apply()); }; cancelEvent = (ev: SyntheticEvent) => { diff --git a/frontend/components/Editor/insertImage.js b/frontend/components/Editor/insertImage.js new file mode 100644 index 00000000..43e56069 --- /dev/null +++ b/frontend/components/Editor/insertImage.js @@ -0,0 +1,56 @@ +// @flow +import uuid from 'uuid'; +import uploadFile from 'utils/uploadFile'; +import type { Editor, Transform } from './types'; + +export default async function insertImageFile( + transform: Transform, + file: window.File, + editor: Editor, + onImageUploadStart: () => void, + onImageUploadStop: () => void +) { + onImageUploadStart(); + + try { + // load the file as a data URL + const id = uuid.v4(); + const alt = file.name; + const reader = new FileReader(); + reader.addEventListener('load', () => { + const src = reader.result; + + // insert into document as uploading placeholder + const state = transform + .insertBlock({ + type: 'image', + isVoid: true, + data: { src, alt, id, loading: true }, + }) + .apply(); + editor.onChange(state); + }); + reader.readAsDataURL(file); + + // now we have a placeholder, start the upload + const asset = await uploadFile(file); + const src = asset.url; + + // we dont use the original transform provided to the callback here + // as the state may have changed significantly in the time it took to + // upload the file. + const state = editor.getState(); + const finalTransform = state.transform(); + const placeholder = state.document.findDescendant( + node => node.data && node.data.get('id') === id + ); + + return finalTransform.setNodeByKey(placeholder.key, { + data: { src, alt, loading: false }, + }); + } catch (err) { + throw err; + } finally { + onImageUploadStop(); + } +} diff --git a/frontend/components/Editor/plugins.js b/frontend/components/Editor/plugins.js index dfd0d155..d56bfcfa 100644 --- a/frontend/components/Editor/plugins.js +++ b/frontend/components/Editor/plugins.js @@ -1,4 +1,5 @@ // @flow +import DropOrPasteImages from 'slate-drop-or-paste-images'; import PasteLinkify from 'slate-paste-linkify'; import EditList from 'slate-edit-list'; import CollapseOnEscape from 'slate-collapse-on-escape'; @@ -7,7 +8,7 @@ import EditCode from 'slate-edit-code'; import Prism from 'slate-prism'; import KeyboardShortcuts from './plugins/KeyboardShortcuts'; import MarkdownShortcuts from './plugins/MarkdownShortcuts'; -import ImageUploads from './plugins/ImageUploads'; +import insertImage from './insertImage'; const onlyInCode = node => node.type === 'code'; @@ -16,13 +17,24 @@ type Options = { onImageUploadStop: Function, }; -const createPlugins = (options: Options) => { +const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => { return [ PasteLinkify({ type: 'link', collapseTo: 'end', }), - ImageUploads(options), + DropOrPasteImages({ + extensions: ['png', 'jpg', 'gif'], + applyTransform: (transform, editor, file) => { + return insertImage( + transform, + file, + editor, + onImageUploadStart, + onImageUploadStop + ); + }, + }), EditList({ types: ['ordered-list', 'bulleted-list', 'todo-list'], typeItem: 'list-item', diff --git a/frontend/components/Editor/plugins/ImageUploads.js b/frontend/components/Editor/plugins/ImageUploads.js deleted file mode 100644 index e7967f61..00000000 --- a/frontend/components/Editor/plugins/ImageUploads.js +++ /dev/null @@ -1,63 +0,0 @@ -// @flow -import uuid from 'uuid'; -import DropOrPasteImages from 'slate-drop-or-paste-images'; -import uploadFile from 'utils/uploadFile'; - -type Options = { - onImageUploadStart: Function, - onImageUploadStop: Function, -}; - -export default function ImageUploads({ - onImageUploadStart, - onImageUploadStop, -}: Options) { - return DropOrPasteImages({ - extensions: ['png', 'jpg', 'gif'], - applyTransform: async (transform, editor, file) => { - onImageUploadStart(); - - // load the file as a data URL - const id = uuid.v4(); - const alt = file.name; - const reader = new FileReader(); - reader.addEventListener('load', () => { - const src = reader.result; - - // insert into document as uploading placeholder - const state = transform - .insertBlock({ - type: 'image', - isVoid: true, - data: { src, alt, id, loading: true }, - }) - .apply(); - editor.onChange(state); - }); - reader.readAsDataURL(file); - - // now we have a placeholder, start the upload - try { - const asset = await uploadFile(file); - const src = asset.url; - - // we dont use the original transform provided to the callback here - // as the state may have changed significantly in the time it took to - // upload the file. - const state = editor.getState(); - const transform = state.transform(); - const placeholder = state.document.findDescendant( - node => node.data && node.data.get('id') === id - ); - return transform.setNodeByKey(placeholder.key, { - data: { src, alt, loading: false }, - }); - } catch (err) { - // TODO: Show a failure alert - console.error(err); - } finally { - onImageUploadStop(); - } - }, - }); -} diff --git a/package.json b/package.json index 8ddc39b4..2384681f 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "sequelize-encrypted": "0.1.0", "slate": "^0.19.30", "slate-collapse-on-escape": "^0.2.1", - "slate-drop-or-paste-images": "tommoor/slate-drop-or-paste-images", + "slate-drop-or-paste-images": "tommoor/slate-drop-or-paste-images#dev", "slate-edit-code": "^0.10.2", "slate-edit-list": "^0.7.0", "slate-markdown-serializer": "tommoor/slate-markdown-serializer", diff --git a/yarn.lock b/yarn.lock index 1f8073b6..67384488 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4846,6 +4846,10 @@ json-loader@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" +json-loader@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -8009,9 +8013,9 @@ slate-collapse-on-escape@^0.2.1: dependencies: to-pascal-case "^1.0.0" -slate-drop-or-paste-images@tommoor/slate-drop-or-paste-images: +slate-drop-or-paste-images@tommoor/slate-drop-or-paste-images#dev: version "0.5.0" - resolved "https://codeload.github.com/tommoor/slate-drop-or-paste-images/tar.gz/a3ebc74658941ec2c56d4b90493b8718b8a9fd4f" + resolved "https://codeload.github.com/tommoor/slate-drop-or-paste-images/tar.gz/935894631acb528b1eec6a8a1a78857da9d0d1da" dependencies: data-uri-to-blob "0.0.4" es6-promise "^4.0.5" @@ -8019,6 +8023,7 @@ slate-drop-or-paste-images@tommoor/slate-drop-or-paste-images: is-data-uri "^0.1.0" is-image "^1.0.1" is-url "^1.2.2" + json-loader "^0.5.7" mime-types "^2.1.11" slate-edit-code@^0.10.2: