Merge pull request #541 from outline/issue-537
Fixes Collection ... menu not working
This commit is contained in:
@ -4,10 +4,10 @@ import { observable } from 'mobx';
|
|||||||
import { observer, inject } from 'mobx-react';
|
import { observer, inject } from 'mobx-react';
|
||||||
import { injectGlobal } from 'styled-components';
|
import { injectGlobal } from 'styled-components';
|
||||||
import { color } from 'shared/styles/constants';
|
import { color } from 'shared/styles/constants';
|
||||||
|
import importFile from 'utils/importFile';
|
||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Dropzone from 'react-dropzone';
|
import Dropzone from 'react-dropzone';
|
||||||
import Document from 'models/Document';
|
|
||||||
import DocumentsStore from 'stores/DocumentsStore';
|
import DocumentsStore from 'stores/DocumentsStore';
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
|
|
||||||
@ -19,7 +19,6 @@ type Props = {
|
|||||||
rejectClassName?: string,
|
rejectClassName?: string,
|
||||||
documents: DocumentsStore,
|
documents: DocumentsStore,
|
||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
dropzoneRef: Function,
|
|
||||||
history: Object,
|
history: Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,30 +39,6 @@ class DropToImport extends Component {
|
|||||||
@observable isImporting: boolean = false;
|
@observable isImporting: boolean = false;
|
||||||
props: Props;
|
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 = []) => {
|
onDropAccepted = async (files = []) => {
|
||||||
this.isImporting = true;
|
this.isImporting = true;
|
||||||
|
|
||||||
@ -79,7 +54,16 @@ class DropToImport extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
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) {
|
} catch (err) {
|
||||||
// TODO: show error alert.
|
// TODO: show error alert.
|
||||||
@ -96,7 +80,6 @@ class DropToImport extends Component {
|
|||||||
'collectionId',
|
'collectionId',
|
||||||
'documents',
|
'documents',
|
||||||
'disabled',
|
'disabled',
|
||||||
'dropzoneRef',
|
|
||||||
'menuOpen'
|
'menuOpen'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -110,7 +93,6 @@ class DropToImport extends Component {
|
|||||||
disableClick
|
disableClick
|
||||||
disablePreview
|
disablePreview
|
||||||
multiple
|
multiple
|
||||||
ref={this.props.dropzoneRef}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{this.isImporting && <LoadingIndicator />}
|
{this.isImporting && <LoadingIndicator />}
|
||||||
|
@ -76,14 +76,9 @@ type CollectionLinkProps = {
|
|||||||
@observer
|
@observer
|
||||||
class CollectionLink extends Component {
|
class CollectionLink extends Component {
|
||||||
props: CollectionLinkProps;
|
props: CollectionLinkProps;
|
||||||
dropzoneRef;
|
|
||||||
|
|
||||||
@observable menuOpen = false;
|
@observable menuOpen = false;
|
||||||
|
|
||||||
handleImport = () => {
|
|
||||||
this.dropzoneRef.open();
|
|
||||||
};
|
|
||||||
|
|
||||||
renderDocuments() {
|
renderDocuments() {
|
||||||
const {
|
const {
|
||||||
history,
|
history,
|
||||||
@ -119,7 +114,6 @@ class CollectionLink extends Component {
|
|||||||
collectionId={collection.id}
|
collectionId={collection.id}
|
||||||
activeClassName="activeDropZone"
|
activeClassName="activeDropZone"
|
||||||
menuOpen={this.menuOpen}
|
menuOpen={this.menuOpen}
|
||||||
dropzoneRef={ref => (this.dropzoneRef = ref)}
|
|
||||||
>
|
>
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
key={collection.id}
|
key={collection.id}
|
||||||
@ -132,19 +126,16 @@ class CollectionLink extends Component {
|
|||||||
>
|
>
|
||||||
<CollectionName justify="space-between">
|
<CollectionName justify="space-between">
|
||||||
{collection.name}
|
{collection.name}
|
||||||
|
|
||||||
<CollectionAction>
|
|
||||||
<CollectionMenu
|
|
||||||
history={history}
|
|
||||||
collection={collection}
|
|
||||||
onOpen={() => (this.menuOpen = true)}
|
|
||||||
onClose={() => (this.menuOpen = false)}
|
|
||||||
onImport={this.handleImport}
|
|
||||||
open={this.menuOpen}
|
|
||||||
/>
|
|
||||||
</CollectionAction>
|
|
||||||
</CollectionName>
|
</CollectionName>
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
|
<CollectionAction>
|
||||||
|
<CollectionMenu
|
||||||
|
history={history}
|
||||||
|
collection={collection}
|
||||||
|
onOpen={() => (this.menuOpen = true)}
|
||||||
|
onClose={() => (this.menuOpen = false)}
|
||||||
|
/>
|
||||||
|
</CollectionAction>
|
||||||
</StyledDropToImport>
|
</StyledDropToImport>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -233,6 +224,7 @@ const CollectionName = styled(Flex)`
|
|||||||
const CollectionAction = styled.span`
|
const CollectionAction = styled.span`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
top: 0;
|
||||||
color: ${color.slate};
|
color: ${color.slate};
|
||||||
svg {
|
svg {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
@ -246,6 +238,8 @@ const CollectionAction = styled.span`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledDropToImport = styled(DropToImport)`
|
const StyledDropToImport = styled(DropToImport)`
|
||||||
|
position: relative;
|
||||||
|
|
||||||
${CollectionAction} {
|
${CollectionAction} {
|
||||||
display: ${props => (props.menuOpen ? 'inline' : 'none')};
|
display: ${props => (props.menuOpen ? 'inline' : 'none')};
|
||||||
}
|
}
|
||||||
@ -260,6 +254,7 @@ const StyledDropToImport = styled(DropToImport)`
|
|||||||
const CollectionChildren = styled(Flex)`
|
const CollectionChildren = styled(Flex)`
|
||||||
margin-top: -4px;
|
margin-top: -4px;
|
||||||
margin-left: 36px;
|
margin-left: 36px;
|
||||||
|
padding-bottom: 4px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const DocumentChildren = styled(Flex)`
|
const DocumentChildren = styled(Flex)`
|
||||||
|
@ -1,24 +1,31 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { inject, observer } from 'mobx-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 Collection from 'models/Collection';
|
||||||
import UiStore from 'stores/UiStore';
|
import UiStore from 'stores/UiStore';
|
||||||
|
import DocumentsStore from 'stores/DocumentsStore';
|
||||||
import MoreIcon from 'components/Icon/MoreIcon';
|
import MoreIcon from 'components/Icon/MoreIcon';
|
||||||
import Flex from 'shared/components/Flex';
|
import Flex from 'shared/components/Flex';
|
||||||
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
|
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label?: React$Element<*>,
|
||||||
|
onOpen?: () => void,
|
||||||
|
onClose?: () => void,
|
||||||
|
history: Object,
|
||||||
|
ui: UiStore,
|
||||||
|
documents: DocumentsStore,
|
||||||
|
collection: Collection,
|
||||||
|
};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class CollectionMenu extends Component {
|
class CollectionMenu extends Component {
|
||||||
props: {
|
props: Props;
|
||||||
label?: React$Element<*>,
|
file: HTMLInputElement;
|
||||||
onOpen?: () => void,
|
|
||||||
onClose?: () => void,
|
|
||||||
onImport?: () => void,
|
|
||||||
history: Object,
|
|
||||||
ui: UiStore,
|
|
||||||
collection: Collection,
|
|
||||||
};
|
|
||||||
|
|
||||||
onNewDocument = (ev: SyntheticEvent) => {
|
onNewDocument = (ev: SyntheticEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@ -26,6 +33,24 @@ class CollectionMenu extends Component {
|
|||||||
history.push(`${collection.url}/new`);
|
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) => {
|
onEdit = (ev: SyntheticEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const { collection } = this.props;
|
const { collection } = this.props;
|
||||||
@ -39,32 +64,47 @@ class CollectionMenu extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { collection, label, onOpen, onClose, onImport } = this.props;
|
const { collection, label, onOpen, onClose } = this.props;
|
||||||
const { allowDelete } = collection;
|
const { allowDelete } = collection;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<span>
|
||||||
label={label || <MoreIcon />}
|
<HiddenInput
|
||||||
onOpen={onOpen}
|
type="file"
|
||||||
onClose={onClose}
|
innerRef={ref => (this.file = ref)}
|
||||||
>
|
onChange={this.onFilePicked}
|
||||||
{collection && (
|
accept="text/markdown, text/plain"
|
||||||
<Flex column>
|
/>
|
||||||
<DropdownMenuItem onClick={this.onNewDocument}>
|
<DropdownMenu
|
||||||
New document
|
label={label || <MoreIcon />}
|
||||||
</DropdownMenuItem>
|
onOpen={onOpen}
|
||||||
<DropdownMenuItem onClick={onImport}>
|
onClose={onClose}
|
||||||
Import document
|
>
|
||||||
</DropdownMenuItem>
|
{collection && (
|
||||||
<DropdownMenuItem onClick={this.onEdit}>Edit…</DropdownMenuItem>
|
<Flex column>
|
||||||
</Flex>
|
<DropdownMenuItem onClick={this.onNewDocument}>
|
||||||
)}
|
New document
|
||||||
{allowDelete && (
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={this.onDelete}>Delete…</DropdownMenuItem>
|
<DropdownMenuItem onClick={this.onImportDocument}>
|
||||||
)}
|
Import document
|
||||||
</DropdownMenu>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={this.onEdit}>Edit…</DropdownMenuItem>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{allowDelete && (
|
||||||
|
<DropdownMenuItem onClick={this.onDelete}>Delete…</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</DropdownMenu>
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default inject('ui')(CollectionMenu);
|
const HiddenInput = styled.input`
|
||||||
|
position: absolute;
|
||||||
|
top: -100px;
|
||||||
|
left: -100px;
|
||||||
|
visibility: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default inject('ui', 'documents')(CollectionMenu);
|
||||||
|
@ -133,7 +133,10 @@ class CollectionScene extends Component {
|
|||||||
/>
|
/>
|
||||||
<Actions align="center" justify="flex-end">
|
<Actions align="center" justify="flex-end">
|
||||||
<Action>
|
<Action>
|
||||||
<CollectionMenu collection={this.collection} />
|
<CollectionMenu
|
||||||
|
history={this.props.history}
|
||||||
|
collection={this.collection}
|
||||||
|
/>
|
||||||
</Action>
|
</Action>
|
||||||
<Separator />
|
<Separator />
|
||||||
<Action>
|
<Action>
|
||||||
|
40
app/utils/importFile.js
Normal file
40
app/utils/importFile.js
Normal file
@ -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<Document> => {
|
||||||
|
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;
|
Reference in New Issue
Block a user