From 1815ba4bc90be995a802cd8c6bdb36d8f1a169e6 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 13 Jul 2017 22:19:56 -0700 Subject: [PATCH 1/6] Fixes: Image uploads not working --- frontend/components/DropToImport/DropToImport.js | 6 +++++- frontend/components/Editor/plugins.js | 3 ++- frontend/scenes/Document/Document.js | 11 ++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/frontend/components/DropToImport/DropToImport.js b/frontend/components/DropToImport/DropToImport.js index b5a84100c..84b4cb163 100644 --- a/frontend/components/DropToImport/DropToImport.js +++ b/frontend/components/DropToImport/DropToImport.js @@ -19,6 +19,7 @@ class DropToImport extends Component { activeClassName?: string, rejectClassName?: string, documents: DocumentsStore, + disabled: boolean, history: Object, }; state = { @@ -83,9 +84,12 @@ class DropToImport extends Component { 'history', 'documentId', 'collectionId', - 'documents' + 'documents', + 'disabled' ); + if (this.props.disabled) return this.props.children; + return ( { DropOrPasteImages({ extensions: ['png', 'jpg', 'gif'], applyTransform: async (transform, file) => { + onImageUploadStart(); try { - onImageUploadStart(); const asset = await uploadFile(file); const alt = file.name; const src = asset.url; @@ -39,6 +39,7 @@ const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => { }); } catch (err) { // TODO: Show a failure alert + console.error(err); } finally { onImageUploadStop(); } diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index 7f6d95107..9b11fc141 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -124,13 +124,13 @@ type Props = { ); } - onImageUploadStart() { + onImageUploadStart = () => { this.setState({ isLoading: true }); - } + }; - onImageUploadStop() { + onImageUploadStop = () => { this.setState({ isLoading: false }); - } + }; onChange = text => { if (!this.document) return; @@ -166,7 +166,7 @@ type Props = { render() { const isNew = this.props.newDocument; - const isEditing = this.props.match.params.edit || isNew; + const isEditing = !!this.props.match.params.edit || isNew; const isFetching = !this.document; const titleText = get(this.document, 'title', 'Loading'); @@ -190,6 +190,7 @@ type Props = { onDragEnter={this.onStartDragging} onDragLeave={this.onStopDragging} onDrop={this.onStopDragging} + disabled={isEditing} > Date: Thu, 13 Jul 2017 22:32:06 -0700 Subject: [PATCH 2/6] Fixes #136 - Keyboard shortcuts should work when editor is not focused --- frontend/components/Editor/Editor.js | 42 ++++++++++++---------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index ad73209e1..51051ce20 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { observer } from 'mobx-react'; import { Editor, Plain } from 'slate'; +import keydown from 'react-keydown'; import classnames from 'classnames/bind'; import type { Document, State, Editor as EditorType } from './types'; import Flex from 'components/Flex'; @@ -30,11 +31,6 @@ type Props = { heading?: ?React.Element<*>, }; -type KeyData = { - isMeta: boolean, - key: string, -}; - @observer class MarkdownEditor extends Component { props: Props; editor: EditorType; @@ -82,25 +78,24 @@ type KeyData = { this.props.onChange(Markdown.serialize(state)); }; - onKeyDown = (ev: SyntheticKeyboardEvent, data: KeyData, state: State) => { - if (!data.isMeta) return; + @keydown('meta+s') + onSaveAndContinue(ev: SyntheticKeyboardEvent) { + ev.preventDefault(); + ev.stopPropagation(); + this.props.onSave(); + } - switch (data.key) { - case 's': - ev.preventDefault(); - ev.stopPropagation(); - this.props.onSave(); - return state; - case 'enter': - ev.preventDefault(); - ev.stopPropagation(); - this.props.onSave({ redirect: false }); - return state; - case 'escape': - return this.props.onCancel(); - default: - } - }; + @keydown('meta+enter') + onSave(ev: SyntheticKeyboardEvent) { + ev.preventDefault(); + ev.stopPropagation(); + this.props.onSave({ redirect: false }); + } + + @keydown('esc') + onCancel() { + this.props.onCancel(); + } focusAtStart = () => { const state = this.editor.getState(); @@ -138,7 +133,6 @@ type KeyData = { state={this.state.state} onChange={this.onChange} onDocumentChange={this.onDocumentChange} - onKeyDown={this.onKeyDown} onSave={this.props.onSave} readOnly={this.props.readOnly} /> From 0136c0ddcf92cd9e3cf4f81bb6b9ccee5225db8a Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 13 Jul 2017 23:19:49 -0700 Subject: [PATCH 3/6] Allow images to be dragged anywhere on document editor --- frontend/components/Editor/Editor.js | 50 ++++++++++++++++++++++---- frontend/components/Editor/Editor.scss | 6 +++- frontend/scenes/Document/Document.js | 36 ++++++++----------- frontend/utils/getDataTransferFiles.js | 18 ++++++++++ 4 files changed, 82 insertions(+), 28 deletions(-) create mode 100644 frontend/utils/getDataTransferFiles.js diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 51051ce20..26f2d1672 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -6,6 +6,8 @@ import { Editor, Plain } from 'slate'; 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'; @@ -78,6 +80,39 @@ type Props = { 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') onSaveAndContinue(ev: SyntheticKeyboardEvent) { ev.preventDefault(); @@ -115,7 +150,14 @@ type Props = { render = () => { return ( - + {!this.props.readOnly && } - + ); }; } @@ -147,10 +189,6 @@ MarkdownEditor.childContextTypes = { starred: PropTypes.bool, }; -const Container = styled(Flex)` - height: 100%; -`; - const HeaderContainer = styled(Flex).attrs({ align: 'flex-end', })` diff --git a/frontend/components/Editor/Editor.scss b/frontend/components/Editor/Editor.scss index 9cb57c6d5..d9dd72698 100644 --- a/frontend/components/Editor/Editor.scss +++ b/frontend/components/Editor/Editor.scss @@ -1,6 +1,10 @@ .editor { + font-weight: 400; + font-size: 1em; + line-height: 1.5em; + padding: 0 3em; + width: 50em; color: #1b2631; - width: 100%; h1, h2, diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index 9b11fc141..c6eab2c04 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -197,22 +197,20 @@ type Props = { when={this.document.hasPendingChanges} message={DISCARD_CHANGES} /> - - - + @@ -270,11 +268,7 @@ const LoadingState = styled(PreviewLoading)` `; const DocumentContainer = styled.div` - font-weight: 400; - font-size: 1em; - line-height: 1.5em; - padding: 0 3em; - width: 50em; + `; const StyledDropToImport = styled(DropToImport)` diff --git a/frontend/utils/getDataTransferFiles.js b/frontend/utils/getDataTransferFiles.js new file mode 100644 index 000000000..5811090be --- /dev/null +++ b/frontend/utils/getDataTransferFiles.js @@ -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); +} From eb3b597272ac3ba94916faa33de5a801a19c5cc2 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 13 Jul 2017 23:51:03 -0700 Subject: [PATCH 4/6] Fixes #137 - vertical alignment --- frontend/components/Editor/Editor.js | 55 +++++++++++++++----------- frontend/components/Editor/Editor.scss | 3 +- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 26f2d1672..1ad4d8993 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -154,32 +154,34 @@ type Props = { onDrop={this.handleDrop} onDragOver={this.cancelEvent} onDragEnter={this.cancelEvent} + align="flex-start" + justify="center" auto - column - align="center" > - - {this.props.heading} - - - (this.editor = ref)} - placeholder="Start with a title..." - className={cx(styles.editor, { readOnly: this.props.readOnly })} - schema={this.schema} - plugins={this.plugins} - state={this.state.state} - onChange={this.onChange} - onDocumentChange={this.onDocumentChange} - onSave={this.props.onSave} - readOnly={this.props.readOnly} - /> - {!this.props.readOnly && - } + + + {this.props.heading} + + + (this.editor = ref)} + placeholder="Start with a title..." + className={cx(styles.editor, { readOnly: this.props.readOnly })} + schema={this.schema} + plugins={this.plugins} + state={this.state.state} + onChange={this.onChange} + onDocumentChange={this.onDocumentChange} + onSave={this.props.onSave} + readOnly={this.props.readOnly} + /> + {!this.props.readOnly && + } + ); }; @@ -189,6 +191,11 @@ MarkdownEditor.childContextTypes = { starred: PropTypes.bool, }; +const MaxWidth = styled(Flex)` + max-width: 50em; + height: 100%; +`; + const HeaderContainer = styled(Flex).attrs({ align: 'flex-end', })` diff --git a/frontend/components/Editor/Editor.scss b/frontend/components/Editor/Editor.scss index d9dd72698..808cd0f01 100644 --- a/frontend/components/Editor/Editor.scss +++ b/frontend/components/Editor/Editor.scss @@ -2,8 +2,7 @@ font-weight: 400; font-size: 1em; line-height: 1.5em; - padding: 0 3em; - width: 50em; + width: 100%; color: #1b2631; h1, From 4992ceab69fdc60f768ce74fce931179e1542149 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 13 Jul 2017 23:58:17 -0700 Subject: [PATCH 5/6] Restore shortcuts with editor focus --- frontend/components/Editor/Editor.js | 29 ++++++++++++++++++++++++++-- frontend/scenes/Document/Document.js | 4 ---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 1ad4d8993..2e95eed08 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -33,6 +33,11 @@ type Props = { heading?: ?React.Element<*>, }; +type KeyData = { + isMeta: boolean, + key: string, +}; + @observer class MarkdownEditor extends Component { props: Props; editor: EditorType; @@ -113,15 +118,16 @@ type Props = { ev.preventDefault(); }; + // Handling of keyboard shortcuts outside of editor focus @keydown('meta+s') - onSaveAndContinue(ev: SyntheticKeyboardEvent) { + onSave(ev: SyntheticKeyboardEvent) { ev.preventDefault(); ev.stopPropagation(); this.props.onSave(); } @keydown('meta+enter') - onSave(ev: SyntheticKeyboardEvent) { + onSaveAndExit(ev: SyntheticKeyboardEvent) { ev.preventDefault(); ev.stopPropagation(); this.props.onSave({ redirect: false }); @@ -132,6 +138,24 @@ type Props = { this.props.onCancel(); } + // Handling of keyboard shortcuts within editor focus + onKeyDown = (ev: SyntheticKeyboardEvent, data: KeyData, state: State) => { + if (!data.isMeta) return; + + switch (data.key) { + case 's': + this.onSave(ev); + break; + case 'enter': + this.onSaveAndExit(ev); + break; + case 'escape': + this.onCancel(); + break; + default: + } + }; + focusAtStart = () => { const state = this.editor.getState(); const transform = state.transform(); @@ -174,6 +198,7 @@ type Props = { schema={this.schema} plugins={this.plugins} state={this.state.state} + onKeyDown={this.onKeyDown} onChange={this.onChange} onDocumentChange={this.onDocumentChange} onSave={this.props.onSave} diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index c6eab2c04..43fdde1d1 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -267,10 +267,6 @@ const LoadingState = styled(PreviewLoading)` margin: 80px 20px; `; -const DocumentContainer = styled.div` - -`; - const StyledDropToImport = styled(DropToImport)` display: flex; flex: 1; From 9f18bd2fb1c7007e6e5fe654e4e3d2661ffd6d63 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Fri, 14 Jul 2017 08:48:42 -0700 Subject: [PATCH 6/6] Restore 'e' to edit current document Fixed up ? to open keyboard shortcuts --- frontend/components/Layout/Layout.js | 23 +++++++++++++++-------- frontend/static/flatpages/keyboard.md | 2 +- frontend/utils/routeHelpers.js | 11 ++++++++++- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index 96e7ccacc..001d4d961 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -9,6 +9,7 @@ import _ from 'lodash'; import keydown from 'react-keydown'; import Flex from 'components/Flex'; import { color, layout } from 'styles/constants'; +import { documentEditUrl, homeUrl, searchUrl } from 'utils/routeHelpers'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; import { LoadingIndicatorBar } from 'components/LoadingIndicator'; @@ -51,15 +52,21 @@ type Props = { @observable modal = null; @keydown(['/', 't']) - search() { - if (this.props.auth.authenticated) - _.defer(() => this.props.history.push('/search')); + goToSearch(ev) { + ev.preventDefault(); + ev.stopPropagation(); + this.props.history.push(searchUrl()); } @keydown('d') - dashboard() { - if (this.props.auth.authenticated) - _.defer(() => this.props.history.push('/')); + goToDashboard() { + this.props.history.push(homeUrl()); + } + + @keydown('e') + goToEdit() { + if (!this.props.ui.activeDocument) return; + this.props.history.push(documentEditUrl(this.props.ui.activeDocument)); } handleLogout = () => { @@ -67,9 +74,9 @@ type Props = { }; @keydown('shift+/') - handleOpenKeyboardShortcuts = () => { + handleOpenKeyboardShortcuts() { this.modal = 'keyboard-shortcuts'; - }; + } handleCreateCollection = () => { this.modal = 'create-collection'; diff --git a/frontend/static/flatpages/keyboard.md b/frontend/static/flatpages/keyboard.md index 4855c385f..8d0eeeb02 100644 --- a/frontend/static/flatpages/keyboard.md +++ b/frontend/static/flatpages/keyboard.md @@ -1,5 +1,5 @@ - `Cmd+Enter` - Save and exit document editor -- `Cmd+s` - Save document and continue editing +- `Cmd+S` - Save document and continue editing - `Cmd+Esc` - Cancel edit - `/` or `t` - Jump to search - `d` - Jump to dashboard diff --git a/frontend/utils/routeHelpers.js b/frontend/utils/routeHelpers.js index 456150418..8c43c22dd 100644 --- a/frontend/utils/routeHelpers.js +++ b/frontend/utils/routeHelpers.js @@ -1,4 +1,5 @@ // @flow +import Document from 'models/Document'; export function homeUrl(): string { return '/dashboard'; @@ -12,7 +13,15 @@ export function newCollectionUrl(): string { return '/collections/new'; } -export function searchUrl(query: string): string { +export function documentUrl(doc: Document): string { + return doc.url; +} + +export function documentEditUrl(doc: Document): string { + return `${doc.url}/edit`; +} + +export function searchUrl(query?: string): string { if (query) return `/search/${query}`; return `/search`; }