Restore New Document Functionality (#94)

* Restored New Document functionality

* Declarative SidebarHidden

* Fix edit route
This commit is contained in:
Tom Moor
2017-07-04 23:02:06 -07:00
committed by GitHub
parent 88cdbc413a
commit 1c83257483
8 changed files with 111 additions and 72 deletions

View File

@ -45,23 +45,24 @@ class PublishingInfo extends Component {
<Avatar key={user.id} src={user.avatarUrl} title={user.name} /> <Avatar key={user.id} src={user.avatarUrl} title={user.name} />
))} ))}
</Avatars>} </Avatars>}
<span> {this.props.createdBy &&
{this.props.createdBy.name} <span>
{' '} {this.props.createdBy.name}
published {' '}
{' '} published
{moment(this.props.createdAt).fromNow()} {' '}
{this.props.createdAt !== this.props.updatedAt {moment(this.props.createdAt).fromNow()}
? <span> {this.props.createdAt !== this.props.updatedAt
&nbsp;and&nbsp; ? <span>
{this.props.createdBy.id !== this.props.updatedBy.id && &nbsp;and&nbsp;
` ${this.props.updatedBy.name} `} {this.props.createdBy.id !== this.props.updatedBy.id &&
modified ` ${this.props.updatedBy.name} `}
{' '} modified
{moment(this.props.updatedAt).fromNow()} {' '}
</span> {moment(this.props.updatedAt).fromNow()}
: null} </span>
</span> : null}
</span>}
</Container> </Container>
); );
} }

View File

@ -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<any>,
};
componentDidMount() {
this.props.ui.enableEditMode();
}
componentWillUnmount() {
this.props.ui.disableEditMode();
}
render() {
return this.props.children;
}
}
export default inject('ui')(SidebarHidden);

View File

@ -0,0 +1,3 @@
// @flow
import SidebarHidden from './SidebarHidden';
export default SidebarHidden;

View File

