diff --git a/frontend/components/Editor/components/Image.js b/frontend/components/Editor/components/Image.js index d7841f37c..aacbf36b2 100644 --- a/frontend/components/Editor/components/Image.js +++ b/frontend/components/Editor/components/Image.js @@ -1,13 +1,16 @@ // @flow import React from 'react'; import type { Props } from '../types'; +import styled from 'styled-components'; + +const LoadingImage = styled.img` + opacity: .5; +`; export default function Image({ attributes, node }: Props) { - return ( - {node.data.get('alt')} - ); + const loading = node.data.get('loading'); + const Component = loading ? LoadingImage : 'img'; + const src = node.data.get('inlineSrc') || node.data.get('src'); + + return ; } diff --git a/frontend/components/Editor/plugins.js b/frontend/components/Editor/plugins.js index af9fd158b..dfd0d1559 100644 --- a/frontend/components/Editor/plugins.js +++ b/frontend/components/Editor/plugins.js @@ -1,14 +1,13 @@ // @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'; import TrailingBlock from 'slate-trailing-block'; import EditCode from 'slate-edit-code'; import Prism from 'slate-prism'; -import uploadFile from 'utils/uploadFile'; import KeyboardShortcuts from './plugins/KeyboardShortcuts'; import MarkdownShortcuts from './plugins/MarkdownShortcuts'; +import ImageUploads from './plugins/ImageUploads'; const onlyInCode = node => node.type === 'code'; @@ -17,34 +16,13 @@ type Options = { onImageUploadStop: Function, }; -const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => { +const createPlugins = (options: Options) => { return [ PasteLinkify({ type: 'link', collapseTo: 'end', }), - DropOrPasteImages({ - extensions: ['png', 'jpg', 'gif'], - applyTransform: async (transform, file) => { - onImageUploadStart(); - try { - const asset = await uploadFile(file); - const alt = file.name; - const src = asset.url; - - return transform.insertBlock({ - type: 'image', - isVoid: true, - data: { src, alt }, - }); - } catch (err) { - // TODO: Show a failure alert - console.error(err); - } finally { - onImageUploadStop(); - } - }, - }), + ImageUploads(options), 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 new file mode 100644 index 000000000..e7967f616 --- /dev/null +++ b/frontend/components/Editor/plugins/ImageUploads.js @@ -0,0 +1,63 @@ +// @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 dfa1cd5f4..8ddc39b45 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": "^0.5.0", + "slate-drop-or-paste-images": "tommoor/slate-drop-or-paste-images", "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 810762be4..1f8073b6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2501,11 +2501,7 @@ emoji-name-map@1.1.2: iterate-object "^1.3.1" map-o "^2.0.1" -emoji-regex@^6.1.0: - version "6.4.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.4.2.tgz#a30b6fee353d406d96cfb9fa765bdc82897eff6e" - -emoji-regex@^6.5.1: +emoji-regex@^6.1.0, emoji-regex@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" @@ -8013,9 +8009,9 @@ slate-collapse-on-escape@^0.2.1: dependencies: to-pascal-case "^1.0.0" -slate-drop-or-paste-images@^0.5.0: +slate-drop-or-paste-images@tommoor/slate-drop-or-paste-images: version "0.5.0" - resolved "https://registry.yarnpkg.com/slate-drop-or-paste-images/-/slate-drop-or-paste-images-0.5.0.tgz#c90367f9612f75abae0d1d6b8b2008108da02598" + resolved "https://codeload.github.com/tommoor/slate-drop-or-paste-images/tar.gz/a3ebc74658941ec2c56d4b90493b8718b8a9fd4f" dependencies: data-uri-to-blob "0.0.4" es6-promise "^4.0.5"