diff --git a/.eslintrc b/.eslintrc
index be1271ebd..a1eb5ce44 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -18,6 +18,13 @@
// Prettier automatically uses the least amount of parens possible, so this
// does more harm than good.
"no-mixed-operators": "off",
+ // Temporary fix for a failing import lint
+ "import/no-unresolved": [
+ "error",
+ {
+ "ignore": [ "slate-drop-or-paste-images" ]
+ }
+ ],
// Flow
"flowtype/require-valid-file-annotation": [
2,
diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js
index 946064ff8..258031285 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/components/Image.js b/frontend/components/Editor/components/Image.js
index d7841f37c..c98ee7a47 100644
--- a/frontend/components/Editor/components/Image.js
+++ b/frontend/components/Editor/components/Image.js
@@ -1,13 +1,27 @@
// @flow
import React from 'react';
import type { Props } from '../types';
+import { color } from 'styles/constants';
+import styled from 'styled-components';
+
+const StyledImg = styled.img`
+ box-shadow: ${props => (props.active ? `0 0 0 3px ${color.slate}` : '0')};
+ opacity: ${props => (props.loading ? 0.5 : 1)};
+`;
+
+export default function Image({ attributes, state, node }: Props) {
+ const loading = node.data.get('loading');
+ const alt = node.data.get('alt');
+ const src = node.data.get('src');
+ const active = state.isFocused && state.selection.hasEdgeIn(node);
-export default function Image({ attributes, node }: Props) {
return (
-
);
}
diff --git a/frontend/components/Editor/insertImage.js b/frontend/components/Editor/insertImage.js
new file mode 100644
index 000000000..43e56069a
--- /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 af9fd158b..d56bfcfaf 100644
--- a/frontend/components/Editor/plugins.js
+++ b/frontend/components/Editor/plugins.js
@@ -6,9 +6,9 @@ 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 insertImage from './insertImage';
const onlyInCode = node => node.type === 'code';
@@ -25,24 +25,14 @@ const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => {
}),
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();
- }
+ applyTransform: (transform, editor, file) => {
+ return insertImage(
+ transform,
+ file,
+ editor,
+ onImageUploadStart,
+ onImageUploadStop
+ );
},
}),
EditList({
diff --git a/frontend/components/Editor/types.js b/frontend/components/Editor/types.js
index 79cad5d49..0db4d68ae 100644
--- a/frontend/components/Editor/types.js
+++ b/frontend/components/Editor/types.js
@@ -1,5 +1,6 @@
// @flow
import { List, Set, Map } from 'immutable';
+import { Selection } from 'slate';
export type NodeTransform = {
addMarkByKey: Function,
@@ -83,15 +84,6 @@ export type Block = Node & {
export type Document = Node;
-export type Props = {
- node: Node,
- parent?: Node,
- attributes?: Object,
- editor: Editor,
- readOnly?: boolean,
- children?: React$Element,
-};
-
export type State = {
document: Document,
selection: Selection,
@@ -108,3 +100,13 @@ export type State = {
transform: Function,
isBlurred: Function,
};
+
+export type Props = {
+ node: Node,
+ parent?: Node,
+ attributes?: Object,
+ state: State,
+ editor: Editor,
+ readOnly?: boolean,
+ children?: React$Element,
+};
diff --git a/package.json b/package.json
index dfa1cd5f4..2384681f6 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#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 810762be4..67384488e 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"
@@ -4850,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"
@@ -8013,9 +8013,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#dev:
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/935894631acb528b1eec6a8a1a78857da9d0d1da"
dependencies:
data-uri-to-blob "0.0.4"
es6-promise "^4.0.5"
@@ -8023,6 +8023,7 @@ slate-drop-or-paste-images@^0.5.0:
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: