From 603d5f2b6b8cdd1d3ce5ca391fde578f09b1ca98 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Fri, 30 Jun 2017 22:09:22 -0700 Subject: [PATCH 01/25] Closes #92 - Sidebar now scrolls instead of squishes --- frontend/components/Layout/Layout.js | 41 +++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index b04a9546..fdc2e179 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -11,6 +11,7 @@ import { textColor } from 'styles/constants.scss'; import DropdownMenu, { MenuItem } from 'components/DropdownMenu'; import { LoadingIndicatorBar } from 'components/LoadingIndicator'; +import Scrollable from 'components/Scrollable'; import SidebarCollection from './components/SidebarCollection'; import SidebarCollectionList from './components/SidebarCollectionList'; @@ -101,21 +102,23 @@ type Props = { - - Search - - - Home - Starred - - - {ui.activeCollection - ? - : } - + + + Search + + + Home + Starred + + + {ui.activeCollection + ? + : } + + } @@ -135,7 +138,7 @@ const Container = styled(Flex)` `; const LogoLink = styled(Link)` - margin-top: 5px; + margin-top: 15px; font-family: 'Atlas Grotesk'; font-weight: bold; color: ${textColor}; @@ -166,19 +169,19 @@ const Content = styled(Flex)` const Sidebar = styled(Flex)` width: 250px; margin-left: ${props => (props.editMode ? '-250px' : 0)}; - padding: 10px 20px; background: rgba(250, 251, 252, 0.71); border-right: 1px solid #eceff3; transition: margin-left 200ms ease-in-out; `; const Header = styled(Flex)` - margin-bottom: 20px; + flex-shrink: 0; + padding: 10px 20px; `; const LinkSection = styled(Flex)` - margin-bottom: 20px; flex-direction: column; + padding: 10px 20px; `; export default withRouter(inject('user', 'auth', 'ui', 'collections')(Layout)); From 6425450617b9aef522e3618e6b0d597591bb1ad1 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 1 Jul 2017 17:02:17 -0700 Subject: [PATCH 02/25] Fixes #96 - No longer a delay between clicking on a doc and it becoming active. Note: I removed a bunch of stuff that was observing mobx state change for no reason --- .../SidebarCollection/SidebarCollection.js | 6 ++--- .../components/SidebarLink/SidebarLink.js | 26 +++++++------------ frontend/scenes/Document/Document.js | 7 ++++- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js index 82822118..2ca591be 100644 --- a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js +++ b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js @@ -1,6 +1,5 @@ // @flow import React from 'react'; -import { observer } from 'mobx-react'; import { Flex } from 'reflexbox'; import styled from 'styled-components'; @@ -8,6 +7,7 @@ import SidebarLink from '../SidebarLink'; import Collection from 'models/Collection'; import Document from 'models/Document'; +import type { NavigationNode } from 'types'; type Props = { collection: ?Collection, @@ -17,7 +17,7 @@ type Props = { class SidebarCollection extends React.Component { props: Props; - renderDocuments(documentList) { + renderDocuments(documentList: Array) { const { document } = this.props; if (document) { @@ -63,4 +63,4 @@ const Children = styled(Flex)` margin-left: 20px; `; -export default observer(SidebarCollection); +export default SidebarCollection; diff --git a/frontend/components/Layout/components/SidebarLink/SidebarLink.js b/frontend/components/Layout/components/SidebarLink/SidebarLink.js index df60708c..eed38e03 100644 --- a/frontend/components/Layout/components/SidebarLink/SidebarLink.js +++ b/frontend/components/Layout/components/SidebarLink/SidebarLink.js @@ -1,7 +1,6 @@ // @flow import React from 'react'; -import { observer } from 'mobx-react'; -import { NavLink, withRouter } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import { Flex } from 'reflexbox'; import styled from 'styled-components'; @@ -9,27 +8,20 @@ const activeStyle = { color: '#000000', }; -@observer class SidebarLink extends React.Component { - shouldComponentUpdate(nextProps) { - // Navlink is having issues updating, forcing update on URL changes - return this.props.match !== nextProps.match; - } - - render() { - return ( - - - - ); - } +function SidebarLink(props: Object) { + return ( + + + + ); } const LinkContainer = styled(Flex)` padding: 5px 0; - + a { color: #848484; } `; -export default withRouter(SidebarLink); +export default SidebarLink; diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index edcf7c25..275ba042 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -54,8 +54,13 @@ type Props = { } loadDocument = async props => { + let document = this.document; + if (document) { + this.props.ui.setActiveDocument(document); + } + await this.props.documents.fetch(props.match.params.id); - const document = this.document; + document = this.document; if (document) { this.props.ui.setActiveDocument(document); From 8dfbd1cc59f2579830406314051a25c35193e430 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 1 Jul 2017 18:40:46 -0700 Subject: [PATCH 03/25] Fixed: Bad layout on multiple nested docs --- .../Layout/components/SidebarCollection/SidebarCollection.js | 2 +- frontend/scenes/Document/Document.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js index 2ca591be..ec197e6c 100644 --- a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js +++ b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js @@ -28,7 +28,7 @@ class SidebarCollection extends React.Component { {(document.pathToDocument.includes(doc.id) || document.id === doc.id) && - + {doc.children && this.renderDocuments(doc.children)} } diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index 275ba042..b606de36 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -119,7 +119,7 @@ type Props = { render() { const isNew = this.props.newDocument || this.props.newChildDocument; const isEditing = this.props.match.params.edit; - const isFetching = !this.document && get(this.document, 'isFetching'); + const isFetching = !this.document; const titleText = get(this.document, 'title', 'Loading'); return ( From 5fa9029caa3c226ed7e7a694c7f9e2e70bf2f865 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 2 Jul 2017 22:16:48 -0700 Subject: [PATCH 04/25] Closes #103 - Scroll to top on nav --- .../components/ScrollToTop/ScrollToTop.js | 18 +++++ frontend/components/ScrollToTop/index.js | 3 + frontend/index.js | 71 ++++++++++--------- 3 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 frontend/components/ScrollToTop/ScrollToTop.js create mode 100644 frontend/components/ScrollToTop/index.js diff --git a/frontend/components/ScrollToTop/ScrollToTop.js b/frontend/components/ScrollToTop/ScrollToTop.js new file mode 100644 index 00000000..44e52f7f --- /dev/null +++ b/frontend/components/ScrollToTop/ScrollToTop.js @@ -0,0 +1,18 @@ +// @flow +// based on: https://reacttraining.com/react-router/web/guides/scroll-restoration +import { Component } from 'react'; +import { withRouter } from 'react-router'; + +class ScrollToTop extends Component { + componentDidUpdate(prevProps) { + if (this.props.location !== prevProps.location) { + window.scrollTo(0, 0); + } + } + + render() { + return this.props.children; + } +} + +export default withRouter(ScrollToTop); diff --git a/frontend/components/ScrollToTop/index.js b/frontend/components/ScrollToTop/index.js new file mode 100644 index 00000000..0f8823a1 --- /dev/null +++ b/frontend/components/ScrollToTop/index.js @@ -0,0 +1,3 @@ +// @flow +import ScrollToTop from './ScrollToTop'; +export default ScrollToTop; diff --git a/frontend/index.js b/frontend/index.js index 0f050e1a..7fe3a575 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -33,6 +33,7 @@ import Flatpage from 'scenes/Flatpage'; import ErrorAuth from 'scenes/ErrorAuth'; import Error404 from 'scenes/Error404'; +import ScrollToTop from 'components/ScrollToTop'; import Layout from 'components/Layout'; import flatpages from 'static/flatpages'; @@ -91,46 +92,48 @@ render(
- - + + + - - - + + + - - - - - - - + + + + + + + - - - + + + - - - + + + - - + + - - - - - - + + + + + + + {DevTools && } From 60bad34942da3d158ce0bf91246ed41f0f3e5665 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 2 Jul 2017 23:18:38 -0700 Subject: [PATCH 05/25] Entering edit mode should focus editor. closes #97 --- frontend/components/Editor/Editor.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 025e5a03..36a2374a 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -61,6 +61,12 @@ type KeyData = { } } + componentDidUpdate(prevProps: Props) { + if (prevProps.readOnly && !this.props.readOnly) { + this.focusAtEnd(); + } + } + getChildContext() { return { starred: this.props.starred }; } From 29037251c0b4b229f8fe78450ea2872eff71b574 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Mon, 3 Jul 2017 13:17:29 -0500 Subject: [PATCH 06/25] Better document URLs --- frontend/index.js | 20 ++++++++++++++++++-- frontend/scenes/Document/Document.js | 11 ++++++++--- server/models/Document.js | 4 ++-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/frontend/index.js b/frontend/index.js index 7fe3a575..539aa4fc 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -87,6 +87,9 @@ const KeyboardShortcuts = () => ( const Api = () => ; const DocumentNew = () => ; const DocumentNewChild = () => ; +const RedirectDocument = ({ match }: { match: Object }) => ( + +); render(
@@ -106,9 +109,22 @@ render( - + + - + { diff --git a/server/models/Document.js b/server/models/Document.js index f0f5e900..45df9bd0 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -9,7 +9,7 @@ import { convertToMarkdown } from '../../frontend/utils/markdown'; import { truncateMarkdown } from '../utils/truncate'; import Revision from './Revision'; -const URL_REGEX = /^[a-zA-Z0-9-]*-([a-zA-Z0-9]{10,15})$/; +const URL_REGEX = /^[a-zA-Z0-9-]*-([a-zA-Z]{10,15})$/; slug.defaults.mode = 'rfc3986'; const slugify = text => @@ -100,7 +100,7 @@ const Document = sequelize.define( instanceMethods: { getUrl() { const slugifiedTitle = slugify(this.title); - return `/d/${slugifiedTitle}-${this.urlId}`; + return `/${slugifiedTitle}-${this.urlId}`; }, toJSON() { // Warning: only use for new documents as order of children is From f90d17049730061ae712c0e3a0541ad1aba37e4f Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 3 Jul 2017 21:35:17 -0700 Subject: [PATCH 07/25] Using the magic of joins --- server/api/documents.js | 11 ++++++--- server/models/Document.js | 26 ++++++++++++++++++++- server/presenters/document.js | 43 +++++++---------------------------- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/server/api/documents.js b/server/api/documents.js index 5c8e3d7b..b43ba923 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -5,10 +5,9 @@ import httpErrors from 'http-errors'; import auth from './middlewares/authentication'; import pagination from './middlewares/pagination'; import { presentDocument } from '../presenters'; -import { Document, Collection, Star, View } from '../models'; +import { User, Document, Collection, Star, View } from '../models'; const router = new Router(); - router.post('documents.list', auth(), pagination(), async ctx => { let { sort = 'updatedAt', direction } = ctx.body; if (direction !== 'ASC') direction = 'DESC'; @@ -19,6 +18,7 @@ router.post('documents.list', auth(), pagination(), async ctx => { order: [[sort, direction]], offset: ctx.state.pagination.offset, limit: ctx.state.pagination.limit, + include: [{ model: Star, as: 'starred', where: { userId: user.id } }], }); let data = await Promise.all(documents.map(doc => presentDocument(ctx, doc))); @@ -60,7 +60,12 @@ router.post('documents.starred', auth(), pagination(), async ctx => { const views = await Star.findAll({ where: { userId: user.id }, order: [[sort, direction]], - include: [{ model: Document }], + include: [ + { + model: Document, + include: [{ model: Star, as: 'starred', where: { userId: user.id } }], + }, + ], offset: ctx.state.pagination.offset, limit: ctx.state.pagination.limit, }); diff --git a/server/models/Document.js b/server/models/Document.js index f0f5e900..82b5c439 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -115,7 +115,31 @@ const Document = sequelize.define( }, classMethods: { associate: models => { - Document.belongsTo(models.User); + Document.belongsTo(models.Collection, { + foreignKey: 'atlasId', + }); + Document.belongsTo(models.User, { + as: 'createdBy', + foreignKey: 'createdById', + }); + Document.belongsTo(models.User, { + as: 'updatedBy', + foreignKey: 'lastModifiedById', + }); + Document.hasMany(models.Star, { + as: 'starred', + }); + Document.addScope( + 'defaultScope', + { + include: [ + { model: models.Collection }, + { model: models.User, as: 'createdBy' }, + { model: models.User, as: 'updatedBy' }, + ], + }, + { override: true } + ); }, findById: async id => { if (isUUID(id)) { diff --git a/server/presenters/document.js b/server/presenters/document.js index 8757ed7e..124f8650 100644 --- a/server/presenters/document.js +++ b/server/presenters/document.js @@ -1,8 +1,9 @@ -import { Collection, Star, User, View } from '../models'; +// @flow +import { Star, User, Document, View } from '../models'; import presentUser from './user'; import presentCollection from './collection'; -async function present(ctx, document, options) { +async function present(ctx: Object, document: Document, options: Object = {}) { options = { includeCollection: true, includeCollaborators: true, @@ -10,8 +11,6 @@ async function present(ctx, document, options) { ...options, }; ctx.cache.set(document.id, document); - - const userId = ctx.state.user.id; const data = { id: document.id, url: document.getUrl(), @@ -20,37 +19,23 @@ async function present(ctx, document, options) { text: document.text, html: document.html, preview: document.preview, + collection: presentCollection(ctx, document.collection), createdAt: document.createdAt, - createdBy: undefined, + createdBy: presentUser(ctx, document.createdBy), updatedAt: document.updatedAt, - updatedBy: undefined, + updatedBy: presentUser(ctx, document.updatedBy), team: document.teamId, collaborators: [], + starred: !!document.starred, + views: undefined, }; - data.starred = !!await Star.findOne({ - where: { documentId: document.id, userId }, - }); - if (options.includeViews) { data.views = await View.sum('count', { where: { documentId: document.id }, }); } - if (options.includeCollection) { - data.collection = await ctx.cache.get(document.atlasId, async () => { - const collection = - options.collection || - (await Collection.findOne({ - where: { - id: document.atlasId, - }, - })); - return presentCollection(ctx, collection); - }); - } - if (options.includeCollaborators) { // This could be further optimized by using ctx.cache data.collaborators = await User.findAll({ @@ -62,18 +47,6 @@ async function present(ctx, document, options) { }).map(user => presentUser(ctx, user)); } - const createdBy = await ctx.cache.get( - document.createdById, - async () => await User.findById(document.createdById) - ); - data.createdBy = await presentUser(ctx, createdBy); - - const updatedBy = await ctx.cache.get( - document.lastModifiedById, - async () => await User.findById(document.lastModifiedById) - ); - data.updatedBy = await presentUser(ctx, updatedBy); - return data; } From f65a5bcca2fddb8d23851494697a5ae289992ab5 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Tue, 4 Jul 2017 12:03:41 -0500 Subject: [PATCH 08/25] Swapped document urls to have `/doc` prefix --- frontend/index.js | 6 +++--- server/models/Document.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/index.js b/frontend/index.js index 539aa4fc..160ef99d 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -88,7 +88,7 @@ const Api = () => ; const DocumentNew = () => ; const DocumentNewChild = () => ; const RedirectDocument = ({ match }: { match: Object }) => ( - + ); render( @@ -116,13 +116,13 @@ render( /> Date: Tue, 4 Jul 2017 20:18:14 -0700 Subject: [PATCH 09/25] Updated Document scene to use new url --- frontend/scenes/Document/Document.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index c7cf6fa2..f3cfa06c 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -79,7 +79,7 @@ type Props = { get document() { return this.props.documents.getByUrl( - `/${this.props.match.params.documentSlug}` + `/doc/${this.props.match.params.documentSlug}` ); } From 1c83257483263020b0c06a0d2542eb74f83dcf36 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 4 Jul 2017 23:02:06 -0700 Subject: [PATCH 10/25] Restore New Document Functionality (#94) * Restored New Document functionality * Declarative SidebarHidden * Fix edit route --- .../PublishingInfo/PublishingInfo.js | 35 +++++------ .../components/SidebarHidden/SidebarHidden.js | 25 ++++++++ frontend/components/SidebarHidden/index.js | 3 + frontend/index.js | 34 ++++++----- frontend/models/Document.js | 21 ++++--- frontend/scenes/Document/Document.js | 58 ++++++++++--------- frontend/scenes/Document/components/Menu.js | 5 +- server/models/Document.js | 2 +- 8 files changed, 111 insertions(+), 72 deletions(-) create mode 100644 frontend/components/SidebarHidden/SidebarHidden.js create mode 100644 frontend/components/SidebarHidden/index.js diff --git a/frontend/components/PublishingInfo/PublishingInfo.js b/frontend/components/PublishingInfo/PublishingInfo.js index 166c23fc..298e7ef6 100644 --- a/frontend/components/PublishingInfo/PublishingInfo.js +++ b/frontend/components/PublishingInfo/PublishingInfo.js @@ -45,23 +45,24 @@ class PublishingInfo extends Component { ))} } - - {this.props.createdBy.name} - {' '} - published - {' '} - {moment(this.props.createdAt).fromNow()} - {this.props.createdAt !== this.props.updatedAt - ? -  and  - {this.props.createdBy.id !== this.props.updatedBy.id && - ` ${this.props.updatedBy.name} `} - modified - {' '} - {moment(this.props.updatedAt).fromNow()} - - : null} - + {this.props.createdBy && + + {this.props.createdBy.name} + {' '} + published + {' '} + {moment(this.props.createdAt).fromNow()} + {this.props.createdAt !== this.props.updatedAt + ? +  and  + {this.props.createdBy.id !== this.props.updatedBy.id && + ` ${this.props.updatedBy.name} `} + modified + {' '} + {moment(this.props.updatedAt).fromNow()} + + : null} + } ); } diff --git a/frontend/components/SidebarHidden/SidebarHidden.js b/frontend/components/SidebarHidden/SidebarHidden.js new file mode 100644 index 00000000..adf26b44 --- /dev/null +++ b/frontend/components/SidebarHidden/SidebarHidden.js @@ -0,0 +1,25 @@ +// @flow +import { Component } from 'react'; +import { inject } from 'mobx-react'; +import UiStore from 'stores/UiStore'; + +class SidebarHidden extends Component { + props: { + ui: UiStore, + children: React$Element, + }; + + componentDidMount() { + this.props.ui.enableEditMode(); + } + + componentWillUnmount() { + this.props.ui.disableEditMode(); + } + + render() { + return this.props.children; + } +} + +export default inject('ui')(SidebarHidden); diff --git a/frontend/components/SidebarHidden/index.js b/frontend/components/SidebarHidden/index.js new file mode 100644 index 00000000..b63c0b7a --- /dev/null +++ b/frontend/components/SidebarHidden/index.js @@ -0,0 +1,3 @@ +// @flow +import SidebarHidden from './SidebarHidden'; +export default SidebarHidden; diff --git a/frontend/index.js b/frontend/index.js index 160ef99d..22843cda 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -35,6 +35,7 @@ import Error404 from 'scenes/Error404'; import ScrollToTop from 'components/ScrollToTop'; import Layout from 'components/Layout'; +import SidebarHidden from 'components/SidebarHidden'; import flatpages from 'static/flatpages'; @@ -86,11 +87,12 @@ const KeyboardShortcuts = () => ( ); const Api = () => ; const DocumentNew = () => ; -const DocumentNewChild = () => ; const RedirectDocument = ({ match }: { match: Object }) => ( ); +const matchDocumentSlug = ':documentSlug([0-9a-zA-Z-]*-[a-zA-z0-9]{10,15})'; + render(
@@ -111,26 +113,28 @@ render( - - - - + + + + + + diff --git a/frontend/models/Document.js b/frontend/models/Document.js index e19f7971..097e4563 100644 --- a/frontend/models/Document.js +++ b/frontend/models/Document.js @@ -15,7 +15,7 @@ const parseHeader = text => { }; class Document { - isSaving: boolean; + isSaving: boolean = false; hasPendingChanges: boolean = false; errors: ErrorsStore; @@ -25,10 +25,10 @@ class Document { createdBy: User; html: string; id: string; - private: boolean; - starred: boolean; team: string; - text: string; + private: boolean = false; + starred: boolean = false; + text: string = ''; title: string = 'Untitled document'; updatedAt: string; updatedBy: User; @@ -83,9 +83,9 @@ class Document { }; @action view = async () => { + this.views++; try { await client.post('/views.create', { id: this.id }); - this.views++; } catch (e) { this.errors.add('Document failed to record view'); } @@ -133,20 +133,25 @@ class Document { } invariant(res && res.data, 'Data should be available'); - this.hasPendingChanges = false; + this.updateData({ + ...res.data, + hasPendingChanges: false, + }); } catch (e) { this.errors.add('Document failed saving'); } finally { this.isSaving = false; } + + return this; }; updateData(data: Object | Document) { - data.title = parseHeader(data.text); + if (data.text) data.title = parseHeader(data.text); extendObservable(this, data); } - constructor(document: Document) { + constructor(document?: Object = {}) { this.updateData(document); this.errors = stores.errors; } diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index f3cfa06c..c37c5172 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -6,6 +6,7 @@ import { observer, inject } from 'mobx-react'; import { withRouter, Prompt } from 'react-router'; import { Flex } from 'reflexbox'; +import Document from 'models/Document'; import UiStore from 'stores/UiStore'; import DocumentsStore from 'stores/DocumentsStore'; import Menu from './components/Menu'; @@ -28,15 +29,18 @@ type Props = { history: Object, keydown: Object, documents: DocumentsStore, - newChildDocument?: boolean, + newDocument?: boolean, ui: UiStore, }; -@observer class Document extends Component { +@observer class DocumentScene extends Component { props: Props; - + state: { + newDocument?: Document, + }; state = { isLoading: false, + newDocument: undefined, }; componentDidMount() { @@ -57,27 +61,29 @@ type Props = { } loadDocument = async props => { - let document = this.document; - if (document) { - this.props.ui.setActiveDocument(document); - } - - await this.props.documents.fetch(props.match.params.documentSlug); - document = this.document; - - if (document) { - this.props.ui.setActiveDocument(document); - document.view(); - } - - if (this.props.match.params.edit) { - this.props.ui.enableEditMode(); + if (props.newDocument) { + const newDocument = new Document({ + collection: { id: props.match.params.id }, + }); + this.setState({ newDocument }); } else { - this.props.ui.disableEditMode(); + let document = this.document; + if (document) { + this.props.ui.setActiveDocument(document); + } + + await this.props.documents.fetch(props.match.params.documentSlug); + document = this.document; + + if (document) { + this.props.ui.setActiveDocument(document); + document.view(); + } } }; get document() { + if (this.state.newDocument) return this.state.newDocument; return this.props.documents.getByUrl( `/doc/${this.props.match.params.documentSlug}` ); @@ -87,19 +93,17 @@ type Props = { if (!this.document) return; const url = `${this.document.url}/edit`; this.props.history.push(url); - this.props.ui.enableEditMode(); }; onSave = async (redirect: boolean = false) => { - const document = this.document; + let document = this.document; if (!document) return; this.setState({ isLoading: true }); - await document.save(); + document = await document.save(); this.setState({ isLoading: false }); - this.props.ui.disableEditMode(); - if (redirect) { + if (redirect || this.props.newDocument) { this.props.history.push(document.url); } }; @@ -122,8 +126,8 @@ type Props = { }; render() { - const isNew = this.props.newDocument || this.props.newChildDocument; - const isEditing = this.props.match.params.edit; + const isNew = this.props.newDocument; + const isEditing = this.props.match.params.edit || isNew; const isFetching = !this.document; const titleText = get(this.document, 'title', 'Loading'); @@ -217,4 +221,4 @@ const DocumentContainer = styled.div` width: 50em; `; -export default withRouter(inject('ui', 'documents')(Document)); +export default withRouter(inject('ui', 'user', 'documents')(DocumentScene)); diff --git a/frontend/scenes/Document/components/Menu.js b/frontend/scenes/Document/components/Menu.js index 76ac51a9..a5fe0976 100644 --- a/frontend/scenes/Document/components/Menu.js +++ b/frontend/scenes/Document/components/Menu.js @@ -16,9 +16,7 @@ type Props = { props: Props; onCreateDocument = () => { - // Disabled until created a better API - // invariant(this.props.collectionTree, 'collectionTree is not available'); - // this.props.history.push(`${this.props.collectionTree.url}/new`); + this.props.history.push(`${this.props.document.collection.url}/new`); }; onCreateChild = () => { @@ -68,7 +66,6 @@ type Props = { New document - New child
} Export {allowDelete && Delete} diff --git a/server/models/Document.js b/server/models/Document.js index 206cb161..d94fe7f5 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -9,7 +9,7 @@ import { convertToMarkdown } from '../../frontend/utils/markdown'; import { truncateMarkdown } from '../utils/truncate'; import Revision from './Revision'; -const URL_REGEX = /^[a-zA-Z0-9-]*-([a-zA-Z]{10,15})$/; +const URL_REGEX = /^[a-zA-Z0-9-]*-([a-zA-Z0-9]{10,15})$/; slug.defaults.mode = 'rfc3986'; const slugify = text => From f08ca8d46004f2bf00fb09b2b0d09015a2d7bcd1 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 6 Jul 2017 20:59:45 -0700 Subject: [PATCH 11/25] Moar --- index.js | 14 +++++++------- server/api/documents.js | 8 +------- server/presenters/document.js | 8 ++++++-- server/presenters/team.js | 5 ++++- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index 0ac7c2d0..9f822e6c 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,13 @@ require('./init'); -var app = require('./server').default; -var http = require('http'); +const app = require('./server').default; +const http = require('http'); -var server = http.createServer(app.callback()); +const server = http.createServer(app.callback()); server.listen(process.env.PORT || '3000'); -server.on('error', (err) => { +server.on('error', err => { throw err; }); server.on('listening', () => { - var address = server.address(); - console.log('Listening on %s%s', address.address, address.port); -}); \ No newline at end of file + const address = server.address(); + console.log(`Listening on http://localhost:${address.port}`); +}); diff --git a/server/api/documents.js b/server/api/documents.js index b43ba923..c362f097 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -5,7 +5,7 @@ import httpErrors from 'http-errors'; import auth from './middlewares/authentication'; import pagination from './middlewares/pagination'; import { presentDocument } from '../presenters'; -import { User, Document, Collection, Star, View } from '../models'; +import { Document, Collection, Star, View } from '../models'; const router = new Router(); router.post('documents.list', auth(), pagination(), async ctx => { @@ -118,7 +118,6 @@ router.post('documents.search', auth(), async ctx => { documents.map(async document => { data.push( await presentDocument(ctx, document, { - includeCollection: true, includeCollaborators: true, }) ); @@ -206,9 +205,7 @@ router.post('documents.create', auth(), async ctx => { ctx.body = { data: await presentDocument(ctx, newDocument, { - includeCollection: true, includeCollaborators: true, - collection: ownerCollection, }), }; }); @@ -236,9 +233,7 @@ router.post('documents.update', auth(), async ctx => { ctx.body = { data: await presentDocument(ctx, document, { - includeCollection: true, includeCollaborators: true, - collection: collection, }), }; }); @@ -279,7 +274,6 @@ router.post('documents.move', auth(), async ctx => { ctx.body = { data: await presentDocument(ctx, document, { - includeCollection: true, includeCollaborators: true, collection: collection, }), diff --git a/server/presenters/document.js b/server/presenters/document.js index 124f8650..7e7cb31a 100644 --- a/server/presenters/document.js +++ b/server/presenters/document.js @@ -1,5 +1,5 @@ // @flow -import { Star, User, Document, View } from '../models'; +import { User, Document, View } from '../models'; import presentUser from './user'; import presentCollection from './collection'; @@ -19,7 +19,6 @@ async function present(ctx: Object, document: Document, options: Object = {}) { text: document.text, html: document.html, preview: document.preview, - collection: presentCollection(ctx, document.collection), createdAt: document.createdAt, createdBy: presentUser(ctx, document.createdBy), updatedAt: document.updatedAt, @@ -27,9 +26,14 @@ async function present(ctx: Object, document: Document, options: Object = {}) { team: document.teamId, collaborators: [], starred: !!document.starred, + collection: undefined, views: undefined, }; + if (options.includeCollection) { + data.collection = presentCollection(ctx, document.collection); + } + if (options.includeViews) { data.views = await View.sum('count', { where: { documentId: document.id }, diff --git a/server/presenters/team.js b/server/presenters/team.js index c418192b..6587798a 100644 --- a/server/presenters/team.js +++ b/server/presenters/team.js @@ -1,4 +1,7 @@ -function present(ctx, team) { +// @flow +import { Team } from '../models'; + +function present(ctx: Object, team: Team) { ctx.cache.set(team.id, team); return { From b854c2ca53f2dd2fedf766cf73ea66a86db1cfda Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 6 Jul 2017 22:02:55 -0700 Subject: [PATCH 12/25] Tidy, move recent documents to query scope --- .../DocumentViews/DocumentViewersStore.js | 2 +- server/api/collections.js | 18 ++++------ server/api/documents.js | 35 ++++++------------- server/models/Collection.js | 10 ++++++ server/models/Document.js | 3 +- server/presenters/collection.js | 35 +++++++------------ server/presenters/document.js | 7 ++-- 7 files changed, 46 insertions(+), 64 deletions(-) diff --git a/frontend/components/DocumentViews/DocumentViewersStore.js b/frontend/components/DocumentViews/DocumentViewersStore.js index 8fda9223..0f620ffb 100644 --- a/frontend/components/DocumentViews/DocumentViewersStore.js +++ b/frontend/components/DocumentViews/DocumentViewersStore.js @@ -18,7 +18,7 @@ class DocumentViewersStore { this.isFetching = true; try { - const res = await client.get( + const res = await client.post( '/views.list', { id: this.documentId, diff --git a/server/api/collections.js b/server/api/collections.js index b129c085..1872fd6c 100644 --- a/server/api/collections.js +++ b/server/api/collections.js @@ -24,7 +24,7 @@ router.post('collections.create', auth(), async ctx => { }); ctx.body = { - data: await presentCollection(ctx, atlas, true), + data: await presentCollection(ctx, atlas), }; }); @@ -33,7 +33,7 @@ router.post('collections.info', auth(), async ctx => { ctx.assertPresent(id, 'id is required'); const user = ctx.state.user; - const atlas = await Collection.findOne({ + const atlas = await Collection.scope('withRecentDocuments').findOne({ where: { id, teamId: user.teamId, @@ -43,13 +43,13 @@ router.post('collections.info', auth(), async ctx => { if (!atlas) throw httpErrors.NotFound(); ctx.body = { - data: await presentCollection(ctx, atlas, true), + data: await presentCollection(ctx, atlas), }; }); router.post('collections.list', auth(), pagination(), async ctx => { const user = ctx.state.user; - const collections = await Collection.findAll({ + const collections = await Collection.scope('withRecentDocuments').findAll({ where: { teamId: user.teamId, }, @@ -58,16 +58,10 @@ router.post('collections.list', auth(), pagination(), async ctx => { limit: ctx.state.pagination.limit, }); - // Collectiones - let data = []; - await Promise.all( - collections.map(async atlas => { - return data.push(await presentCollection(ctx, atlas, true)); - }) + const data = await Promise.all( + collections.map(async atlas => await presentCollection(ctx, atlas)) ); - data = _.orderBy(data, ['updatedAt'], ['desc']); - ctx.body = { pagination: ctx.state.pagination, data, diff --git a/server/api/documents.js b/server/api/documents.js index c362f097..cf525861 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -21,7 +21,9 @@ router.post('documents.list', auth(), pagination(), async ctx => { include: [{ model: Star, as: 'starred', where: { userId: user.id } }], }); - let data = await Promise.all(documents.map(doc => presentDocument(ctx, doc))); + const data = await Promise.all( + documents.map(document => presentDocument(ctx, document)) + ); ctx.body = { pagination: ctx.state.pagination, @@ -42,7 +44,7 @@ router.post('documents.viewed', auth(), pagination(), async ctx => { limit: ctx.state.pagination.limit, }); - let data = await Promise.all( + const data = await Promise.all( views.map(view => presentDocument(ctx, view.document)) ); @@ -70,7 +72,7 @@ router.post('documents.starred', auth(), pagination(), async ctx => { limit: ctx.state.pagination.limit, }); - let data = await Promise.all( + const data = await Promise.all( views.map(view => presentDocument(ctx, view.document)) ); @@ -99,8 +101,7 @@ router.post('documents.info', auth(), async ctx => { ctx.body = { data: await presentDocument(ctx, document, { - includeCollection: document.private, - includeCollaborators: true, + includeViews: true, }), }; }); @@ -113,15 +114,8 @@ router.post('documents.search', auth(), async ctx => { const documents = await Document.searchForUser(user, query); - const data = []; - await Promise.all( - documents.map(async document => { - data.push( - await presentDocument(ctx, document, { - includeCollaborators: true, - }) - ); - }) + const data = await Promise.all( + documents.map(async document => await presentDocument(ctx, document)) ); ctx.body = { @@ -204,9 +198,7 @@ router.post('documents.create', auth(), async ctx => { } ctx.body = { - data: await presentDocument(ctx, newDocument, { - includeCollaborators: true, - }), + data: await presentDocument(ctx, newDocument), }; }); @@ -232,9 +224,7 @@ router.post('documents.update', auth(), async ctx => { } ctx.body = { - data: await presentDocument(ctx, document, { - includeCollaborators: true, - }), + data: await presentDocument(ctx, document), }; }); @@ -273,10 +263,7 @@ router.post('documents.move', auth(), async ctx => { } ctx.body = { - data: await presentDocument(ctx, document, { - includeCollaborators: true, - collection: collection, - }), + data: await presentDocument(ctx, document), }; }); diff --git a/server/models/Collection.js b/server/models/Collection.js index 953a57ae..382b5476 100644 --- a/server/models/Collection.js +++ b/server/models/Collection.js @@ -60,6 +60,16 @@ const Collection = sequelize.define( as: 'documents', foreignKey: 'atlasId', }); + Collection.addScope('withRecentDocuments', { + include: [ + { + as: 'documents', + limit: 10, + model: models.Document, + order: [['updatedAt', 'DESC']], + }, + ], + }); }, }, instanceMethods: { diff --git a/server/models/Document.js b/server/models/Document.js index 82b5c439..121b2523 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -116,6 +116,7 @@ const Document = sequelize.define( classMethods: { associate: models => { Document.belongsTo(models.Collection, { + as: 'collection', foreignKey: 'atlasId', }); Document.belongsTo(models.User, { @@ -133,7 +134,7 @@ const Document = sequelize.define( 'defaultScope', { include: [ - { model: models.Collection }, + { model: models.Collection, as: 'collection' }, { model: models.User, as: 'createdBy' }, { model: models.User, as: 'updatedBy' }, ], diff --git a/server/presenters/collection.js b/server/presenters/collection.js index 8dc1777d..a6fdbc8e 100644 --- a/server/presenters/collection.js +++ b/server/presenters/collection.js @@ -1,8 +1,9 @@ +// @flow import _ from 'lodash'; -import { Document } from '../models'; +import { Collection } from '../models'; import presentDocument from './document'; -async function present(ctx, collection, includeRecentDocuments = false) { +async function present(ctx: Object, collection: Collection) { ctx.cache.set(collection.id, collection); const data = { @@ -13,31 +14,21 @@ async function present(ctx, collection, includeRecentDocuments = false) { type: collection.type, createdAt: collection.createdAt, updatedAt: collection.updatedAt, + recentDocuments: undefined, + documents: undefined, }; - if (collection.type === 'atlas') + if (collection.type === 'atlas') { data.documents = await collection.getDocumentsStructure(); + } - if (includeRecentDocuments) { - const documents = await Document.findAll({ - where: { - atlasId: collection.id, - }, - limit: 10, - order: [['updatedAt', 'DESC']], - }); - - const recentDocuments = []; - await Promise.all( - documents.map(async document => { - recentDocuments.push( - await presentDocument(ctx, document, { - includeCollaborators: true, - }) - ); - }) + if (collection.documents) { + data.recentDocuments = await Promise.all( + collection.documents.map( + async document => + await presentDocument(ctx, document, { includeCollaborators: true }) + ) ); - data.recentDocuments = _.orderBy(recentDocuments, ['updatedAt'], ['desc']); } return data; diff --git a/server/presenters/document.js b/server/presenters/document.js index 7e7cb31a..80db7f53 100644 --- a/server/presenters/document.js +++ b/server/presenters/document.js @@ -5,9 +5,8 @@ import presentCollection from './collection'; async function present(ctx: Object, document: Document, options: Object = {}) { options = { - includeCollection: true, includeCollaborators: true, - includeViews: true, + includeViews: false, ...options, }; ctx.cache.set(document.id, document); @@ -30,8 +29,8 @@ async function present(ctx: Object, document: Document, options: Object = {}) { views: undefined, }; - if (options.includeCollection) { - data.collection = presentCollection(ctx, document.collection); + if (document.private) { + data.collection = await presentCollection(ctx, document.collection); } if (options.includeViews) { From c618b956d21e1ac3950929767df031e7c544eff1 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 6 Jul 2017 22:17:37 -0700 Subject: [PATCH 13/25] Incorporate limit --- server/middlewares/cache.js | 3 ++- server/presenters/document.js | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/middlewares/cache.js b/server/middlewares/cache.js index 7a10ad64..22068f62 100644 --- a/server/middlewares/cache.js +++ b/server/middlewares/cache.js @@ -1,9 +1,10 @@ +// @flow import debug from 'debug'; const debugCache = debug('cache'); export default function cache() { - return async function cacheMiddleware(ctx, next) { + return async function cacheMiddleware(ctx: Object, next: Function) { ctx.cache = {}; ctx.cache.set = async (id, value) => { diff --git a/server/presenters/document.js b/server/presenters/document.js index 80db7f53..e89658a5 100644 --- a/server/presenters/document.js +++ b/server/presenters/document.js @@ -1,4 +1,5 @@ // @flow +import _ from 'lodash'; import { User, Document, View } from '../models'; import presentUser from './user'; import presentCollection from './collection'; @@ -43,9 +44,7 @@ async function present(ctx: Object, document: Document, options: Object = {}) { // This could be further optimized by using ctx.cache data.collaborators = await User.findAll({ where: { - id: { - $in: document.collaboratorIds || [], - }, + id: { $in: _.takeRight(document.collaboratorIds, 10) || [] }, }, }).map(user => presentUser(ctx, user)); } From ff133f373c65082167928c1e4041f4726da7243b Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 6 Jul 2017 22:20:24 -0700 Subject: [PATCH 14/25] Remove recentDocuments from default collections list response --- server/api/collections.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/collections.js b/server/api/collections.js index 1872fd6c..2b48f40f 100644 --- a/server/api/collections.js +++ b/server/api/collections.js @@ -49,7 +49,7 @@ router.post('collections.info', auth(), async ctx => { router.post('collections.list', auth(), pagination(), async ctx => { const user = ctx.state.user; - const collections = await Collection.scope('withRecentDocuments').findAll({ + const collections = await Collection.findAll({ where: { teamId: user.teamId, }, From 6656689c2d416aa11a297a549532d3dcc48e5314 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Thu, 6 Jul 2017 22:34:38 -0700 Subject: [PATCH 15/25] remove ignore-loader --- package.json | 1 - webpack.config.js | 7 ------- yarn.lock | 4 ---- 3 files changed, 12 deletions(-) diff --git a/package.json b/package.json index 3ce6affa..d8cf22da 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,6 @@ "fetch-test-server": "^1.1.0", "flow-bin": "^0.45.0", "identity-obj-proxy": "^3.0.0", - "ignore-loader": "0.1.1", "jest-cli": "^20.0.0", "koa-webpack-dev-middleware": "1.4.5", "koa-webpack-hot-middleware": "1.0.3", diff --git a/webpack.config.js b/webpack.config.js index 4d69a7a4..54b66792 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -36,13 +36,6 @@ module.exports = { loader: 'url-loader?limit=1&mimetype=application/font-woff&name=public/fonts/[name].[ext]', }, { test: /\.md/, loader: 'raw-loader' }, - - // Excludes - { - // slug - test: /unicode/, - loader: 'ignore-loader', - }, ], }, resolve: { diff --git a/yarn.lock b/yarn.lock index 27043076..d8865ca3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3970,10 +3970,6 @@ ignore-by-default@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" -ignore-loader@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ignore-loader/-/ignore-loader-0.1.1.tgz#187c846c661afcdee269ef4c3f42c73888903334" - ignore@^3.2.0: version "3.2.7" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.7.tgz#4810ca5f1d8eca5595213a34b94f2eb4ed926bbd" From 948bbd6e92bab67c228a95482c0bfcf63f2b2f15 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Thu, 6 Jul 2017 23:11:11 -0700 Subject: [PATCH 16/25] rm cross-env, fixed deployments --- init.js | 2 +- package.json | 8 +++--- server/static/dev.html | 53 ++++++++++++++++++++++------------------ server/static/index.html | 42 ++++++++++++++++++++----------- yarn.lock | 30 +++-------------------- 5 files changed, 64 insertions(+), 71 deletions(-) diff --git a/init.js b/init.js index 2905afe7..8c5d982b 100644 --- a/init.js +++ b/init.js @@ -3,4 +3,4 @@ require('safestart')(__dirname, { }); require('babel-core/register'); require('babel-polyfill'); -require('localenv'); +require('dotenv').config({ silent: true }); diff --git a/package.json b/package.json index d8cf22da..e50ef439 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,11 @@ "main": "index.js", "scripts": { "clean": "rimraf dist", - "build:webpack": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js", - "build:analyze": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js --json | webpack-bundle-size-analyzer", + "build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js", + "build:analyze": "NODE_ENV=production webpack --config webpack.config.prod.js --json | webpack-bundle-size-analyzer", "build": "npm run clean && npm run build:webpack", "start": "node index.js", - "dev": "cross-env NODE_ENV=development DEBUG=sql,cache,presenters ./node_modules/.bin/nodemon --watch server index.js", + "dev": "NODE_ENV=development DEBUG=sql,cache,presenters ./node_modules/.bin/nodemon --watch server index.js", "lint": "npm run lint:js && npm run lint:flow", "lint:js": "eslint frontend", "lint:flow": "flow check", @@ -80,7 +80,6 @@ "boundless-popover": "^1.0.4", "bugsnag": "^1.7.0", "classnames": "2.2.3", - "cross-env": "1.0.7", "css-loader": "0.23.1", "debug": "2.2.0", "dotenv": "^4.0.0", @@ -119,7 +118,6 @@ "koa-mount": "^3.0.0", "koa-router": "7.0.1", "koa-sendfile": "2.0.0", - "localenv": "0.2.2", "lodash": "^4.17.4", "lodash.orderby": "4.4.0", "marked": "0.3.6", diff --git a/server/static/dev.html b/server/static/dev.html index 04b8c94d..1933c80b 100644 --- a/server/static/dev.html +++ b/server/static/dev.html @@ -1,28 +1,33 @@ - - Atlas - - - - -
- - - + body { + display: flex; + width: 100%; + height: 100%; + } + + #root { + flex: 1; + height: 100%; + } + + + + +
+ + + + \ No newline at end of file diff --git a/server/static/index.html b/server/static/index.html index 01e0eca1..0b3a902c 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -1,16 +1,30 @@ - - Atlas - - - - - + + + Atlas + + + + +
+ + + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d8865ca3..28fe5d82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1639,10 +1639,6 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.5.0.tgz#d777b6a4d847d423e5d475da864294ac1ff5aa9d" - commander@2.8.x: version "2.8.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" @@ -1899,20 +1895,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.2: create-hash "^1.1.0" inherits "^2.0.1" -cross-env@1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-1.0.7.tgz#dd6cea13b31df4ffab4591343e605e370182647e" - dependencies: - cross-spawn-async "2.0.0" - lodash.assign "^3.2.0" - -cross-spawn-async@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.0.0.tgz#4af143df4156900d012be41cabf4da3abfc797c0" - dependencies: - lru-cache "^2.6.5" - which "^1.1.1" - cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -5161,12 +5143,6 @@ loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.14, loader-utils@^0. json5 "^0.5.0" object-assign "^4.0.1" -localenv@0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/localenv/-/localenv-0.2.2.tgz#c508f29d3485bdc9341d3ead17f61c5abd1b0bab" - dependencies: - commander "2.5.0" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -5289,7 +5265,7 @@ lodash._topath@^3.0.0: dependencies: lodash.isarray "^3.0.0" -lodash.assign@^3.0.0, lodash.assign@^3.2.0: +lodash.assign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-3.2.0.tgz#3ce9f0234b4b2223e296b8fa0ac1fee8ebca64fa" dependencies: @@ -5560,7 +5536,7 @@ lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" -lru-cache@2, lru-cache@^2.6.5: +lru-cache@2: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" @@ -8965,7 +8941,7 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" -which@1, which@^1.0.5, which@^1.1.1, which@^1.2.10, which@^1.2.12, which@^1.2.9: +which@1, which@^1.0.5, which@^1.2.10, which@^1.2.12, which@^1.2.9: version "1.2.14" resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" dependencies: From 10b746e140987791149b798db211c70212e1bfbd Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Thu, 6 Jul 2017 23:15:49 -0700 Subject: [PATCH 17/25] rm localenv --- .sequelizerc | 2 +- server/test/helper.js | 2 +- webpack.config.prod.js | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.sequelizerc b/.sequelizerc index 0f26a4e1..aa05bd02 100644 --- a/.sequelizerc +++ b/.sequelizerc @@ -1,4 +1,4 @@ -require('localenv'); +require('dotenv').config({ silent: true }); var path = require('path'); diff --git a/server/test/helper.js b/server/test/helper.js index ed178251..51171e1e 100644 --- a/server/test/helper.js +++ b/server/test/helper.js @@ -1,4 +1,4 @@ -require('localenv'); +require('dotenv').config({ silent: true }); // test environment variables process.env.DATABASE_URL = process.env.DATABASE_URL_TEST; diff --git a/webpack.config.prod.js b/webpack.config.prod.js index 1c460696..2658cbd8 100644 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -41,12 +41,5 @@ productionWebpackConfig.plugins.push( }, }) ); -productionWebpackConfig.plugins.push( - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production'), - }, - }) -); module.exports = productionWebpackConfig; From 920aee223bbee6bdbc49bf031683c3ef6191ade0 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Tue, 4 Jul 2017 12:21:11 -0500 Subject: [PATCH 18/25] removed unused css file --- frontend/scenes/Flatpage/Flatpage.scss | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 frontend/scenes/Flatpage/Flatpage.scss diff --git a/frontend/scenes/Flatpage/Flatpage.scss b/frontend/scenes/Flatpage/Flatpage.scss deleted file mode 100644 index e69de29b..00000000 From 53e4a94c079449d48bd78c974a23edb1b107daa7 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Tue, 4 Jul 2017 12:22:23 -0500 Subject: [PATCH 19/25] simplified CenteredContent and fixed alignment issues --- .../CenteredContent/CenteredContent.js | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/frontend/components/CenteredContent/CenteredContent.js b/frontend/components/CenteredContent/CenteredContent.js index 2d6cdd5a..6f135e1e 100644 --- a/frontend/components/CenteredContent/CenteredContent.js +++ b/frontend/components/CenteredContent/CenteredContent.js @@ -4,8 +4,6 @@ import styled from 'styled-components'; type Props = { children?: React.Element, - style?: Object, - maxWidth?: string, }; const Container = styled.div` @@ -13,20 +11,17 @@ const Container = styled.div` margin: 40px 20px; `; -const CenteredContent = ({ - children, - maxWidth = '740px', - style, - ...rest -}: Props) => { - const styles = { - maxWidth, - ...style, - }; +const Content = styled.div` + max-width: 740px; + margin: 0 auto; +`; +const CenteredContent = ({ children, ...rest }: Props) => { return ( - - {children} + + + {children} + ); }; From 8b3b3222b781cfcb0cfd6b45ecb2b9d75e4cb116 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Tue, 4 Jul 2017 20:15:01 -0700 Subject: [PATCH 20/25] Swapped Flex to homegrown component No more element prop warnings! --- frontend/components/Alert/Alert.js | 2 +- .../components/DocumentViews/DocumentViews.js | 2 +- .../DocumentViewers/DocumentViewers.js | 2 +- frontend/components/Flex/Flex.js | 64 +++++++++++++++++++ frontend/components/Flex/index.js | 3 + frontend/components/Input/Input.js | 2 +- frontend/components/Layout/Layout.js | 2 +- .../SidebarCollection/SidebarCollection.js | 2 +- .../SidebarCollectionList.js | 2 +- .../components/SidebarLink/SidebarLink.js | 2 +- .../PreviewLoading/PreviewLoading.js | 2 +- .../PublishingInfo/PublishingInfo.js | 2 +- frontend/index.js | 2 +- frontend/scenes/Document/Document.js | 2 +- frontend/scenes/Home/Home.js | 2 +- frontend/scenes/Search/Search.js | 2 +- .../components/SearchField/SearchField.js | 2 +- frontend/scenes/Settings/Settings.js | 2 +- package.json | 1 - 19 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 frontend/components/Flex/Flex.js create mode 100644 frontend/components/Flex/index.js diff --git a/frontend/components/Alert/Alert.js b/frontend/components/Alert/Alert.js index 83022756..70c2081d 100644 --- a/frontend/components/Alert/Alert.js +++ b/frontend/components/Alert/Alert.js @@ -1,6 +1,6 @@ // @flow import React, { PropTypes } from 'react'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import classNames from 'classnames/bind'; import styles from './Alert.scss'; diff --git a/frontend/components/DocumentViews/DocumentViews.js b/frontend/components/DocumentViews/DocumentViews.js index 7d0c67d0..0ff1b2d1 100644 --- a/frontend/components/DocumentViews/DocumentViews.js +++ b/frontend/components/DocumentViews/DocumentViews.js @@ -5,7 +5,7 @@ import Popover from 'components/Popover'; import styled from 'styled-components'; import DocumentViewers from './components/DocumentViewers'; import DocumentViewersStore from './DocumentViewersStore'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; const Container = styled(Flex)` font-size: 13px; diff --git a/frontend/components/DocumentViews/components/DocumentViewers/DocumentViewers.js b/frontend/components/DocumentViews/components/DocumentViewers/DocumentViewers.js index ecd4289f..e545a8d3 100644 --- a/frontend/components/DocumentViews/components/DocumentViewers/DocumentViewers.js +++ b/frontend/components/DocumentViews/components/DocumentViewers/DocumentViewers.js @@ -1,6 +1,6 @@ // @flow import React, { Component } from 'react'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import styled from 'styled-components'; import map from 'lodash/map'; import Avatar from 'components/Avatar'; diff --git a/frontend/components/Flex/Flex.js b/frontend/components/Flex/Flex.js new file mode 100644 index 00000000..be6c9a94 --- /dev/null +++ b/frontend/components/Flex/Flex.js @@ -0,0 +1,64 @@ +// @flow +import React from 'react'; +import styled from 'styled-components'; + +type JustifyValues = + | 'center' + | 'space-around' + | 'space-between' + | 'flex-start' + | 'flex-end'; + +type AlignValues = + | 'stretch' + | 'center' + | 'baseline' + | 'flex-start' + | 'flex-end'; + +type Props = { + column?: ?boolean, + align?: AlignValues, + justify?: JustifyValues, + auto?: ?boolean, + className?: string, + children?: React.Element, +}; + +const Flex = (props: Props) => { + const { children, ...restProps } = props; + return {children}; +}; + +const Container = styled.div` + display: flex; + flex: ${({ auto }) => (auto ? '1 1 auto' : 'initial')}; + flex-direction: ${({ column }) => (column ? 'column' : 'row')}; + align-items: ${({ align }) => align}; + justify-content: ${({ justify }) => justify}; +`; + +export default Flex; + +// flex: React.PropTypes.bool, +// wrap: React.PropTypes.bool, +// flexColumn: React.PropTypes.bool, +// column: React.PropTypes.bool, +// align: React.PropTypes.oneOf([ +// 'stretch', +// 'center', +// 'baseline', +// 'flex-start', +// 'flex-end' +// ]), +// justify: React.PropTypes.oneOf([ +// 'center', +// 'space-around', +// 'space-between', +// 'flex-start', +// 'flex-end' +// ]), +// flexAuto: React.PropTypes.bool, +// auto: React.PropTypes.bool, +// flexNone: React.PropTypes.bool, +// order: React.PropTypes.number, diff --git a/frontend/components/Flex/index.js b/frontend/components/Flex/index.js new file mode 100644 index 00000000..d798e8cd --- /dev/null +++ b/frontend/components/Flex/index.js @@ -0,0 +1,3 @@ +// @flow +import Flex from './Flex'; +export default Flex; diff --git a/frontend/components/Input/Input.js b/frontend/components/Input/Input.js index da09d541..9ac8641e 100644 --- a/frontend/components/Input/Input.js +++ b/frontend/components/Input/Input.js @@ -1,7 +1,7 @@ // @flow import React from 'react'; import styled from 'styled-components'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import { size } from 'styles/constants'; const RealTextarea = styled.textarea` diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index fdc2e179..cb0b7648 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -6,7 +6,7 @@ import styled from 'styled-components'; import { observer, inject } from 'mobx-react'; import _ from 'lodash'; import keydown from 'react-keydown'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import { textColor } from 'styles/constants.scss'; import DropdownMenu, { MenuItem } from 'components/DropdownMenu'; diff --git a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js index ec197e6c..3b9329e3 100644 --- a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js +++ b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js @@ -1,6 +1,6 @@ // @flow import React from 'react'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import styled from 'styled-components'; import SidebarLink from '../SidebarLink'; diff --git a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js index 0115ec66..0f865e3d 100644 --- a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js +++ b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js @@ -1,7 +1,7 @@ // @flow import React from 'react'; import { observer, inject } from 'mobx-react'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import styled from 'styled-components'; import SidebarLink from '../SidebarLink'; diff --git a/frontend/components/Layout/components/SidebarLink/SidebarLink.js b/frontend/components/Layout/components/SidebarLink/SidebarLink.js index eed38e03..1e73d5e1 100644 --- a/frontend/components/Layout/components/SidebarLink/SidebarLink.js +++ b/frontend/components/Layout/components/SidebarLink/SidebarLink.js @@ -1,7 +1,7 @@ // @flow import React from 'react'; import { NavLink } from 'react-router-dom'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import styled from 'styled-components'; const activeStyle = { diff --git a/frontend/components/PreviewLoading/PreviewLoading.js b/frontend/components/PreviewLoading/PreviewLoading.js index 43ddae04..4d6eec02 100644 --- a/frontend/components/PreviewLoading/PreviewLoading.js +++ b/frontend/components/PreviewLoading/PreviewLoading.js @@ -2,7 +2,7 @@ import React from 'react'; import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; import styled, { keyframes } from 'styled-components'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import { randomInteger } from 'utils/random'; diff --git a/frontend/components/PublishingInfo/PublishingInfo.js b/frontend/components/PublishingInfo/PublishingInfo.js index 298e7ef6..98f2bda9 100644 --- a/frontend/components/PublishingInfo/PublishingInfo.js +++ b/frontend/components/PublishingInfo/PublishingInfo.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import moment from 'moment'; import styled from 'styled-components'; import type { User } from 'types'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; const Container = styled(Flex)` justify-content: space-between; diff --git a/frontend/index.js b/frontend/index.js index 22843cda..065c1ff8 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -8,7 +8,7 @@ import { Route, Redirect, } from 'react-router-dom'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import stores from 'stores'; import DocumentsStore from 'stores/DocumentsStore'; diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index c37c5172..f6598e62 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -4,7 +4,7 @@ import get from 'lodash/get'; import styled from 'styled-components'; import { observer, inject } from 'mobx-react'; import { withRouter, Prompt } from 'react-router'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import Document from 'models/Document'; import UiStore from 'stores/UiStore'; diff --git a/frontend/scenes/Home/Home.js b/frontend/scenes/Home/Home.js index 022f1b94..549fb443 100644 --- a/frontend/scenes/Home/Home.js +++ b/frontend/scenes/Home/Home.js @@ -2,7 +2,7 @@ import React from 'react'; import { observer, inject } from 'mobx-react'; import { Redirect } from 'react-router'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import styled from 'styled-components'; import AuthStore from 'stores/AuthStore'; diff --git a/frontend/scenes/Search/Search.js b/frontend/scenes/Search/Search.js index 34c35b22..c94eaf4f 100644 --- a/frontend/scenes/Search/Search.js +++ b/frontend/scenes/Search/Search.js @@ -3,7 +3,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { observer } from 'mobx-react'; import _ from 'lodash'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import { withRouter } from 'react-router'; import { searchUrl } from 'utils/routeHelpers'; import styled from 'styled-components'; diff --git a/frontend/scenes/Search/components/SearchField/SearchField.js b/frontend/scenes/Search/components/SearchField/SearchField.js index 9046e1db..785a3a49 100644 --- a/frontend/scenes/Search/components/SearchField/SearchField.js +++ b/frontend/scenes/Search/components/SearchField/SearchField.js @@ -1,6 +1,6 @@ // @flow import React, { Component } from 'react'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import styled from 'styled-components'; import searchImg from 'assets/icons/search.svg'; diff --git a/frontend/scenes/Settings/Settings.js b/frontend/scenes/Settings/Settings.js index 9c27d69e..38204d7e 100644 --- a/frontend/scenes/Settings/Settings.js +++ b/frontend/scenes/Settings/Settings.js @@ -1,7 +1,7 @@ // @flow import React from 'react'; import { observer } from 'mobx-react'; -import { Flex } from 'reflexbox'; +import Flex from 'components/Flex'; import ApiKeyRow from './components/ApiKeyRow'; import styles from './Settings.scss'; diff --git a/package.json b/package.json index e50ef439..d068d9d5 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,6 @@ "react-router-dom": "^4.1.1", "redis": "^2.6.2", "redis-lock": "^0.1.0", - "reflexbox": "^2.2.3", "rimraf": "^2.5.4", "safestart": "1.1.0", "sass-loader": "4.0.0", From fc4d9cb0b5f7e6917fb990d56e106ba2bb96d773 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Fri, 7 Jul 2017 00:03:59 -0700 Subject: [PATCH 21/25] removed comments --- frontend/components/Flex/Flex.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/frontend/components/Flex/Flex.js b/frontend/components/Flex/Flex.js index be6c9a94..d14c0ce1 100644 --- a/frontend/components/Flex/Flex.js +++ b/frontend/components/Flex/Flex.js @@ -39,26 +39,3 @@ const Container = styled.div` `; export default Flex; - -// flex: React.PropTypes.bool, -// wrap: React.PropTypes.bool, -// flexColumn: React.PropTypes.bool, -// column: React.PropTypes.bool, -// align: React.PropTypes.oneOf([ -// 'stretch', -// 'center', -// 'baseline', -// 'flex-start', -// 'flex-end' -// ]), -// justify: React.PropTypes.oneOf([ -// 'center', -// 'space-around', -// 'space-between', -// 'flex-start', -// 'flex-end' -// ]), -// flexAuto: React.PropTypes.bool, -// auto: React.PropTypes.bool, -// flexNone: React.PropTypes.bool, -// order: React.PropTypes.number, From dbcfe1199e233238bb7ced12ed7fc93151f347e3 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Fri, 7 Jul 2017 00:14:00 -0700 Subject: [PATCH 22/25] Positioned document loading state with the document content --- frontend/components/PreviewLoading/PreviewLoading.js | 4 ++-- frontend/scenes/Document/Document.js | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/components/PreviewLoading/PreviewLoading.js b/frontend/components/PreviewLoading/PreviewLoading.js index 4d6eec02..150e1c8e 100644 --- a/frontend/components/PreviewLoading/PreviewLoading.js +++ b/frontend/components/PreviewLoading/PreviewLoading.js @@ -11,7 +11,7 @@ const randomValues = Array.from( () => `${randomInteger(85, 100)}%` ); -export default () => { +export default (props: {}) => { return ( { transitionEnterTimeout={0} transitionLeaveTimeout={0} > - + diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index f6598e62..e653ab27 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -137,7 +137,7 @@ type Props = { {this.state.isLoading && } {isFetching && - + } {!isFetching && this.document && @@ -208,6 +208,10 @@ const Container = styled(Flex)` width: 100%; `; +const LoadingState = styled(PreviewLoading)` + margin: 80px 20px; +`; + const PagePadding = styled(Flex)` padding: 80px 20px; position: relative; From b64e47f42ad4318091092916c544bf320716e6db Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Sat, 8 Jul 2017 21:13:47 -0700 Subject: [PATCH 23/25] Circle CI (#113) Circle CI --- circle.yml | 24 +++++++ frontend/models/Document.test.js | 3 +- server/.jestconfig.json | 5 +- server/config/database.json | 2 +- ...-collection-documentStructure-migration.js | 14 ++-- server/migrations/20170604052346-add-views.js | 70 ++++++++++--------- server/migrations/20170604052347-add-stars.js | 60 ++++++++-------- server/test/helper.js | 3 +- 8 files changed, 107 insertions(+), 74 deletions(-) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000..51020211 --- /dev/null +++ b/circle.yml @@ -0,0 +1,24 @@ +machine: + node: + version: 7.6 + services: + - redis + environment: + ENVIRONMENT: test + PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" + SEQUELIZE_SECRET: F0E5AD933D7F6FD8F4DBB3E038C501C052DC0593C686D21ACB30AE205D2F634B + DATABASE_URL_TEST: postgres://ubuntu@localhost:5432/circle_test + DATABASE_URL: postgres://ubuntu@localhost:5432/circle_test + +dependencies: + override: + - yarn + cache_directories: + - ~/.cache/yarn + +test: + pre: + - sequelize db:migrate --url postgres://ubuntu@localhost:5432/circle_test + override: + - yarn test + - yarn lint diff --git a/frontend/models/Document.test.js b/frontend/models/Document.test.js index e7db05b7..f8432706 100644 --- a/frontend/models/Document.test.js +++ b/frontend/models/Document.test.js @@ -5,8 +5,7 @@ describe('Document model', () => { test('should initialize with data', () => { const document = new Document({ id: 123, - title: 'Onboarding', - text: 'Some body text' + text: '# Onboarding\nSome body text', }); expect(document.title).toBe('Onboarding'); }); diff --git a/server/.jestconfig.json b/server/.jestconfig.json index 56da3efd..383aded9 100644 --- a/server/.jestconfig.json +++ b/server/.jestconfig.json @@ -5,8 +5,7 @@ "/server" ], "setupFiles": [ - "/__mocks__/console.js", - "./server/test/helper.js" + "/__mocks__/console.js" ], "testEnvironment": "node" -} +} \ No newline at end of file diff --git a/server/config/database.json b/server/config/database.json index bff4eeb0..7fdc7599 100644 --- a/server/config/database.json +++ b/server/config/database.json @@ -11,4 +11,4 @@ "use_env_variable": "DATABASE_URL", "dialect": "postgres" } -} +} \ No newline at end of file diff --git a/server/migrations/20170603185012-add-collection-documentStructure-migration.js b/server/migrations/20170603185012-add-collection-documentStructure-migration.js index aa92416d..671b2e67 100644 --- a/server/migrations/20170603185012-add-collection-documentStructure-migration.js +++ b/server/migrations/20170603185012-add-collection-documentStructure-migration.js @@ -1,14 +1,16 @@ module.exports = { up: (queryInterface, Sequelize) => { - queryInterface.renameTable('atlases', 'collections'); - queryInterface.addColumn('collections', 'documentStructure', { - type: Sequelize.JSONB, - allowNull: true, + queryInterface.renameTable('atlases', 'collections').then(() => { + queryInterface.addColumn('collections', 'documentStructure', { + type: Sequelize.JSONB, + allowNull: true, + }); }); }, down: (queryInterface, _Sequelize) => { - queryInterface.renameTable('collections', 'atlases'); - queryInterface.removeColumn('atlases', 'documentStructure'); + queryInterface.renameTable('collections', 'atlases').then(() => { + queryInterface.removeColumn('atlases', 'documentStructure'); + }); }, }; diff --git a/server/migrations/20170604052346-add-views.js b/server/migrations/20170604052346-add-views.js index cd143c72..cbaea808 100644 --- a/server/migrations/20170604052346-add-views.js +++ b/server/migrations/20170604052346-add-views.js @@ -1,40 +1,44 @@ module.exports = { up: function(queryInterface, Sequelize) { - queryInterface.createTable('views', { - id: { - type: Sequelize.UUID, - allowNull: false, - primaryKey: true, - }, - documentId: { - type: Sequelize.UUID, - allowNull: false, - }, - userId: { - type: Sequelize.UUID, - allowNull: false, - }, - count: { - type: Sequelize.INTEGER, - allowNull: false, - defaultValue: 1, - }, - createdAt: { - type: Sequelize.DATE, - allowNull: false, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - }, - }); - queryInterface.addIndex('views', ['documentId', 'userId'], { - indicesType: 'UNIQUE', - }); + queryInterface + .createTable('views', { + id: { + type: Sequelize.UUID, + allowNull: false, + primaryKey: true, + }, + documentId: { + type: Sequelize.UUID, + allowNull: false, + }, + userId: { + type: Sequelize.UUID, + allowNull: false, + }, + count: { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 1, + }, + createdAt: { + type: Sequelize.DATE, + allowNull: false, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + }, + }) + .then(() => { + queryInterface.addIndex('views', ['documentId', 'userId'], { + indicesType: 'UNIQUE', + }); + }); }, down: function(queryInterface, Sequelize) { - queryInterface.removeIndex('views', ['documentId', 'userId']); - queryInterface.dropTable('views'); + queryInterface.removeIndex('views', ['documentId', 'userId']).then(() => { + queryInterface.dropTable('views'); + }); }, }; diff --git a/server/migrations/20170604052347-add-stars.js b/server/migrations/20170604052347-add-stars.js index 78ad7712..1046022d 100644 --- a/server/migrations/20170604052347-add-stars.js +++ b/server/migrations/20170604052347-add-stars.js @@ -1,35 +1,39 @@ module.exports = { up: function(queryInterface, Sequelize) { - queryInterface.createTable('stars', { - id: { - type: Sequelize.UUID, - allowNull: false, - primaryKey: true, - }, - documentId: { - type: Sequelize.UUID, - allowNull: false, - }, - userId: { - type: Sequelize.UUID, - allowNull: false, - }, - createdAt: { - type: Sequelize.DATE, - allowNull: false, - }, - updatedAt: { - type: Sequelize.DATE, - allowNull: false, - }, - }); - queryInterface.addIndex('stars', ['documentId', 'userId'], { - indicesType: 'UNIQUE', - }); + queryInterface + .createTable('stars', { + id: { + type: Sequelize.UUID, + allowNull: false, + primaryKey: true, + }, + documentId: { + type: Sequelize.UUID, + allowNull: false, + }, + userId: { + type: Sequelize.UUID, + allowNull: false, + }, + createdAt: { + type: Sequelize.DATE, + allowNull: false, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + }, + }) + .then(() => { + queryInterface.addIndex('stars', ['documentId', 'userId'], { + indicesType: 'UNIQUE', + }); + }); }, down: function(queryInterface, Sequelize) { - queryInterface.removeIndex('stars', ['documentId', 'userId']); - queryInterface.dropTable('stars'); + queryInterface.removeIndex('stars', ['documentId', 'userId']).then(() => { + queryInterface.dropTable('stars'); + }); }, }; diff --git a/server/test/helper.js b/server/test/helper.js index 51171e1e..05d110c6 100644 --- a/server/test/helper.js +++ b/server/test/helper.js @@ -1,4 +1,5 @@ -require('dotenv').config({ silent: true }); +// @flow +require('../../init'); // test environment variables process.env.DATABASE_URL = process.env.DATABASE_URL_TEST; From b7e1ac8a36b2d0a9d81e0096c97b6548c0a5b318 Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Sat, 8 Jul 2017 21:19:48 -0700 Subject: [PATCH 24/25] Added Circle CI badge to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b0154c0b..b4b11cf2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Atlas +![](https://circleci.com/gh/jorilallo/atlas.svg?style=shield&circle-token=c0c4c2f39990e277385d5c1ae96169c409eb887a) + ## Installation 1. Install dependencies with `yarn` From 444c8afb2aa572ff526bcc83a5eba7a40d0be250 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 8 Jul 2017 22:12:14 -0700 Subject: [PATCH 25/25] Drag and Drop Import (#95) * Drag and drop files into collection first pass * Allow import of sub documents Fix up UI styles * Import Loading indicator * Drag onto document to import --- .../components/DropToImport/DropToImport.js | 108 +++++++++++++++ frontend/components/DropToImport/index.js | 3 + frontend/components/Layout/Layout.js | 26 ++-- .../SidebarCollection/SidebarCollection.js | 31 ++++- .../SidebarCollectionList.js | 23 ++- .../components/SidebarLink/SidebarLink.js | 19 ++- frontend/models/Document.js | 15 +- frontend/scenes/Document/Document.js | 131 +++++++++++------- frontend/stores/DocumentsStore.js | 6 +- frontend/styles/constants.js | 11 ++ 10 files changed, 283 insertions(+), 90 deletions(-) create mode 100644 frontend/components/DropToImport/DropToImport.js create mode 100644 frontend/components/DropToImport/index.js diff --git a/frontend/components/DropToImport/DropToImport.js b/frontend/components/DropToImport/DropToImport.js new file mode 100644 index 00000000..944dde6f --- /dev/null +++ b/frontend/components/DropToImport/DropToImport.js @@ -0,0 +1,108 @@ +// @flow +import React, { Component } from 'react'; +import { inject } from 'mobx-react'; +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'; + +class DropToImport extends Component { + state: { + isImporting: boolean, + }; + props: { + children?: React$Element, + collectionId: string, + documentId?: string, + activeClassName?: string, + rejectClassName?: string, + documents: DocumentsStore, + history: Object, + }; + state = { + isImporting: false, + }; + + 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 = { + id: 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.setState({ isImporting: true }); + + try { + let collectionId = this.props.collectionId; + const documentId = this.props.documentId; + const redirect = files.length === 1; + + if (documentId && !collectionId) { + const document = await this.props.documents.fetch(documentId); + invariant(document, 'Document not available'); + collectionId = document.collection.id; + } + + for (const file of files) { + await this.importFile({ file, documentId, collectionId, redirect }); + } + } catch (err) { + // TODO: show error alert. + } finally { + this.setState({ isImporting: false }); + } + }; + + render() { + const props = _.omit( + this.props, + 'history', + 'documentId', + 'collectionId', + 'documents' + ); + + return ( + + + {this.state.isImporting && } + {this.props.children} + + + ); + } +} + +export default inject('documents')(DropToImport); diff --git a/frontend/components/DropToImport/index.js b/frontend/components/DropToImport/index.js new file mode 100644 index 00000000..a95a2a44 --- /dev/null +++ b/frontend/components/DropToImport/index.js @@ -0,0 +1,3 @@ +// @flow +import DropToImport from './DropToImport'; +export default DropToImport; diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index cb0b7648..eba7cac6 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -7,11 +7,12 @@ import { observer, inject } from 'mobx-react'; import _ from 'lodash'; import keydown from 'react-keydown'; import Flex from 'components/Flex'; -import { textColor } from 'styles/constants.scss'; +import { color, layout } from 'styles/constants'; import DropdownMenu, { MenuItem } from 'components/DropdownMenu'; import { LoadingIndicatorBar } from 'components/LoadingIndicator'; import Scrollable from 'components/Scrollable'; +import Avatar from 'components/Avatar'; import SidebarCollection from './components/SidebarCollection'; import SidebarCollectionList from './components/SidebarCollectionList'; @@ -115,8 +116,9 @@ type Props = { ? - : } + : } @@ -141,19 +143,13 @@ const LogoLink = styled(Link)` margin-top: 15px; font-family: 'Atlas Grotesk'; font-weight: bold; - color: ${textColor}; + color: ${color.text}; text-decoration: none; font-size: 16px; `; -const Avatar = styled.img` - width: 24px; - height: 24px; - border-radius: 50%; -`; - const MenuLink = styled(Link)` - color: ${textColor}; + color: ${color.text}; `; const Content = styled(Flex)` @@ -162,13 +158,13 @@ const Content = styled(Flex)` top: 0; bottom: 0; right: 0; - left: ${props => (props.editMode ? 0 : '250px')}; + left: ${props => (props.editMode ? 0 : layout.sidebarWidth)}; transition: left 200ms ease-in-out; `; const Sidebar = styled(Flex)` - width: 250px; - margin-left: ${props => (props.editMode ? '-250px' : 0)}; + width: ${layout.sidebarWidth}; + margin-left: ${props => (props.editMode ? `-${layout.sidebarWidth}` : 0)}; background: rgba(250, 251, 252, 0.71); border-right: 1px solid #eceff3; transition: margin-left 200ms ease-in-out; @@ -176,12 +172,12 @@ const Sidebar = styled(Flex)` const Header = styled(Flex)` flex-shrink: 0; - padding: 10px 20px; + padding: ${layout.padding}; `; const LinkSection = styled(Flex)` flex-direction: column; - padding: 10px 20px; + padding: 10px 0; `; export default withRouter(inject('user', 'auth', 'ui', 'collections')(Layout)); diff --git a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js index 3b9329e3..60a5a05d 100644 --- a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js +++ b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js @@ -2,8 +2,9 @@ import React from 'react'; import Flex from 'components/Flex'; import styled from 'styled-components'; - +import { layout } from 'styles/constants'; import SidebarLink from '../SidebarLink'; +import DropToImport from 'components/DropToImport'; import Collection from 'models/Collection'; import Document from 'models/Document'; @@ -12,24 +13,39 @@ import type { NavigationNode } from 'types'; type Props = { collection: ?Collection, document: ?Document, + history: Object, +}; + +const activeStyle = { + color: '#000', + background: '#E1E1E1', }; class SidebarCollection extends React.Component { props: Props; - renderDocuments(documentList: Array) { - const { document } = this.props; + renderDocuments(documentList: Array, depth = 0) { + const { document, history } = this.props; + const canDropToImport = depth === 0; if (document) { return documentList.map(doc => ( - - {doc.title} - + {canDropToImport && + + {doc.title} + } + {!canDropToImport && + {doc.title}} + {(document.pathToDocument.includes(doc.id) || document.id === doc.id) && - {doc.children && this.renderDocuments(doc.children)} + {doc.children && this.renderDocuments(doc.children, depth + 1)} } )); @@ -57,6 +73,7 @@ const Header = styled(Flex)` text-transform: uppercase; color: #9FA6AB; letter-spacing: 0.04em; + padding: 0 ${layout.hpadding}; `; const Children = styled(Flex)` diff --git a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js index 0f865e3d..6d292dbe 100644 --- a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js +++ b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js @@ -3,23 +3,37 @@ import React from 'react'; import { observer, inject } from 'mobx-react'; import Flex from 'components/Flex'; import styled from 'styled-components'; +import { layout } from 'styles/constants'; import SidebarLink from '../SidebarLink'; +import DropToImport from 'components/DropToImport'; import CollectionsStore from 'stores/CollectionsStore'; type Props = { + history: Object, collections: CollectionsStore, }; -const SidebarCollectionList = observer(({ collections }: Props) => { +const activeStyle = { + color: '#000', + background: '#E1E1E1', +}; + +const SidebarCollectionList = observer(({ history, collections }: Props) => { return (
Collections
{collections.data.map(collection => ( - - {collection.name} - + + + {collection.name} + + ))}
); @@ -31,6 +45,7 @@ const Header = styled(Flex)` text-transform: uppercase; color: #9FA6AB; letter-spacing: 0.04em; + padding: 0 ${layout.hpadding}; `; export default inject('collections')(SidebarCollectionList); diff --git a/frontend/components/Layout/components/SidebarLink/SidebarLink.js b/frontend/components/Layout/components/SidebarLink/SidebarLink.js index 1e73d5e1..7df81e86 100644 --- a/frontend/components/Layout/components/SidebarLink/SidebarLink.js +++ b/frontend/components/Layout/components/SidebarLink/SidebarLink.js @@ -1,7 +1,8 @@ // @flow import React from 'react'; import { NavLink } from 'react-router-dom'; -import Flex from 'components/Flex'; +import { layout, color } from 'styles/constants'; +import { darken } from 'polished'; import styled from 'styled-components'; const activeStyle = { @@ -9,18 +10,16 @@ const activeStyle = { }; function SidebarLink(props: Object) { - return ( - - - - ); + return ; } -const LinkContainer = styled(Flex)` - padding: 5px 0; +const StyledNavLink = styled(NavLink)` + display: block; + padding: 5px ${layout.hpadding}; + color: ${color.slateDark}; - a { - color: #848484; + &:hover { + color: ${darken(0.1, color.slateDark)}; } `; diff --git a/frontend/models/Document.js b/frontend/models/Document.js index 097e4563..db35d742 100644 --- a/frontend/models/Document.js +++ b/frontend/models/Document.js @@ -10,7 +10,7 @@ import type { User } from 'types'; import Collection from './Collection'; const parseHeader = text => { - const firstLine = text.split(/\r?\n/)[0]; + const firstLine = text.trim().split(/\r?\n/)[0]; return firstLine.replace(/^#/, '').trim(); }; @@ -20,7 +20,7 @@ class Document { errors: ErrorsStore; collaborators: Array; - collection: Collection; + collection: $Shape; createdAt: string; createdBy: User; html: string; @@ -113,7 +113,7 @@ class Document { }; @action save = async () => { - if (this.isSaving) return; + if (this.isSaving) return this; this.isSaving = true; try { @@ -125,11 +125,16 @@ class Document { text: this.text, }); } else { - res = await client.post('/documents.create', { + const data = { + parentDocument: undefined, collection: this.collection.id, title: this.title, text: this.text, - }); + }; + if (this.parentDocument) { + data.parentDocument = this.parentDocument.id; + } + res = await client.post('/documents.create', data); } invariant(res && res.data, 'Data should be available'); diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index e653ab27..017e6f1f 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -5,12 +5,14 @@ import styled from 'styled-components'; import { observer, inject } from 'mobx-react'; import { withRouter, Prompt } from 'react-router'; import Flex from 'components/Flex'; +import { layout } from 'styles/constants'; import Document from 'models/Document'; import UiStore from 'stores/UiStore'; import DocumentsStore from 'stores/DocumentsStore'; import Menu from './components/Menu'; import Editor from 'components/Editor'; +import DropToImport from 'components/DropToImport'; import { HeaderAction, SaveAction } from 'components/Layout'; import LoadingIndicator from 'components/LoadingIndicator'; import PublishingInfo from 'components/PublishingInfo'; @@ -39,6 +41,7 @@ type Props = { newDocument?: Document, }; state = { + isDragging: false, isLoading: false, newDocument: undefined, }; @@ -125,6 +128,14 @@ type Props = { this.props.history.goBack(); }; + onStartDragging = () => { + this.setState({ isDragging: true }); + }; + + onStopDragging = () => { + this.setState({ isDragging: false }); + }; + render() { const isNew = this.props.newDocument; const isEditing = this.props.match.params.edit || isNew; @@ -133,6 +144,10 @@ type Props = { return ( + {this.state.isDragging && + + Drop files here to import into Atlas. + } {titleText && } {this.state.isLoading && } {isFetching && @@ -141,66 +156,86 @@ type Props = { } {!isFetching && this.document && - - - - + + - - - {!isEditing && - } - {!isEditing && - } - - - {isEditing - ? - : Edit} - - {!isEditing && } - - - } + + + + + {!isEditing && + } + {!isEditing && + } + + + {isEditing + ? + : Edit} + + {!isEditing && } + + + + } ); } } +const DropHere = styled(Flex)` + pointer-events: none; + position: fixed; + top: 0; + left: ${layout.sidebarWidth}; + bottom: 0; + right: 0; + text-align: center; + background: rgba(255,255,255,.9); + z-index: 1; +`; + const Meta = styled(Flex)` justify-content: ${props => (props.readOnly ? 'space-between' : 'flex-end')}; align-items: flex-start; width: 100%; position: absolute; top: 0; - padding: 10px 20px; + padding: ${layout.padding}; `; const Container = styled(Flex)` diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js index c33195a3..aaffa4b0 100644 --- a/frontend/stores/DocumentsStore.js +++ b/frontend/stores/DocumentsStore.js @@ -50,10 +50,14 @@ class DocumentsStore { const res = await client.post('/documents.info', { id }); invariant(res && res.data, 'Document not available'); const { data } = res; + const document = new Document(data); + runInAction('DocumentsStore#fetch', () => { - this.data.set(data.id, new Document(data)); + this.data.set(data.id, document); this.isLoaded = true; }); + + return document; } catch (e) { this.errors.add('Failed to load documents'); } diff --git a/frontend/styles/constants.js b/frontend/styles/constants.js index cc9cbcc1..d3a09f95 100644 --- a/frontend/styles/constants.js +++ b/frontend/styles/constants.js @@ -1,5 +1,14 @@ // @flow +export const layout = { + padding: '1.5vw 1.875vw', + vpadding: '1.5vw', + hpadding: '1.875vw', + sidebarWidth: '22%', + sidebarMinWidth: '250px', + sidebarMaxWidth: '350px', +}; + export const size = { tiny: '2px', small: '4px', @@ -28,6 +37,8 @@ export const fontWeight = { }; export const color = { + text: '#171B35', + /* Brand */ primary: '#73DF7B',