From 93a44e4a4cd47efee84cc286d07052ead754568f Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 25 Jun 2017 19:21:08 -0700 Subject: [PATCH 01/10] Move inputs and buttons to central components --- frontend/components/Button/Button.js | 69 ++++++++++++++++++++++++++ frontend/components/Button/index.js | 3 ++ frontend/components/Input/Input.js | 51 +++++++++++++++++++ frontend/components/Input/index.js | 3 ++ frontend/scenes/Dashboard/Dashboard.js | 6 ++- frontend/scenes/Settings/Settings.js | 41 ++------------- frontend/scenes/Starred/Starred.js | 10 +--- frontend/styles/base.scss | 1 + package.json | 1 + yarn.lock | 4 ++ 10 files changed, 141 insertions(+), 48 deletions(-) create mode 100644 frontend/components/Button/Button.js create mode 100644 frontend/components/Button/index.js create mode 100644 frontend/components/Input/Input.js create mode 100644 frontend/components/Input/index.js diff --git a/frontend/components/Button/Button.js b/frontend/components/Button/Button.js new file mode 100644 index 000000000..31b826dd0 --- /dev/null +++ b/frontend/components/Button/Button.js @@ -0,0 +1,69 @@ +// @flow +import React from 'react'; +import styled from 'styled-components'; +import { size, color } from 'styles/constants'; +import { darken } from 'polished'; + +const RealButton = styled.button` + display: inline-block; + margin: 0 0 ${size.large}; + padding: 0; + border: 0; + background: ${color.primary}; + color: ${color.white}; + border-radius: 4px; + min-width: 32px; + min-height: 32px; + text-decoration: none; + flex-shrink: 0; + outline: none; + + &::-moz-focus-inner { + padding: 0; + border: 0; + } + &:hover { + background: ${darken(0.05, color.primary)}; + } +`; + +const Label = styled.span` + padding: 2px 12px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +`; + +const Inner = styled.span` + display: flex; + line-height: 28px; + justify-content: center; +`; + +export type Props = { + type?: string, + value?: string, + icon?: React$Element, + className?: string, + children?: React$Element, +}; + +export default function Button({ + type = 'text', + icon, + children, + value, + ...rest +}: Props) { + const hasText = children !== undefined || value !== undefined; + const hasIcon = icon !== undefined; + + return ( + + + {hasIcon && icon} + {hasText && } + + + ); +} diff --git a/frontend/components/Button/index.js b/frontend/components/Button/index.js new file mode 100644 index 000000000..70118ffe8 --- /dev/null +++ b/frontend/components/Button/index.js @@ -0,0 +1,3 @@ +// @flow +import Button from './Button'; +export default Button; diff --git a/frontend/components/Input/Input.js b/frontend/components/Input/Input.js new file mode 100644 index 000000000..da09d5417 --- /dev/null +++ b/frontend/components/Input/Input.js @@ -0,0 +1,51 @@ +// @flow +import React from 'react'; +import styled from 'styled-components'; +import { Flex } from 'reflexbox'; +import { size } from 'styles/constants'; + +const RealTextarea = styled.textarea` + border: 0; + flex: 1; + padding: 8px 12px; + outline: none; +`; + +const RealInput = styled.input` + border: 0; + flex: 1; + padding: 8px 12px; + outline: none; +`; + +const Wrapper = styled(Flex)` + display: flex; + flex: 1; + margin: 0 0 ${size.large}; + color: inherit; + border-width: 2px; + border-style: solid; + border-color: ${props => (props.hasError ? 'red' : 'rgba(0, 0, 0, .15)')}; + border-radius: ${size.small}; + + &:focus, + &:active { + border-color: rgba(0, 0, 0, .25); + } +`; + +export type Props = { + type: string, + value: string, + className?: string, +}; + +export default function Input({ type, ...rest }: Props) { + const Component = type === 'textarea' ? RealTextarea : RealInput; + + return ( + + + + ); +} diff --git a/frontend/components/Input/index.js b/frontend/components/Input/index.js new file mode 100644 index 000000000..e005a8af8 --- /dev/null +++ b/frontend/components/Input/index.js @@ -0,0 +1,3 @@ +// @flow +import Input from './Input'; +export default Input; diff --git a/frontend/scenes/Dashboard/Dashboard.js b/frontend/scenes/Dashboard/Dashboard.js index af6ff2179..d470b9da3 100644 --- a/frontend/scenes/Dashboard/Dashboard.js +++ b/frontend/scenes/Dashboard/Dashboard.js @@ -4,10 +4,10 @@ import { observer, inject } from 'mobx-react'; import { Flex } from 'reflexbox'; import CollectionsStore from 'stores/CollectionsStore'; - +import PageTitle from 'components/PageTitle'; import Collection from 'components/Collection'; -import PreviewLoading from 'components/PreviewLoading'; import CenteredContent from 'components/CenteredContent'; +import PreviewLoading from 'components/PreviewLoading'; type Props = { collections: CollectionsStore, @@ -21,6 +21,8 @@ type Props = { return ( + +

Home

