diff --git a/frontend/scenes/Document/components/DocumentMove/DocumentMove.js b/frontend/scenes/Document/components/DocumentMove/DocumentMove.js index 7c60ea6b..78ffcdba 100644 --- a/frontend/scenes/Document/components/DocumentMove/DocumentMove.js +++ b/frontend/scenes/Document/components/DocumentMove/DocumentMove.js @@ -1,7 +1,7 @@ // @flow import React, { Component } from 'react'; import ReactDOM from 'react-dom'; -import { observable, computed, action } from 'mobx'; +import { observable, computed } from 'mobx'; import { observer, inject } from 'mobx-react'; import { withRouter } from 'react-router'; import { Search } from 'js-search'; @@ -18,7 +18,7 @@ import PathToDocument from './components/PathToDocument'; import Document from 'models/Document'; import DocumentsStore from 'stores/DocumentsStore'; -import CollectionsStore from 'stores/CollectionsStore'; +import CollectionsStore, { type DocumentPath } from 'stores/CollectionsStore'; type Props = { match: Object, @@ -28,16 +28,6 @@ type Props = { collections: CollectionsStore, }; -type DocumentResult = { - id: string, - title: string, - type: 'document' | 'collection', -} - -type SearchResult = DocumentResult & { - path: Array, -} - @observer class DocumentMove extends Component { props: Props; firstDocument: HTMLElement; @@ -45,8 +35,7 @@ type SearchResult = DocumentResult & { @observable searchTerm: ?string; @observable isSaving: boolean; - @computed - get searchIndex() { + @computed get searchIndex() { const { document, collections } = this.props; const paths = collections.pathsToDocuments; const index = new Search('id'); @@ -55,49 +44,53 @@ type SearchResult = DocumentResult & { // Build index paths.forEach(path => { // TMP: For now, exclude paths to other collections - if (_.first(path).id !== document.collection.id) return; + if (_.first(path.path).id !== document.collection.id) return; - const tail = _.last(path); - index.addDocuments([{ - ...tail, - path: path, - }]); + index.addDocuments([path]); }); return index; } - @computed get results(): Array { + @computed get results(): Array { const { document, collections } = this.props; let results = []; if (collections.isLoaded) { if (this.searchTerm) { - // Search by + // Search by results = this.searchIndex.search(this.searchTerm); } else { // Default results, root of the current collection - results = document.collection.documents.map( - doc => collections.getPathForDocument(doc.id) - ); + results = []; + document.collection.documents.forEach(doc => { + const path = collections.getPathForDocument(doc.id); + if (doc && path) { + results.push(path); + } + }); } } - if (document.parentDocumentId) { + if (document && document.parentDocumentId) { // Add root if document does have a parent document - results = [ - collections.getPathForDocument(document.collection.id), - ...results, - ] - } else { - // Exclude root from search results if document is already at the root - results = results.filter(result => - result.id !== document.collection.id); + const rootPath = collections.getPathForDocument(document.collection.id); + if (rootPath) { + results = [rootPath, ...results]; + } + } + + // Exclude root from search results if document is already at the root + if (!document.parentDocumentId) { + results = results.filter(result => result.id !== document.collection.id); } // Exclude document if on the path to result, or the same result results = results.filter(result => { - return !result.path.map(doc => doc.id).includes(document.parentDocumentId); + return ( + !result.path.map(doc => doc.id).includes(document.id) && + !result.path.map(doc => doc.id).includes(document.parentDocumentId) + ); }); return results; @@ -126,49 +119,58 @@ type SearchResult = DocumentResult & { this.firstDocument = ref; }; + renderPathToCurrentDocument() { + const { collections, document } = this.props; + const result = collections.getPathForDocument(document.id); + if (result) { + return ; + } + } + render() { - const { document, documents, collections } = this.props; + const { document, collections } = this.props; return ( - {collections.isLoaded ? ( - -
- - - -
+ {document && collections.isLoaded + ? +
+ + {this.renderPathToCurrentDocument()} + +
-
- - - - - - {this.results.map((result, index) => ( - index === 0 && this.setFirstDocumentRef(ref)} - onClick={ () => 'move here' } - /> - ))} - - -
-
- ) :
loading
} +
+ + + + + + {this.results.map((result, index) => ( + + index === 0 && this.setFirstDocumentRef(ref)} + onSuccess={this.handleClose} + /> + ))} + + +
+
+ :
} ); } diff --git a/frontend/scenes/Document/components/DocumentMove/components/PathToDocument.js b/frontend/scenes/Document/components/DocumentMove/components/PathToDocument.js index 69b717f2..2e5bab44 100644 --- a/frontend/scenes/Document/components/DocumentMove/components/PathToDocument.js +++ b/frontend/scenes/Document/components/DocumentMove/components/PathToDocument.js @@ -1,14 +1,16 @@ // @flow import React from 'react'; import { observer } from 'mobx-react'; -import _ from 'lodash'; import invariant from 'invariant'; +import _ from 'lodash'; import styled from 'styled-components'; import { color } from 'styles/constants'; import Flex from 'components/Flex'; import ChevronIcon from 'components/Icon/ChevronIcon'; +import Document from 'models/Document'; + const ResultWrapper = styled.div` display: flex; margin-bottom: 10px; @@ -46,28 +48,41 @@ const ResultWrapperLink = ResultWrapper.withComponent('a').extend` type Props = { result: Object, - document: Document, - onClick?: Function, + document?: Document, + onSuccess?: Function, ref?: Function, }; @observer class PathToDocument extends React.Component { props: Props; - render() { - const { result, document, onClick, ref } = this.props; - // $FlowIssue we'll always have a document - const Component = onClick ? ResultWrapperLink : ResultWrapper; + handleClick = async (ev: SyntheticEvent) => { + ev.preventDefault(); + const { document, result, onSuccess } = this.props; - if (!result) return
; + invariant(onSuccess && document, 'onSuccess unavailable'); + + if (result.type === 'document') { + await document.move(result.id); + } else if ( + result.type === 'collection' && + result.id === document.collection.id + ) { + await document.move(null); + } else { + throw new Error('Not implemented yet'); + } + onSuccess(); + }; + + render() { + const { result, document, ref } = this.props; + const Component = document ? ResultWrapperLink : ResultWrapper; + + if (!result) return
; return ( - + {result.path .map(doc => {doc.title}) .reduce((prev, curr) => [prev, , curr])} diff --git a/frontend/stores/CollectionsStore.js b/frontend/stores/CollectionsStore.js index 8540e29a..7231560f 100644 --- a/frontend/stores/CollectionsStore.js +++ b/frontend/stores/CollectionsStore.js @@ -22,6 +22,16 @@ type Options = { ui: UiStore, }; +type DocumentPathItem = { + id: string, + title: string, + type: 'document' | 'collection', +}; + +export type DocumentPath = DocumentPathItem & { + path: Array, +}; + class CollectionsStore { @observable data: ObservableArray = observable.array([]); @observable isLoaded: boolean = false; @@ -41,7 +51,7 @@ class CollectionsStore { /** * List of paths to each of the documents, where paths are composed of id and title/name pairs */ - @computed get pathsToDocuments(): ?[[{ id: string, title: string }]] { + @computed get pathsToDocuments(): Array { let results = []; const travelDocuments = (documentList, path) => documentList.forEach(document => { @@ -60,17 +70,17 @@ class CollectionsStore { }); } - return results; + return results.map(result => { + const tail = _.last(result); + return { + ...tail, + path: result, + }; + }); } - getPathForDocument(documentId: string): Object { - const result = this.pathsToDocuments.find(path => _.last(path).id === documentId); - - const tail = _.last(result); - return { - ...tail, - path: result, - } + getPathForDocument(documentId: string): ?DocumentPath { + return this.pathsToDocuments.find(path => path.id === documentId); } /* Actions */ diff --git a/server/api/documents.js b/server/api/documents.js index b617b29c..9d989255 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -264,7 +264,7 @@ router.post('documents.move', auth(), async ctx => { // Set parent document if (parentDocument) { const parent = await Document.findById(parentDocument); - if (parent.atlasId !== document.atlasId) + if (!parent || parent.atlasId !== document.atlasId) throw httpErrors.BadRequest( 'Invalid parentDocument (must be same collection)' );