@ -35,6 +35,7 @@ import Error404 from 'scenes/Error404';
import ScrollToTop from 'components/ScrollToTop'; import ScrollToTop from 'components/ScrollToTop';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import SidebarHidden from 'components/SidebarHidden';
import flatpages from 'static/flatpages'; import flatpages from 'static/flatpages';
@ -86,11 +87,12 @@ const KeyboardShortcuts = () => (
); );
const Api = () => <Flatpage title="API" content={flatpages.api} />; const Api = () => <Flatpage title="API" content={flatpages.api} />;
const DocumentNew = () => <Document newDocument />; const DocumentNew = () => <Document newDocument />;
const DocumentNewChild = () => <Document newChildDocument />;
const RedirectDocument = ({ match }: { match: Object }) => ( const RedirectDocument = ({ match }: { match: Object }) => (
<Redirect to={`/doc/${match.params.documentSlug}`} /> <Redirect to={`/doc/${match.params.documentSlug}`} />
); );
const matchDocumentSlug = ':documentSlug([0-9a-zA-Z-]*-[a-zA-z0-9]{10,15})';
render( render(
<div style={{ display: 'flex', flex: 1, height: '100%' }}> <div style={{ display: 'flex', flex: 1, height: '100%' }}>
<Provider {...stores}> <Provider {...stores}>
@ -111,26 +113,28 @@ render(
<Route exact path="/collections/:id" component={Collection} /> <Route exact path="/collections/:id" component={Collection} />
<Route <Route
exact exact
path="/d/:documentSlug([0-9a-zA-Z-]*-[a-zA-z]{10,15})" path={`/d/${matchDocumentSlug}`}
component={RedirectDocument} component={RedirectDocument}
/> />
<Route <Route
exact exact
path="/doc/:documentSlug([0-9a-zA-Z-]*-[a-zA-z]{10,15})" path={`/doc/${matchDocumentSlug}`}
component={Document} component={Document}
/> />
<SidebarHidden>
<Route <Switch>
exact <Route
path="/doc/:documentSlug([0-9a-zA-Z-]*-[a-zA-z]{10,15})/:edit" exact
component={Document} path={`/doc/${matchDocumentSlug}/:edit`}
/> component={Document}
<Route />
exact <Route
path="/collections/:id/new" exact
component={DocumentNew} path="/collections/:id/new"
/> component={DocumentNew}
<Route exact path="/d/:id/new" component={DocumentNewChild} /> />
</Switch>
</SidebarHidden>
<Route exact path="/search" component={Search} /> <Route exact path="/search" component={Search} />
<Route exact path="/search/:query" component={Search} /> <Route exact path="/search/:query" component={Search} />

View File

@ -15,7 +15,7 @@ const parseHeader = text => {
}; };
class Document { class Document {
isSaving: boolean; isSaving: boolean = false;
hasPendingChanges: boolean = false; hasPendingChanges: boolean = false;
errors: ErrorsStore; errors: ErrorsStore;
@ -25,10 +25,10 @@ class Document {
createdBy: User; createdBy: User;
html: string; html: string;
id: string; id: string;
private: boolean;
starred: boolean;
team: string; team: string;
text: string; private: boolean = false;
starred: boolean = false;
text: string = '';
title: string = 'Untitled document'; title: string = 'Untitled document';
updatedAt: string; updatedAt: string;
updatedBy: User; updatedBy: User;
@ -83,9 +83,9 @@ class Document {
}; };
@action view = async () => { @action view = async () => {
this.views++;
try { try {
await client.post('/views.create', { id: this.id }); await client.post('/views.create', { id: this.id });
this.views++;
} catch (e) { } catch (e) {
this.errors.add('Document failed to record view'); this.errors.add('Document failed to record view');
} }
@ -133,20 +133,25 @@ class Document {
} }
invariant(res && res.data, 'Data should be available'); invariant(res && res.data, 'Data should be available');
this.hasPendingChanges = false; this.updateData({
...res.data,
hasPendingChanges: false,
});
} catch (e) { } catch (e) {
this.errors.add('Document failed saving'); this.errors.add('Document failed saving');
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }
return this;
}; };
updateData(data: Object | Document) { updateData(data: Object | Document) {
data.title = parseHeader(data.text); if (data.text) data.title = parseHeader(data.text);
extendObservable(this, data); extendObservable(this, data);
} }
constructor(document: Document) { constructor(document?: Object = {}) {
this.updateData(document); this.updateData(document);
this.errors = stores.errors; this.errors = stores.errors;
} }

View File

@ -6,6 +6,7 @@ import { observer, inject } from 'mobx-react';
import { withRouter, Prompt } from 'react-router'; import { withRouter, Prompt } from 'react-router';
import { Flex } from 'reflexbox'; import { Flex } from 'reflexbox';
import Document from 'models/Document';
import UiStore from 'stores/UiStore'; import UiStore from 'stores/UiStore';
import DocumentsStore from 'stores/DocumentsStore'; import DocumentsStore from 'stores/DocumentsStore';
import Menu from './components/Menu'; import Menu from './components/Menu';
@ -28,15 +29,18 @@ type Props = {
history: Object, history: Object,
keydown: Object, keydown: Object,
documents: DocumentsStore, documents: DocumentsStore,
newChildDocument?: boolean, newDocument?: boolean,
ui: UiStore, ui: UiStore,
}; };
@observer class Document extends Component { @observer class DocumentScene extends Component {
props: Props; props: Props;
state: {
newDocument?: Document,
};
state = { state = {
isLoading: false, isLoading: false,
newDocument: undefined,
}; };
componentDidMount() { componentDidMount() {
@ -57,27 +61,29 @@ type Props = {
} }
loadDocument = async props => { loadDocument = async props => {
let document = this.document; if (props.newDocument) {
if (document) { const newDocument = new Document({
this.props.ui.setActiveDocument(document); collection: { id: props.match.params.id },
} });
this.setState({ newDocument });
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();
} else { } 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() { get document() {
if (this.state.newDocument) return this.state.newDocument;
return this.props.documents.getByUrl( return this.props.documents.getByUrl(
`/doc/${this.props.match.params.documentSlug}` `/doc/${this.props.match.params.documentSlug}`
); );
@ -87,19 +93,17 @@ type Props = {
if (!this.document) return; if (!this.document) return;
const url = `${this.document.url}/edit`; const url = `${this.document.url}/edit`;
this.props.history.push(url); this.props.history.push(url);
this.props.ui.enableEditMode();
}; };
onSave = async (redirect: boolean = false) => { onSave = async (redirect: boolean = false) => {
const document = this.document; let document = this.document;
if (!document) return; if (!document) return;
this.setState({ isLoading: true }); this.setState({ isLoading: true });
await document.save(); document = await document.save();
this.setState({ isLoading: false }); this.setState({ isLoading: false });
this.props.ui.disableEditMode();
if (redirect) { if (redirect || this.props.newDocument) {
this.props.history.push(document.url); this.props.history.push(document.url);
} }
}; };
@ -122,8 +126,8 @@ type Props = {
}; };
render() { render() {
const isNew = this.props.newDocument || this.props.newChildDocument; const isNew = this.props.newDocument;
const isEditing = this.props.match.params.edit; const isEditing = this.props.match.params.edit || isNew;
const isFetching = !this.document; const isFetching = !this.document;
const titleText = get(this.document, 'title', 'Loading'); const titleText = get(this.document, 'title', 'Loading');
@ -217,4 +221,4 @@ const DocumentContainer = styled.div`
width: 50em; width: 50em;
`; `;
export default withRouter(inject('ui', 'documents')(Document)); export default withRouter(inject('ui', 'user', 'documents')(DocumentScene));

View File

@ -16,9 +16,7 @@ type Props = {
props: Props; props: Props;
onCreateDocument = () => { onCreateDocument = () => {
// Disabled until created a better API this.props.history.push(`${this.props.document.collection.url}/new`);
// invariant(this.props.collectionTree, 'collectionTree is not available');
// this.props.history.push(`${this.props.collectionTree.url}/new`);
}; };
onCreateChild = () => { onCreateChild = () => {
@ -68,7 +66,6 @@ type Props = {
<MenuItem onClick={this.onCreateDocument}> <MenuItem onClick={this.onCreateDocument}>
New document New document
</MenuItem> </MenuItem>
<MenuItem onClick={this.onCreateChild}>New child</MenuItem>
</div>} </div>}
<MenuItem onClick={this.onExport}>Export</MenuItem> <MenuItem onClick={this.onExport}>Export</MenuItem>
{allowDelete && <MenuItem onClick={this.onDelete}>Delete</MenuItem>} {allowDelete && <MenuItem onClick={this.onDelete}>Delete</MenuItem>}

View File

@ -9,7 +9,7 @@ import { convertToMarkdown } from '../../frontend/utils/markdown';
import { truncateMarkdown } from '../utils/truncate'; import { truncateMarkdown } from '../utils/truncate';
import Revision from './Revision'; 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'; slug.defaults.mode = 'rfc3986';
const slugify = text => const slugify = text =>