{!collections.isLoaded ? diff --git a/frontend/scenes/Settings/Settings.js b/frontend/scenes/Settings/Settings.js index 709a7dfd8..9c27d69e6 100644 --- a/frontend/scenes/Settings/Settings.js +++ b/frontend/scenes/Settings/Settings.js @@ -1,13 +1,14 @@ // @flow import React from 'react'; import { observer } from 'mobx-react'; -import styled from 'styled-components'; import { Flex } from 'reflexbox'; import ApiKeyRow from './components/ApiKeyRow'; import styles from './Settings.scss'; import SettingsStore from './SettingsStore'; +import Button from 'components/Button'; +import Input from 'components/Input'; import CenteredContent from 'components/CenteredContent'; import SlackAuthLink from 'components/SlackAuthLink'; import PageTitle from 'components/PageTitle'; @@ -133,7 +134,7 @@ class InlineForm extends React.Component { return (
- (props.validationError ? 'red' : 'rgba(0, 0, 0, .25)')}; - border-radius:2px 0 0 2px; -`; - -const Button = styled.input` - box-shadow:inset 0 0 0 1px; - font-family:inherit; - font-size:14px; - line-height:16px; - min-height:32px; - text-decoration:none; - display:inline-block; - margin:0; - padding-top:8px; - padding-bottom:8px; - padding-left:16px; - padding-right:16px; - cursor:pointer; - border:0; - color:black; - background-color:transparent; - border-radius:0 2px 2px 0; - margin-left:-1px; -`; - export default Settings; diff --git a/frontend/scenes/Starred/Starred.js b/frontend/scenes/Starred/Starred.js index 71cf53d88..5c51b2446 100644 --- a/frontend/scenes/Starred/Starred.js +++ b/frontend/scenes/Starred/Starred.js @@ -1,17 +1,11 @@ // @flow import React, { Component } from 'react'; import { observer } from 'mobx-react'; -import styled from 'styled-components'; import CenteredContent from 'components/CenteredContent'; import PageTitle from 'components/PageTitle'; import DocumentList from 'components/DocumentList'; import StarredStore from './StarredStore'; -const Container = styled(CenteredContent)` - width: 100%; - padding: 16px; -`; - @observer class Starred extends Component { store: StarredStore; @@ -26,11 +20,11 @@ const Container = styled(CenteredContent)` render() { return ( - +

Starred

-
+ ); } } diff --git a/frontend/styles/base.scss b/frontend/styles/base.scss index c7bd81533..3fcc4acd2 100644 --- a/frontend/styles/base.scss +++ b/frontend/styles/base.scss @@ -55,6 +55,7 @@ h4, h5, h6 { line-height: 1.25; margin-top: 1em; margin-bottom: .5em; + color: #1f2429; } h1 { font-size: 2em } h2 { font-size: 1.5em } diff --git a/package.json b/package.json index 404c6f5cb..3ce6affae 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "normalizr": "2.0.1", "pg": "^6.1.5", "pg-hstore": "2.3.2", + "polished": "^1.2.1", "query-string": "^4.3.4", "randomstring": "1.1.5", "raw-loader": "^0.5.1", diff --git a/yarn.lock b/yarn.lock index aa035e59b..27043076b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6663,6 +6663,10 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" +polished@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/polished/-/polished-1.2.1.tgz#83c18a85bf9d7023477cfc7049763b657d50f0f7" + postcss-calc@^5.2.0: version "5.3.1" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" From 86b0966703b1e4c49668e75a3e59e2456ea8e084 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 25 Jun 2017 20:32:05 -0700 Subject: [PATCH 02/10] Add recently viewed docs to dashboard --- .../components/DocumentList/DocumentList.js | 6 +-- .../DocumentPreview/DocumentPreview.js | 15 ++---- frontend/components/Layout/Layout.js | 2 +- .../PublishingInfo/PublishingInfo.js | 17 ++++--- frontend/scenes/Dashboard/Dashboard.js | 47 ++++++++++++------- .../scenes/Dashboard/RecentDocumentsStore.js | 29 ++++++++++++ frontend/scenes/Search/Search.js | 1 + 7 files changed, 75 insertions(+), 42 deletions(-) create mode 100644 frontend/scenes/Dashboard/RecentDocumentsStore.js diff --git a/frontend/components/DocumentList/DocumentList.js b/frontend/components/DocumentList/DocumentList.js index 583c0c662..bfd0b2d43 100644 --- a/frontend/components/DocumentList/DocumentList.js +++ b/frontend/components/DocumentList/DocumentList.js @@ -2,7 +2,6 @@ import React from 'react'; import type { Document } from 'types'; import DocumentPreview from 'components/DocumentPreview'; -import Divider from 'components/Divider'; class DocumentList extends React.Component { props: { @@ -14,10 +13,7 @@ class DocumentList extends React.Component {
{this.props.documents && this.props.documents.map(document => ( -
- - -
+ ))}
); diff --git a/frontend/components/DocumentPreview/DocumentPreview.js b/frontend/components/DocumentPreview/DocumentPreview.js index aef5e09b3..6b974e20d 100644 --- a/frontend/components/DocumentPreview/DocumentPreview.js +++ b/frontend/components/DocumentPreview/DocumentPreview.js @@ -1,21 +1,20 @@ // @flow import React, { Component } from 'react'; -import { toJS } from 'mobx'; import { Link } from 'react-router-dom'; import type { Document } from 'types'; import styled from 'styled-components'; import { color } from 'styles/constants'; -import Markdown from 'components/Markdown'; import PublishingInfo from 'components/PublishingInfo'; type Props = { document: Document, + highlight?: string, innerRef?: Function, }; const DocumentLink = styled(Link)` display: block; - margin: 16px -16px; + margin: 0 -16px; padding: 16px; border-radius: 8px; border: 2px solid transparent; @@ -35,16 +34,11 @@ const DocumentLink = styled(Link)` border: 2px solid ${color.slateDark}; } - h1 { + h3 { margin-top: 0; } `; -// $FlowIssue -const TruncatedMarkdown = styled(Markdown)` - pointer-events: none; -`; - class DocumentPreview extends Component { props: Props; @@ -53,14 +47,13 @@ class DocumentPreview extends Component { return ( +

{document.title}

