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 (
-
- );
+ 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"