diff --git a/app/components/DropToImport/DropToImport.js b/app/components/DropToImport/DropToImport.js index d948b8d9..a72a4bac 100644 --- a/app/components/DropToImport/DropToImport.js +++ b/app/components/DropToImport/DropToImport.js @@ -4,10 +4,10 @@ import { observable } from 'mobx'; import { observer, inject } from 'mobx-react'; import { injectGlobal } from 'styled-components'; import { color } from 'shared/styles/constants'; +import importFile from 'utils/importFile'; import invariant from 'invariant'; import _ from 'lodash'; import Dropzone from 'react-dropzone'; -import Document from 'models/Document'; import DocumentsStore from 'stores/DocumentsStore'; import LoadingIndicator from 'components/LoadingIndicator'; @@ -19,7 +19,6 @@ type Props = { rejectClassName?: string, documents: DocumentsStore, disabled: boolean, - dropzoneRef: Function, history: Object, }; @@ -40,30 +39,6 @@ class DropToImport extends Component { @observable isImporting: boolean = false; props: Props; - importFile = async ({ file, documentId, collectionId, redirect }) => { - const reader = new FileReader(); - - reader.onload = async ev => { - const text = ev.target.result; - let data = { - parentDocument: undefined, - collection: { id: collectionId }, - text, - }; - - if (documentId) data.parentDocument = documentId; - - let document = new Document(data); - document = await document.save(); - this.props.documents.add(document); - - if (redirect && this.props.history) { - this.props.history.push(document.url); - } - }; - reader.readAsText(file); - }; - onDropAccepted = async (files = []) => { this.isImporting = true; @@ -79,7 +54,16 @@ class DropToImport extends Component { } for (const file of files) { - await this.importFile({ file, documentId, collectionId, redirect }); + const doc = await importFile({ + documents: this.props.documents, + file, + documentId, + collectionId, + }); + + if (redirect) { + this.props.history.push(doc.url); + } } } catch (err) { // TODO: show error alert. @@ -96,7 +80,6 @@ class DropToImport extends Component { 'collectionId', 'documents', 'disabled', - 'dropzoneRef', 'menuOpen' ); @@ -110,7 +93,6 @@ class DropToImport extends Component { disableClick disablePreview multiple - ref={this.props.dropzoneRef} {...props} > {this.isImporting && } diff --git a/app/components/Sidebar/components/Collections.js b/app/components/Sidebar/components/Collections.js index a4fdbcd0..4dfd25c7 100644 --- a/app/components/Sidebar/components/Collections.js +++ b/app/components/Sidebar/components/Collections.js @@ -76,14 +76,9 @@ type CollectionLinkProps = { @observer class CollectionLink extends Component { props: CollectionLinkProps; - dropzoneRef; @observable menuOpen = false; - handleImport = () => { - this.dropzoneRef.open(); - }; - renderDocuments() { const { history, @@ -119,7 +114,6 @@ class CollectionLink extends Component { collectionId={collection.id} activeClassName="activeDropZone" menuOpen={this.menuOpen} - dropzoneRef={ref => (this.dropzoneRef = ref)} > {collection.name} - - - (this.menuOpen = true)} - onClose={() => (this.menuOpen = false)} - onImport={this.handleImport} - open={this.menuOpen} - /> - + + (this.menuOpen = true)} + onClose={() => (this.menuOpen = false)} + /> + ); } @@ -233,6 +224,7 @@ const CollectionName = styled(Flex)` const CollectionAction = styled.span` position: absolute; right: 0; + top: 0; color: ${color.slate}; svg { opacity: 0.75; @@ -246,6 +238,8 @@ const CollectionAction = styled.span` `; const StyledDropToImport = styled(DropToImport)` + position: relative; + ${CollectionAction} { display: ${props => (props.menuOpen ? 'inline' : 'none')}; } @@ -260,6 +254,7 @@ const StyledDropToImport = styled(DropToImport)` const CollectionChildren = styled(Flex)` margin-top: -4px; margin-left: 36px; + padding-bottom: 4px; `; const DocumentChildren = styled(Flex)` diff --git a/app/menus/CollectionMenu.js b/app/menus/CollectionMenu.js index fb1d7e99..fb7d3434 100644 --- a/app/menus/CollectionMenu.js +++ b/app/menus/CollectionMenu.js @@ -1,24 +1,31 @@ // @flow import React, { Component } from 'react'; import { inject, observer } from 'mobx-react'; +import styled from 'styled-components'; +import getDataTransferFiles from 'utils/getDataTransferFiles'; +import importFile from 'utils/importFile'; import Collection from 'models/Collection'; import UiStore from 'stores/UiStore'; +import DocumentsStore from 'stores/DocumentsStore'; import MoreIcon from 'components/Icon/MoreIcon'; import Flex from 'shared/components/Flex'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; +type Props = { + label?: React$Element<*>, + onOpen?: () => void, + onClose?: () => void, + history: Object, + ui: UiStore, + documents: DocumentsStore, + collection: Collection, +}; + @observer class CollectionMenu extends Component { - props: { - label?: React$Element<*>, - onOpen?: () => void, - onClose?: () => void, - onImport?: () => void, - history: Object, - ui: UiStore, - collection: Collection, - }; + props: Props; + file: HTMLInputElement; onNewDocument = (ev: SyntheticEvent) => { ev.preventDefault(); @@ -26,6 +33,24 @@ class CollectionMenu extends Component { history.push(`${collection.url}/new`); }; + onImportDocument = (ev: SyntheticEvent) => { + ev.preventDefault(); + + // simulate a click on the file upload input element + this.file.click(); + }; + + onFilePicked = async (ev: SyntheticEvent) => { + const files = getDataTransferFiles(ev); + const document = await importFile({ + file: files[0], + documents: this.props.documents, + collectionId: this.props.collection.id, + }); + + this.props.history.push(document.url); + }; + onEdit = (ev: SyntheticEvent) => { ev.preventDefault(); const { collection } = this.props; @@ -39,32 +64,47 @@ class CollectionMenu extends Component { }; render() { - const { collection, label, onOpen, onClose, onImport } = this.props; + const { collection, label, onOpen, onClose } = this.props; const { allowDelete } = collection; return ( - } - onOpen={onOpen} - onClose={onClose} - > - {collection && ( - - - New document - - - Import document - - Edit… - - )} - {allowDelete && ( - Delete… - )} - + + (this.file = ref)} + onChange={this.onFilePicked} + accept="text/markdown, text/plain" + /> + } + onOpen={onOpen} + onClose={onClose} + > + {collection && ( + + + New document + + + Import document + + Edit… + + )} + {allowDelete && ( + Delete… + )} + + ); } } -export default inject('ui')(CollectionMenu); +const HiddenInput = styled.input` + position: absolute; + top: -100px; + left: -100px; + visibility: hidden; +`; + +export default inject('ui', 'documents')(CollectionMenu); diff --git a/app/scenes/Collection/Collection.js b/app/scenes/Collection/Collection.js index 74bc1b4a..9736bcf1 100644 --- a/app/scenes/Collection/Collection.js +++ b/app/scenes/Collection/Collection.js @@ -133,7 +133,10 @@ class CollectionScene extends Component { /> - + diff --git a/app/utils/importFile.js b/app/utils/importFile.js new file mode 100644 index 00000000..05b2118b --- /dev/null +++ b/app/utils/importFile.js @@ -0,0 +1,40 @@ +// @flow +import Document from '../models/Document'; +import DocumentsStore from '../stores/DocumentsStore'; + +type Options = { + file: File, + documents: DocumentsStore, + collectionId: string, + documentId?: string, +}; + +const importFile = async ({ + documents, + file, + documentId, + collectionId, +}: Options): Promise => { + return new Promise(resolve => { + const reader = new FileReader(); + + reader.onload = async ev => { + const text = ev.target.result; + let data = { + parentDocument: undefined, + collection: { id: collectionId }, + text, + }; + + if (documentId) data.parentDocument = documentId; + + let document = new Document(data); + document = await document.save(); + documents.add(document); + resolve(document); + }; + reader.readAsText(file); + }); +}; + +export default importFile;