-
); } diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index 302dbbb60..4890fc969 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -106,7 +106,7 @@ type Props = { Search - Dashboard + Home Starred diff --git a/frontend/components/PublishingInfo/PublishingInfo.js b/frontend/components/PublishingInfo/PublishingInfo.js index 1cd946bec..166c23fc4 100644 --- a/frontend/components/PublishingInfo/PublishingInfo.js +++ b/frontend/components/PublishingInfo/PublishingInfo.js @@ -7,7 +7,7 @@ import { Flex } from 'reflexbox'; const Container = styled(Flex)` justify-content: space-between; - color: #ccc; + color: #bbb; font-size: 13px; `; @@ -26,7 +26,7 @@ const Avatar = styled.img` class PublishingInfo extends Component { props: { - collaborators: Array, + collaborators?: Array, createdAt: string, createdBy: User, updatedAt: string, @@ -35,13 +35,16 @@ class PublishingInfo extends Component { }; render() { + const { collaborators } = this.props; + return ( - - {this.props.collaborators.map(user => ( - - ))} - + {collaborators && + + {collaborators.map(user => ( + + ))} + } {this.props.createdBy.name} {' '} diff --git a/frontend/scenes/Dashboard/Dashboard.js b/frontend/scenes/Dashboard/Dashboard.js index d470b9da3..5eccaafca 100644 --- a/frontend/scenes/Dashboard/Dashboard.js +++ b/frontend/scenes/Dashboard/Dashboard.js @@ -1,38 +1,49 @@ // @flow import React from 'react'; -import { observer, inject } from 'mobx-react'; -import { Flex } from 'reflexbox'; +import { observer } from 'mobx-react'; +import styled from 'styled-components'; -import CollectionsStore from 'stores/CollectionsStore'; +import DocumentList from 'components/DocumentList'; import PageTitle from 'components/PageTitle'; -import Collection from 'components/Collection'; import CenteredContent from 'components/CenteredContent'; -import PreviewLoading from 'components/PreviewLoading'; +import RecentDocumentsStore from './RecentDocumentsStore'; -type Props = { - collections: CollectionsStore, -}; +const Subheading = styled.h3` + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + color: #9FA6AB; + letter-spacing: 0.04em; + border-bottom: 1px solid #ddd; + padding-bottom: 10px; +`; + +type Props = {}; @observer class Dashboard extends React.Component { props: Props; + store: RecentDocumentsStore; + + constructor(props: Props) { + super(props); + this.store = new RecentDocumentsStore(); + } + + componentDidMount() { + this.store.fetchDocuments(); + } render() { - const { collections } = this.props; - return (

Home

- - {!collections.isLoaded - ? - : collections.data.map(collection => ( - - ))} - + Recently viewed + +
); } } -export default inject('collections')(Dashboard); +export default Dashboard; diff --git a/frontend/scenes/Dashboard/RecentDocumentsStore.js b/frontend/scenes/Dashboard/RecentDocumentsStore.js new file mode 100644 index 000000000..8b955bdcf --- /dev/null +++ b/frontend/scenes/Dashboard/RecentDocumentsStore.js @@ -0,0 +1,29 @@ +// @flow +import { observable, action, runInAction } from 'mobx'; +import invariant from 'invariant'; +import { client } from 'utils/ApiClient'; +import type { Document } from 'types'; + +class RecentDocumentsStore { + @observable documents: Array = []; + @observable isFetching = false; + + @action fetchDocuments = async () => { + this.isFetching = true; + + try { + const res = await client.get('/documents.viewed'); + invariant(res && res.data, 'res or res.data missing'); + const { data } = res; + runInAction('update state after fetching data', () => { + this.documents = data; + }); + } catch (e) { + console.error('Something went wrong'); + } + + this.isFetching = false; + }; +} + +export default RecentDocumentsStore; diff --git a/frontend/scenes/Search/Search.js b/frontend/scenes/Search/Search.js index 543992e68..34c35b22e 100644 --- a/frontend/scenes/Search/Search.js +++ b/frontend/scenes/Search/Search.js @@ -109,6 +109,7 @@ const ResultsWrapper = styled(Flex)` innerRef={ref => index === 0 && this.setFirstDocumentRef(ref)} key={document.id} document={document} + highlight={this.store.searchTerm} /> ))} From db1d8c225820dfc08040d19681b7e11f5686a898 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 25 Jun 2017 20:42:04 -0700 Subject: [PATCH 03/10] Recently edited --- frontend/scenes/Dashboard/Dashboard.js | 17 +++++++---- .../scenes/Dashboard/EditedDocumentsStore.js | 29 +++++++++++++++++++ ...umentsStore.js => ViewedDocumentsStore.js} | 4 +-- 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 frontend/scenes/Dashboard/EditedDocumentsStore.js rename frontend/scenes/Dashboard/{RecentDocumentsStore.js => ViewedDocumentsStore.js} (91%) diff --git a/frontend/scenes/Dashboard/Dashboard.js b/frontend/scenes/Dashboard/Dashboard.js index 5eccaafca..c36f42391 100644 --- a/frontend/scenes/Dashboard/Dashboard.js +++ b/frontend/scenes/Dashboard/Dashboard.js @@ -6,7 +6,8 @@ import styled from 'styled-components'; import DocumentList from 'components/DocumentList'; import PageTitle from 'components/PageTitle'; import CenteredContent from 'components/CenteredContent'; -import RecentDocumentsStore from './RecentDocumentsStore'; +import ViewedDocumentsStore from './ViewedDocumentsStore'; +import EditedDocumentsStore from './EditedDocumentsStore'; const Subheading = styled.h3` font-size: 11px; @@ -16,21 +17,25 @@ const Subheading = styled.h3` letter-spacing: 0.04em; border-bottom: 1px solid #ddd; padding-bottom: 10px; + margin-top: 30px; `; type Props = {}; @observer class Dashboard extends React.Component { props: Props; - store: RecentDocumentsStore; + viewedStore: ViewedDocumentsStore; + editedStore: EditedDocumentsStore; constructor(props: Props) { super(props); - this.store = new RecentDocumentsStore(); + this.viewedStore = new ViewedDocumentsStore(); + this.editedStore = new EditedDocumentsStore(); } componentDidMount() { - this.store.fetchDocuments(); + this.viewedStore.fetchDocuments(); + this.editedStore.fetchDocuments(); } render() { @@ -39,8 +44,10 @@ type Props = {};

Home

