Restore New Document Functionality (#94)
* Restored New Document functionality * Declarative SidebarHidden * Fix edit route
This commit is contained in:
@ -45,6 +45,7 @@ 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>}
|
||||||
|
{this.props.createdBy &&
|
||||||
<span>
|
<span>
|
||||||
{this.props.createdBy.name}
|
{this.props.createdBy.name}
|
||||||
{' '}
|
{' '}
|
||||||
@ -61,7 +62,7 @@ class PublishingInfo extends Component {
|
|||||||
{moment(this.props.updatedAt).fromNow()}
|
{moment(this.props.updatedAt).fromNow()}
|
||||||
</span>
|
</span>
|
||||||
: null}
|
: null}
|
||||||
</span>
|
</span>}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
25
frontend/components/SidebarHidden/SidebarHidden.js
Normal file
25
frontend/components/SidebarHidden/SidebarHidden.js
Normal 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);
|
3
frontend/components/SidebarHidden/index.js
Normal file
3
frontend/components/SidebarHidden/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// @flow
|
||||||
|
import SidebarHidden from './SidebarHidden';
|
||||||
|
export default SidebarHidden;
|
@ -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,18 +113,19 @@ 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>
|
||||||
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path="/doc/:documentSlug([0-9a-zA-Z-]*-[a-zA-z]{10,15})/:edit"
|
path={`/doc/${matchDocumentSlug}/:edit`}
|
||||||
component={Document}
|
component={Document}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
@ -130,7 +133,8 @@ render(
|
|||||||
path="/collections/:id/new"
|
path="/collections/:id/new"
|
||||||
component={DocumentNew}
|
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} />
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,6 +61,12 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadDocument = async props => {
|
loadDocument = async props => {
|
||||||
|
if (props.newDocument) {
|
||||||
|
const newDocument = new Document({
|
||||||
|
collection: { id: props.match.params.id },
|
||||||
|
});
|
||||||
|
this.setState({ newDocument });
|
||||||
|
} else {
|
||||||
let document = this.document;
|
let document = this.document;
|
||||||
if (document) {
|
if (document) {
|
||||||
this.props.ui.setActiveDocument(document);
|
this.props.ui.setActiveDocument(document);
|
||||||
@ -69,15 +79,11 @@ type Props = {
|
|||||||
this.props.ui.setActiveDocument(document);
|
this.props.ui.setActiveDocument(document);
|
||||||
document.view();
|
document.view();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.match.params.edit) {
|
|
||||||
this.props.ui.enableEditMode();
|
|
||||||
} else {
|
|
||||||
this.props.ui.disableEditMode();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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));
|
||||||
|
@ -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>}
|
||||||
|
@ -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 =>
|
||||||
|
Reference in New Issue
Block a user