Recently viewed - + + Recently edited + ); } diff --git a/frontend/scenes/Dashboard/EditedDocumentsStore.js b/frontend/scenes/Dashboard/EditedDocumentsStore.js new file mode 100644 index 000000000..477500e16 --- /dev/null +++ b/frontend/scenes/Dashboard/EditedDocumentsStore.js @@ -0,0 +1,29 @@ +// @flow +import { observable, action, runInAction } from 'mobx'; +import invariant from 'invariant'; +import { client } from 'utils/ApiClient'; +import type { Document } from 'types'; + +class EditedDocumentsStore { + @observable documents: Array = []; + @observable isFetching = false; + + @action fetchDocuments = async () => { + this.isFetching = true; + + try { + const res = await client.get('/documents.list'); + invariant(res && res.data, 'res or res.data missing'); + const { data } = res; + runInAction('update state after fetching data', () => { + this.documents = data; + }); + } catch (e) { + console.error('Something went wrong'); + } + + this.isFetching = false; + }; +} + +export default EditedDocumentsStore; diff --git a/frontend/scenes/Dashboard/RecentDocumentsStore.js b/frontend/scenes/Dashboard/ViewedDocumentsStore.js similarity index 91% rename from frontend/scenes/Dashboard/RecentDocumentsStore.js rename to frontend/scenes/Dashboard/ViewedDocumentsStore.js index 8b955bdcf..147c85a1c 100644 --- a/frontend/scenes/Dashboard/RecentDocumentsStore.js +++ b/frontend/scenes/Dashboard/ViewedDocumentsStore.js @@ -4,7 +4,7 @@ import invariant from 'invariant'; import { client } from 'utils/ApiClient'; import type { Document } from 'types'; -class RecentDocumentsStore { +class ViewedDocumentsStore { @observable documents: Array = []; @observable isFetching = false; @@ -26,4 +26,4 @@ class RecentDocumentsStore { }; } -export default RecentDocumentsStore; +export default ViewedDocumentsStore; From 72fd39b494399e8a97024cc7d21b66a8eae84c55 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 27 Jun 2017 20:59:53 -0700 Subject: [PATCH 04/10] DocumentsStore WIP --- __mocks__/localStorage.js | 20 ++++++ frontend/models/Document.js | 8 +-- frontend/models/Document.test.js | 13 ++++ frontend/stores/CollectionsStore.js | 2 +- frontend/stores/CollectionsStore.test.js | 4 +- frontend/stores/DocumentsStore.js | 83 ++++++++++++++++++++++++ frontend/stores/UiStore.js | 2 +- frontend/utils/setupJest.js | 2 + 8 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 __mocks__/localStorage.js create mode 100644 frontend/models/Document.test.js create mode 100644 frontend/stores/DocumentsStore.js diff --git a/__mocks__/localStorage.js b/__mocks__/localStorage.js new file mode 100644 index 000000000..3d8394b99 --- /dev/null +++ b/__mocks__/localStorage.js @@ -0,0 +1,20 @@ +const storage = {}; + +export default { + setItem: function(key, value) { + storage[key] = value || ''; + }, + getItem: function(key) { + return key in storage ? storage[key] : null; + }, + removeItem: function(key) { + delete storage[key]; + }, + get length() { + return Object.keys(storage).length; + }, + key: function(i) { + var keys = Object.keys(storage); + return keys[i] || null; + }, +}; diff --git a/frontend/models/Document.js b/frontend/models/Document.js index 6f98addd9..77dc34199 100644 --- a/frontend/models/Document.js +++ b/frontend/models/Document.js @@ -2,7 +2,7 @@ import { extendObservable, action, runInAction, computed } from 'mobx'; import invariant from 'invariant'; -import ApiClient, { client } from 'utils/ApiClient'; +import { client } from 'utils/ApiClient'; import stores from 'stores'; import ErrorsStore from 'stores/ErrorsStore'; @@ -17,14 +17,13 @@ class Document { html: string; id: string; private: boolean; + starred: boolean; team: string; text: string; title: string; updatedAt: string; updatedBy: User; url: string; - - client: ApiClient; errors: ErrorsStore; /* Computed */ @@ -56,7 +55,7 @@ class Document { @action update = async () => { try { - const res = await this.client.post('/documents.info', { id: this.id }); + const res = await client.post('/documents.info', { id: this.id }); invariant(res && res.data, 'Document API response should be available'); const { data } = res; runInAction('Document#update', () => { @@ -73,7 +72,6 @@ class Document { constructor(document: Document) { this.updateData(document); - this.client = client; this.errors = stores.errors; } } diff --git a/frontend/models/Document.test.js b/frontend/models/Document.test.js new file mode 100644 index 000000000..e7db05b7d --- /dev/null +++ b/frontend/models/Document.test.js @@ -0,0 +1,13 @@ +/* eslint-disable */ +import Document from './Document'; + +describe('Document model', () => { + test('should initialize with data', () => { + const document = new Document({ + id: 123, + title: 'Onboarding', + text: 'Some body text' + }); + expect(document.title).toBe('Onboarding'); + }); +}); diff --git a/frontend/stores/CollectionsStore.js b/frontend/stores/CollectionsStore.js index 2f9d2dafe..ae8e26446 100644 --- a/frontend/stores/CollectionsStore.js +++ b/frontend/stores/CollectionsStore.js @@ -22,7 +22,7 @@ class CollectionsStore { /* Actions */ - @action fetch = async (): Promise<*> => { + @action fetchAll = async (): Promise<*> => { try { const res = await this.client.post('/collections.list', { id: this.teamId, diff --git a/frontend/stores/CollectionsStore.test.js b/frontend/stores/CollectionsStore.test.js index 2694a9dc5..ef39ccbe0 100644 --- a/frontend/stores/CollectionsStore.test.js +++ b/frontend/stores/CollectionsStore.test.js @@ -27,7 +27,7 @@ describe('CollectionsStore', () => { })), }; - await store.fetch(); + await store.fetchAll(); expect(store.client.post).toHaveBeenCalledWith('/collections.list', { id: 123, @@ -44,7 +44,7 @@ describe('CollectionsStore', () => { add: jest.fn(), }; - await store.fetch(); + await store.fetchAll(); expect(store.errors.add).toHaveBeenCalledWith( 'Failed to load collections' diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js new file mode 100644 index 000000000..250d107b9 --- /dev/null +++ b/frontend/stores/DocumentsStore.js @@ -0,0 +1,83 @@ +// @flow +import { observable, action, runInAction } from 'mobx'; +import { client } from 'utils/ApiClient'; +import _ from 'lodash'; +import invariant from 'invariant'; + +import stores from 'stores'; +import Document from 'models/Document'; +import ErrorsStore from 'stores/ErrorsStore'; + +type Options = { + teamId: string, +}; + +class DocumentsStore { + @observable data: Object = {}; + @observable isLoaded: boolean = false; + errors: ErrorsStore; + + /* Actions */ + + @action fetchAll = async (): Promise<*> => { + try { + const res = await client.post('/documents.list'); + invariant(res && res.data, 'Document list not available'); + const { data } = res; + runInAction('DocumentsStore#fetchAll', () => { + const loaded = _.keyBy( + data.map(document => new Document(document)), + 'id' + ); + this.data = { + ...this.data, + ...loaded, + }; + this.isLoaded = true; + }); + } catch (e) { + this.errors.add('Failed to load documents'); + } + }; + + @action fetch = async (id: string): Promise<*> => { + try { + const res = await client.post('/documents.info', { id }); + invariant(res && res.data, 'Document not available'); + const { data } = res; + runInAction('DocumentsStore#fetch', () => { + this.update(new Document(data)); + this.isLoaded = true; + }); + } catch (e) { + this.errors.add('Failed to load documents'); + } + }; + + @action add = (document: Document): void => { + this.data[document.id] = document; + }; + + @action remove = (id: string): void => { + delete this.data[id]; + }; + + @action update = (document: Document): void => { + const existing = this.data[document.id]; + + this.data[document.id] = { + ...existing, + document, + }; + }; + + getById = (id: string): Document => { + return _.find(this.data, { id }); + }; + + constructor(options: Options) { + this.errors = stores.errors; + } +} + +export default DocumentsStore; diff --git a/frontend/stores/UiStore.js b/frontend/stores/UiStore.js index 74068d0dd..48327a7bd 100644 --- a/frontend/stores/UiStore.js +++ b/frontend/stores/UiStore.js @@ -1,6 +1,6 @@ // @flow import { observable, action, computed } from 'mobx'; -import type { Document } from 'types'; +import Document from 'models/Document'; import Collection from 'models/Collection'; class UiStore { diff --git a/frontend/utils/setupJest.js b/frontend/utils/setupJest.js index dcd1f0750..61b717c2f 100644 --- a/frontend/utils/setupJest.js +++ b/frontend/utils/setupJest.js @@ -2,10 +2,12 @@ import React from 'react'; import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; +import localStorage from '../../__mocks__/localStorage'; const snap = children => { const wrapper = shallow(children); expect(toJson(wrapper)).toMatchSnapshot(); }; +global.localStorage = localStorage; global.snap = snap; From 4c3f4fea1669e3958318010e44136bbd58fade74 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 27 Jun 2017 21:20:09 -0700 Subject: [PATCH 05/10] recent --- frontend/index.js | 7 ++++++- frontend/stores/DocumentsStore.js | 32 +++++++++++++++---------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/frontend/index.js b/frontend/index.js index c09fac025..102280336 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -11,6 +11,7 @@ import { import { Flex } from 'reflexbox'; import stores from 'stores'; +import DocumentsStore from 'stores/DocumentsStore'; import CollectionsStore from 'stores/CollectionsStore'; import 'normalize.css/normalize.css'; @@ -57,12 +58,14 @@ const Auth = ({ children }: AuthProps) => { const user = stores.auth.getUserStore(); authenticatedStores = { user, + documents: new DocumentsStore(), collections: new CollectionsStore({ teamId: user.team.id, }), }; - authenticatedStores.collections.fetch(); + authenticatedStores.documents.fetchAll(); + authenticatedStores.collections.fetchAll(); } return ( @@ -135,3 +138,5 @@ render( , document.getElementById('root') ); + +window.authenticatedStores = authenticatedStores; diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js index 250d107b9..fa74e4aad 100644 --- a/frontend/stores/DocumentsStore.js +++ b/frontend/stores/DocumentsStore.js @@ -8,10 +8,6 @@ import stores from 'stores'; import Document from 'models/Document'; import ErrorsStore from 'stores/ErrorsStore'; -type Options = { - teamId: string, -}; - class DocumentsStore { @observable data: Object = {}; @observable isLoaded: boolean = false; @@ -19,9 +15,9 @@ class DocumentsStore { /* Actions */ - @action fetchAll = async (): Promise<*> => { + @action fetchAll = async (request?: string = 'list'): Promise<*> => { try { - const res = await client.post('/documents.list'); + const res = await client.post(`/documents.${request}`); invariant(res && res.data, 'Document list not available'); const { data } = res; runInAction('DocumentsStore#fetchAll', () => { @@ -35,18 +31,29 @@ class DocumentsStore { }; this.isLoaded = true; }); + return data; } catch (e) { this.errors.add('Failed to load documents'); } }; + @action fetchRecent = async (): Promise<*> => { + const data = await this.fetchAll('recent'); + console.log(data); + }; + + @action fetchViewed = async (): Promise<*> => { + const data = await this.fetchAll('viewed'); + console.log(data); + }; + @action fetch = async (id: string): Promise<*> => { try { const res = await client.post('/documents.info', { id }); invariant(res && res.data, 'Document not available'); const { data } = res; runInAction('DocumentsStore#fetch', () => { - this.update(new Document(data)); + this.add(new Document(data)); this.isLoaded = true; }); } catch (e) { @@ -62,20 +69,11 @@ class DocumentsStore { delete this.data[id]; }; - @action update = (document: Document): void => { - const existing = this.data[document.id]; - - this.data[document.id] = { - ...existing, - document, - }; - }; - getById = (id: string): Document => { return _.find(this.data, { id }); }; - constructor(options: Options) { + constructor() { this.errors = stores.errors; } } From 07c362cd5423bd21c42cc30bffe3ffe3ce4e3741 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 27 Jun 2017 21:52:47 -0700 Subject: [PATCH 06/10] Refactor to Map --- frontend/stores/DocumentsStore.js | 40 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js index fa74e4aad..05cfb02b5 100644 --- a/frontend/stores/DocumentsStore.js +++ b/frontend/stores/DocumentsStore.js @@ -1,5 +1,5 @@ // @flow -import { observable, action, runInAction } from 'mobx'; +import { observable, action, map, runInAction } from 'mobx'; import { client } from 'utils/ApiClient'; import _ from 'lodash'; import invariant from 'invariant'; @@ -9,26 +9,22 @@ import Document from 'models/Document'; import ErrorsStore from 'stores/ErrorsStore'; class DocumentsStore { - @observable data: Object = {}; + @observable recentlyViewedIds: Array = []; + @observable data: Map = map([]); @observable isLoaded: boolean = false; errors: ErrorsStore; /* Actions */ - @action fetchAll = async (request?: string = 'list'): Promise<*> => { + @action fetchAll = async (request: string = 'list'): Promise<*> => { try { const res = await client.post(`/documents.${request}`); invariant(res && res.data, 'Document list not available'); const { data } = res; runInAction('DocumentsStore#fetchAll', () => { - const loaded = _.keyBy( - data.map(document => new Document(document)), - 'id' - ); - this.data = { - ...this.data, - ...loaded, - }; + data.forEach(document => { + this.data.set(document.id, new Document(document)); + }); this.isLoaded = true; }); return data; @@ -37,14 +33,16 @@ class DocumentsStore { } }; - @action fetchRecent = async (): Promise<*> => { - const data = await this.fetchAll('recent'); - console.log(data); + @action fetchRecentlyEdited = async (): Promise<*> => { + await this.fetchAll('recent'); }; - @action fetchViewed = async (): Promise<*> => { + @action fetchRecentlyViewed = async (): Promise<*> => { const data = await this.fetchAll('viewed'); - console.log(data); + + runInAction('DocumentsStore#fetchRecentlyViewed', () => { + this.recentlyViewedIds = _.pick(data, 'id'); + }); }; @action fetch = async (id: string): Promise<*> => { @@ -53,7 +51,7 @@ class DocumentsStore { invariant(res && res.data, 'Document not available'); const { data } = res; runInAction('DocumentsStore#fetch', () => { - this.add(new Document(data)); + this.data.set(data.id, new Document(data)); this.isLoaded = true; }); } catch (e) { @@ -62,15 +60,15 @@ class DocumentsStore { }; @action add = (document: Document): void => { - this.data[document.id] = document; + this.data.set(document.id, document); }; @action remove = (id: string): void => { - delete this.data[id]; + this.data.delete(id); }; - getById = (id: string): Document => { - return _.find(this.data, { id }); + getById = (id: string): ?Document => { + return this.data.get(id); }; constructor() { From dbb8e3df8e10a37320eea16aef1de1bcd00775dc Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 28 Jun 2017 21:46:00 -0700 Subject: [PATCH 07/10] Use DocumentStore for viewing and editing --- frontend/models/Document.js | 83 ++++++++- frontend/scenes/Document/Document.js | 203 +++++++++++----------- frontend/scenes/Document/DocumentStore.js | 186 -------------------- frontend/stores/DocumentsStore.js | 4 + 4 files changed, 182 insertions(+), 294 deletions(-) delete mode 100644 frontend/scenes/Document/DocumentStore.js diff --git a/frontend/models/Document.js b/frontend/models/Document.js index 40fbd91b7..e19f79711 100644 --- a/frontend/models/Document.js +++ b/frontend/models/Document.js @@ -9,7 +9,16 @@ import ErrorsStore from 'stores/ErrorsStore'; import type { User } from 'types'; import Collection from './Collection'; +const parseHeader = text => { + const firstLine = text.split(/\r?\n/)[0]; + return firstLine.replace(/^#/, '').trim(); +}; + class Document { + isSaving: boolean; + hasPendingChanges: boolean = false; + errors: ErrorsStore; + collaborators: Array; collection: Collection; createdAt: string; @@ -20,12 +29,11 @@ class Document { starred: boolean; team: string; text: string; - title: string; + title: string = 'Untitled document'; updatedAt: string; updatedBy: User; url: string; views: number; - errors: ErrorsStore; /* Computed */ @@ -54,7 +62,44 @@ class Document { /* Actions */ - @action update = async () => { + @action star = async () => { + this.starred = true; + try { + await client.post('/documents.star', { id: this.id }); + } catch (e) { + this.starred = false; + this.errors.add('Document failed star'); + } + }; + + @action unstar = async () => { + this.starred = false; + try { + await client.post('/documents.unstar', { id: this.id }); + } catch (e) { + this.starred = false; + this.errors.add('Document failed unstar'); + } + }; + + @action view = async () => { + try { + await client.post('/views.create', { id: this.id }); + this.views++; + } catch (e) { + this.errors.add('Document failed to record view'); + } + }; + + @action delete = async () => { + try { + await client.post('/documents.delete', { id: this.id }); + } catch (e) { + this.errors.add('Document failed to delete'); + } + }; + + @action fetch = async () => { try { const res = await client.post('/documents.info', { id: this.id }); invariant(res && res.data, 'Document API response should be available'); @@ -67,7 +112,37 @@ class Document { } }; - updateData(data: Document) { + @action save = async () => { + if (this.isSaving) return; + this.isSaving = true; + + try { + let res; + if (this.id) { + res = await client.post('/documents.update', { + id: this.id, + title: this.title, + text: this.text, + }); + } else { + res = await client.post('/documents.create', { + collection: this.collection.id, + title: this.title, + text: this.text, + }); + } + + invariant(res && res.data, 'Data should be available'); + this.hasPendingChanges = false; + } catch (e) { + this.errors.add('Document failed saving'); + } finally { + this.isSaving = false; + } + }; + + updateData(data: Object | Document) { + data.title = parseHeader(data.text); extendObservable(this, data); } diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index a535432fe..636dd7eb7 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -7,8 +7,7 @@ import { withRouter, Prompt } from 'react-router'; import { Flex } from 'reflexbox'; import UiStore from 'stores/UiStore'; - -import DocumentStore from './DocumentStore'; +import DocumentsStore from 'stores/DocumentsStore'; import Menu from './components/Menu'; import Editor from 'components/Editor'; import { HeaderAction, SaveAction } from 'components/Layout'; @@ -27,77 +26,72 @@ type Props = { match: Object, history: Object, keydown: Object, + documents: DocumentsStore, newChildDocument?: boolean, ui: UiStore, }; @observer class Document extends Component { - store: DocumentStore; props: Props; - constructor(props: Props) { - super(props); - this.store = new DocumentStore({ - history: this.props.history, - ui: props.ui, - }); - } - componentDidMount() { this.loadDocument(this.props); } componentWillReceiveProps(nextProps) { - if (nextProps.match.params.id !== this.props.match.params.id) + if (nextProps.match.params.id !== this.props.match.params.id) { this.loadDocument(nextProps); - } - - loadDocument(props) { - if (props.newDocument) { - this.store.collectionId = props.match.params.id; - this.store.newDocument = true; - } else if (props.match.params.edit) { - this.store.documentId = props.match.params.id; - this.store.fetchDocument(); - } else if (props.newChildDocument) { - this.store.documentId = props.match.params.id; - this.store.newChildDocument = true; - this.store.fetchDocument(); - } else { - this.store.documentId = props.match.params.id; - this.store.newDocument = false; - this.store.fetchDocument(); } - - this.store.viewDocument(); } componentWillUnmount() { this.props.ui.clearActiveDocument(); } - onEdit = () => { - const url = `${this.store.document.url}/edit`; + loadDocument = async props => { + await this.props.documents.fetch(props.match.params.id); + if (this.document) this.document.view(); + + if (this.props.match.params.edit) { + this.props.ui.enableEditMode(); + } else { + this.props.ui.disableEditMode(); + } + }; + + get document() { + return this.props.documents.getByUrl(`/d/${this.props.match.params.id}`); + } + + onClickEdit = () => { + if (!this.document) return; + const url = `${this.document.url}/edit`; this.props.history.push(url); this.props.ui.enableEditMode(); }; - onSave = async (options: { redirect?: boolean } = {}) => { - if (this.store.newDocument || this.store.newChildDocument) { - await this.store.saveDocument(options); - } else { - await this.store.updateDocument(options); - } + onSave = async (redirect: boolean = false) => { + if (!this.document) return; + await this.document.save(); this.props.ui.disableEditMode(); + + if (redirect) { + this.props.history.push(this.document.url); + } }; - onImageUploadStart = () => { - this.store.updateUploading(true); - }; + onImageUploadStart() { + // TODO: How to set loading bar on layout? + } - onImageUploadStop = () => { - this.store.updateUploading(false); - }; + onImageUploadStop() { + // TODO: How to set loading bar on layout? + } + + onChange(text) { + if (!this.document) return; + this.document.updateData({ text }); + } onCancel = () => { this.props.history.goBack(); @@ -106,69 +100,70 @@ type Props = { render() { const isNew = this.props.newDocument || this.props.newChildDocument; const isEditing = this.props.match.params.edit; - const titleText = this.store.document && get(this.store, 'document.title'); + const isFetching = !this.document && get(this.document, 'isFetching'); + const titleText = get(this.document, 'title', 'Loading'); - const actions = ( - - - {isEditing - ? - : Edit} - - - {!isEditing && - } - - ); + console.log('isEditing', isEditing); + console.log('isFetching', isFetching); + console.log('document', this.document); return ( {titleText && } - - - - {this.store.isFetching - ? - - - : this.store.document && - - - } - - {this.store.document && - - {!isEditing && - } - {!isEditing && - } - {actions} - } + {isFetching && + + + } + {!isFetching && + this.document && + + + + + + + {!isEditing && + } + {!isEditing && + } + + + {isEditing + ? + : Edit} + + {!isEditing && } + + + } ); } @@ -201,4 +196,4 @@ const DocumentContainer = styled.div` width: 50em; `; -export default withRouter(inject('ui')(Document)); +export default withRouter(inject('ui', 'documents')(Document)); diff --git a/frontend/scenes/Document/DocumentStore.js b/frontend/scenes/Document/DocumentStore.js deleted file mode 100644 index bcc595001..000000000 --- a/frontend/scenes/Document/DocumentStore.js +++ /dev/null @@ -1,186 +0,0 @@ -// @flow -import { observable, action, computed } from 'mobx'; -import get from 'lodash/get'; -import invariant from 'invariant'; -import { client } from 'utils/ApiClient'; -import emojify from 'utils/emojify'; -import Document from 'models/Document'; -import UiStore from 'stores/UiStore'; - -type SaveProps = { redirect?: boolean }; - -const parseHeader = text => { - const firstLine = text.split(/\r?\n/)[0]; - if (firstLine) { - const match = firstLine.match(/^#+ +(.*)$/); - - if (match) { - return emojify(match[1]); - } else { - return ''; - } - } - return ''; -}; - -type Options = { - history: Object, - ui: UiStore, -}; - -class DocumentStore { - document: Document; - @observable collapsedNodes: string[] = []; - @observable documentId = null; - @observable collectionId = null; - @observable parentDocument: Document; - @observable hasPendingChanges = false; - @observable newDocument: ?boolean; - @observable newChildDocument: ?boolean; - - @observable isEditing: boolean = false; - @observable isFetching: boolean = false; - @observable isSaving: boolean = false; - @observable isUploading: boolean = false; - - history: Object; - ui: UiStore; - - /* Computed */ - - @computed get isCollection(): boolean { - return !!this.document && this.document.collection.type === 'atlas'; - } - - /* Actions */ - - @action starDocument = async () => { - this.document.starred = true; - try { - await client.post('/documents.star', { - id: this.documentId, - }); - } catch (e) { - this.document.starred = false; - console.error('Something went wrong'); - } - }; - - @action unstarDocument = async () => { - this.document.starred = false; - try { - await client.post('/documents.unstar', { - id: this.documentId, - }); - } catch (e) { - this.document.starred = true; - console.error('Something went wrong'); - } - }; - - @action viewDocument = async () => { - await client.post('/views.create', { - id: this.documentId, - }); - }; - - @action fetchDocument = async () => { - this.isFetching = true; - - try { - const res = await client.get('/documents.info', { - id: this.documentId, - }); - invariant(res && res.data, 'Data should be available'); - if (this.newChildDocument) { - this.parentDocument = res.data; - } else { - this.document = new Document(res.data); - this.ui.setActiveDocument(this.document); - } - } catch (e) { - console.error('Something went wrong'); - } - this.isFetching = false; - }; - - @action saveDocument = async ({ redirect = true }: SaveProps) => { - if (this.isSaving) return; - - this.isSaving = true; - - try { - const res = await client.post('/documents.create', { - parentDocument: get(this.parentDocument, 'id'), - collection: get( - this.parentDocument, - 'collection.id', - this.collectionId - ), - title: get(this.document, 'title', 'Untitled document'), - text: get(this.document, 'text'), - }); - invariant(res && res.data, 'Data should be available'); - const { url } = res.data; - - this.hasPendingChanges = false; - if (redirect) this.history.push(url); - } catch (e) { - console.error('Something went wrong'); - } - this.isSaving = false; - }; - - @action updateDocument = async ({ redirect = true }: SaveProps) => { - if (this.isSaving) return; - - this.isSaving = true; - - try { - const res = await client.post('/documents.update', { - id: this.documentId, - title: get(this.document, 'title', 'Untitled document'), - text: get(this.document, 'text'), - }); - invariant(res && res.data, 'Data should be available'); - const { url } = res.data; - - this.hasPendingChanges = false; - if (redirect) this.history.push(url); - } catch (e) { - console.error('Something went wrong'); - } - this.isSaving = false; - }; - - @action deleteDocument = async () => { - this.isFetching = true; - - try { - await client.post('/documents.delete', { id: this.documentId }); - this.history.push(this.document.collection.url); - } catch (e) { - console.error('Something went wrong'); - } - this.isFetching = false; - }; - - @action updateText = (text: string) => { - if (!this.document) return; - - this.document.text = text; - this.document.title = parseHeader(text); - this.hasPendingChanges = true; - }; - - @action updateUploading = (uploading: boolean) => { - this.isUploading = uploading; - }; - - constructor(options: Options) { - this.history = options.history; - this.ui = options.ui; - } -} - -export default DocumentStore; diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js index d72ffa5d0..c33195a3c 100644 --- a/frontend/stores/DocumentsStore.js +++ b/frontend/stores/DocumentsStore.js @@ -81,6 +81,10 @@ class DocumentsStore { return this.data.get(id); }; + getByUrl = (url: string): ?Document => { + return _.find(this.data.values(), { url }); + }; + constructor() { this.errors = stores.errors; } From 0b79706d2385591e1aa2220ed26dbf29450dca5b Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 28 Jun 2017 21:50:54 -0700 Subject: [PATCH 08/10] Flow --- frontend/scenes/Document/Document.js | 4 ++-- frontend/scenes/Document/components/Menu.js | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index 636dd7eb7..e8882d91d 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -75,7 +75,7 @@ type Props = { await this.document.save(); this.props.ui.disableEditMode(); - if (redirect) { + if (redirect && this.document) { this.props.history.push(this.document.url); } }; @@ -155,7 +155,7 @@ type Props = { {isEditing ? : Edit} diff --git a/frontend/scenes/Document/components/Menu.js b/frontend/scenes/Document/components/Menu.js index 6e044b59b..76ac51a91 100644 --- a/frontend/scenes/Document/components/Menu.js +++ b/frontend/scenes/Document/components/Menu.js @@ -4,14 +4,12 @@ import invariant from 'invariant'; import get from 'lodash/get'; import { withRouter } from 'react-router-dom'; import { observer } from 'mobx-react'; -import type { Document as DocumentType } from 'types'; +import Document from 'models/Document'; import DropdownMenu, { MenuItem, MoreIcon } from 'components/DropdownMenu'; -import DocumentStore from '../DocumentStore'; type Props = { history: Object, - document: DocumentType, - store: DocumentStore, + document: Document, }; @observer class Menu extends Component { @@ -38,7 +36,7 @@ type Props = { } if (confirm(msg)) { - this.props.store.deleteDocument(); + this.props.document.delete(); } }; From 31f3d72f0155934c0b5bba396abae24f9d3cef2c Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 28 Jun 2017 21:56:29 -0700 Subject: [PATCH 09/10] Remove logging --- frontend/scenes/Document/Document.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index e8882d91d..4c019aeb2 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -88,10 +88,10 @@ type Props = { // TODO: How to set loading bar on layout? } - onChange(text) { + onChange = text => { if (!this.document) return; this.document.updateData({ text }); - } + }; onCancel = () => { this.props.history.goBack(); @@ -103,10 +103,6 @@ type Props = { const isFetching = !this.document && get(this.document, 'isFetching'); const titleText = get(this.document, 'title', 'Loading'); - console.log('isEditing', isEditing); - console.log('isFetching', isFetching); - console.log('document', this.document); - return ( {titleText && } From e40e372fed38514f00fc07503e6cf5a67594c520 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 28 Jun 2017 23:02:11 -0700 Subject: [PATCH 10/10] Fixed: hasPendingChanges Fixed: Vertical alignment changing between read/edit --- frontend/components/Editor/Editor.js | 2 -- frontend/scenes/Document/Document.js | 19 +++++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/frontend/components/Editor/Editor.js b/frontend/components/Editor/Editor.js index 125b1f43c..025e5a034 100644 --- a/frontend/components/Editor/Editor.js +++ b/frontend/components/Editor/Editor.js @@ -111,8 +111,6 @@ type KeyData = { render = () => { return ( - {!this.props.readOnly && - } { await this.props.documents.fetch(props.match.params.id); - if (this.document) this.document.view(); + const document = this.document; + + if (document) { + this.props.ui.setActiveDocument(document); + document.view(); + } if (this.props.match.params.edit) { this.props.ui.enableEditMode(); @@ -71,12 +76,14 @@ type Props = { }; onSave = async (redirect: boolean = false) => { - if (!this.document) return; - await this.document.save(); + const document = this.document; + + if (!document) return; + await document.save(); this.props.ui.disableEditMode(); - if (redirect && this.document) { - this.props.history.push(this.document.url); + if (redirect) { + this.props.history.push(document.url); } }; @@ -90,7 +97,7 @@ type Props = { onChange = text => { if (!this.document) return; - this.document.updateData({ text }); + this.document.updateData({ text, hasPendingChanges: true }); }; onCancel = () => {