diff --git a/app/components/Sidebar/components/Bubble.js b/app/components/Sidebar/components/Bubble.js
index 2d0b4c5d..8f10490d 100644
--- a/app/components/Sidebar/components/Bubble.js
+++ b/app/components/Sidebar/components/Bubble.js
@@ -14,18 +14,18 @@ const Bubble = ({ count }: Props) => {
const Count = styled.div`
animation: ${bounceIn} 600ms;
transform-origin: center center;
- border-radius: 100%;
color: ${props => props.theme.white};
background: ${props => props.theme.slateDark};
display: inline-block;
font-feature-settings: 'tnum';
font-weight: 600;
font-size: 9px;
- line-height: 16px;
white-space: nowrap;
vertical-align: baseline;
min-width: 16px;
min-height: 16px;
+ line-height: 16px;
+ border-radius: 8px;
text-align: center;
padding: 0 4px;
margin-left: 8px;
diff --git a/app/models/Document.js b/app/models/Document.js
index a19d4e19..e22781f9 100644
--- a/app/models/Document.js
+++ b/app/models/Document.js
@@ -44,7 +44,11 @@ export default class Document extends BaseModel {
@action
updateTitle() {
- set(this, parseTitle(this.text));
+ const { title, emoji } = parseTitle(this.text);
+
+ if (title) {
+ set(this, { title, emoji });
+ }
}
@computed
diff --git a/app/routes.js b/app/routes.js
index 9a4005c6..cb192f7f 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -7,8 +7,8 @@ import Starred from 'scenes/Starred';
import Drafts from 'scenes/Drafts';
import Archive from 'scenes/Archive';
import Collection from 'scenes/Collection';
-import Document from 'scenes/Document';
import KeyedDocument from 'scenes/Document/KeyedDocument';
+import DocumentNew from 'scenes/DocumentNew';
import Search from 'scenes/Search';
import Settings from 'scenes/Settings';
import Details from 'scenes/Settings/Details';
@@ -30,7 +30,6 @@ import RouteSidebarHidden from 'components/RouteSidebarHidden';
import { matchDocumentSlug as slug } from 'utils/routeHelpers';
const NotFound = () => ;
-const NewDocument = () => ;
const RedirectDocument = ({ match }: { match: Object }) => (
);
@@ -77,7 +76,7 @@ export default function Routes() {
{
@observable editorComponent = EditorImport;
@observable document: ?Document;
@observable revision: ?Revision;
- @observable newDocument: ?Document;
@observable isUploading: boolean = false;
@observable isSaving: boolean = false;
@observable isPublishing: boolean = false;
@@ -138,67 +136,50 @@ class DocumentScene extends React.Component {
}
loadDocument = async props => {
- if (props.newDocument) {
- this.document = new Document(
- {
- collectionId: props.match.params.id,
- parentDocumentId: new URLSearchParams(props.location.search).get(
- 'parentDocumentId'
- ),
- title: '',
- text: '',
- },
- props.documents
+ const { shareId, revisionId } = props.match.params;
+
+ try {
+ this.document = await props.documents.fetch(
+ props.match.params.documentSlug,
+ { shareId }
);
- } else {
- const { shareId, revisionId } = props.match.params;
- try {
- this.document = await props.documents.fetch(
+ if (revisionId) {
+ this.revision = await props.revisions.fetch(
props.match.params.documentSlug,
- { shareId }
+ { revisionId }
);
+ } else {
+ this.revision = undefined;
+ }
+ } catch (err) {
+ this.error = err;
+ return;
+ }
- if (revisionId) {
- this.revision = await props.revisions.fetch(
- props.match.params.documentSlug,
- { revisionId }
- );
- } else {
- this.revision = undefined;
- }
- } catch (err) {
- this.error = err;
- return;
+ this.isDirty = false;
+ this.isEmpty = false;
+
+ const document = this.document;
+
+ if (document) {
+ this.props.ui.setActiveDocument(document);
+
+ if (document.isArchived && this.isEditing) {
+ return this.goToDocumentCanonical();
}
- this.isDirty = false;
- this.isEmpty = false;
-
- const document = this.document;
-
- if (document) {
- this.props.ui.setActiveDocument(document);
-
- if (document.isArchived && this.isEditing) {
- return this.goToDocumentCanonical();
+ if (this.props.auth.user && !shareId) {
+ if (!this.isEditing && document.publishedAt) {
+ this.viewTimeout = setTimeout(document.view, MARK_AS_VIEWED_AFTER);
}
- if (this.props.auth.user && !shareId) {
- if (!this.isEditing && document.publishedAt) {
- this.viewTimeout = setTimeout(document.view, MARK_AS_VIEWED_AFTER);
- }
-
- const isMove = props.location.pathname.match(/move$/);
- const canRedirect = !this.revision && !isMove;
- if (canRedirect) {
- const canonicalUrl = updateDocumentUrl(
- props.match.url,
- document.url
- );
- if (props.location.pathname !== canonicalUrl) {
- props.history.replace(canonicalUrl);
- }
+ const isMove = props.location.pathname.match(/move$/);
+ const canRedirect = !this.revision && !isMove;
+ if (canRedirect) {
+ const canonicalUrl = updateDocumentUrl(props.match.url, document.url);
+ if (props.location.pathname !== canonicalUrl) {
+ props.history.replace(canonicalUrl);
}
}
}
@@ -327,7 +308,7 @@ class DocumentScene extends React.Component {
title={location.state ? location.state.title : 'Untitled'}
/>
-
+
);
@@ -446,10 +427,6 @@ const Container = styled(Flex)`
margin-top: ${props => (props.isShare ? '50px' : '0')};
`;
-const LoadingState = styled(LoadingPlaceholder)`
- margin: 40px 0;
-`;
-
export default withRouter(
inject('ui', 'auth', 'documents', 'revisions')(DocumentScene)
);
diff --git a/app/scenes/Document/components/LoadingPlaceholder.js b/app/scenes/Document/components/LoadingPlaceholder.js
index b7897e49..05073b9b 100644
--- a/app/scenes/Document/components/LoadingPlaceholder.js
+++ b/app/scenes/Document/components/LoadingPlaceholder.js
@@ -14,17 +14,21 @@ const randomValues = Array.from(
const LoadingPlaceholder = (props: Object) => {
return (
-
+
-
+
);
};
+const Wrapper = styled(Fade)`
+ margin: 40px 0;
+`;
+
const Mask = styled(Flex)`
height: ${props => (props.header ? 28 : 18)}px;
margin-bottom: ${props => (props.header ? 32 : 14)}px;
diff --git a/app/scenes/DocumentNew.js b/app/scenes/DocumentNew.js
new file mode 100644
index 00000000..aa7345b7
--- /dev/null
+++ b/app/scenes/DocumentNew.js
@@ -0,0 +1,49 @@
+// @flow
+import * as React from 'react';
+import { inject } from 'mobx-react';
+import type { RouterHistory, Location } from 'react-router-dom';
+import Flex from 'shared/components/Flex';
+import CenteredContent from 'components/CenteredContent';
+import LoadingPlaceholder from 'scenes/Document/components/LoadingPlaceholder';
+import DocumentsStore from 'stores/DocumentsStore';
+import UiStore from 'stores/UiStore';
+import { documentEditUrl } from 'utils/routeHelpers';
+
+type Props = {
+ history: RouterHistory,
+ location: Location,
+ documents: DocumentsStore,
+ ui: UiStore,
+ match: Object,
+};
+
+class DocumentNew extends React.Component {
+ async componentDidMount() {
+ try {
+ const document = await this.props.documents.create({
+ collectionId: this.props.match.params.id,
+ parentDocumentId: new URLSearchParams(this.props.location.search).get(
+ 'parentDocumentId'
+ ),
+ title: '',
+ text: '',
+ });
+ this.props.history.replace(documentEditUrl(document));
+ } catch (err) {
+ this.props.ui.showToast('Couldn’t create the document, try again?');
+ this.props.history.goBack();
+ }
+ }
+
+ render() {
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+export default inject('documents', 'ui')(DocumentNew);
diff --git a/server/api/documents.js b/server/api/documents.js
index 0a152fa7..a14a2dba 100644
--- a/server/api/documents.js
+++ b/server/api/documents.js
@@ -585,15 +585,14 @@ router.post('documents.unstar', auth(), async ctx => {
router.post('documents.create', auth(), async ctx => {
const {
- title,
- text,
+ title = '',
+ text = '',
publish,
collectionId,
parentDocumentId,
index,
} = ctx.body;
ctx.assertUuid(collectionId, 'collectionId must be an uuid');
- ctx.assertPresent(text, 'text is required');
if (parentDocumentId) {
ctx.assertUuid(parentDocumentId, 'parentDocumentId must be an uuid');
}
diff --git a/server/api/documents.test.js b/server/api/documents.test.js
index 76f6f9cf..82f9c67d 100644
--- a/server/api/documents.test.js
+++ b/server/api/documents.test.js
@@ -1039,22 +1039,6 @@ describe('#documents.create', async () => {
expect(newDocument.collection.id).toBe(collection.id);
});
- it('should fallback to a default title', async () => {
- const { user, collection } = await seed();
- const res = await server.post('/api/documents.create', {
- body: {
- token: user.getJwtToken(),
- collectionId: collection.id,
- title: ' ',
- text: ' ',
- },
- });
- const body = await res.json();
- expect(res.status).toEqual(200);
- expect(body.data.title).toBe('Untitled document');
- expect(body.data.text).toBe('# Untitled document');
- });
-
it('should not allow very long titles', async () => {
const { user, collection } = await seed();
const res = await server.post('/api/documents.create', {
@@ -1183,25 +1167,6 @@ describe('#documents.update', async () => {
expect(revisionRecords).toBe(prevRevisionRecords);
});
- it('should fallback to a default title', async () => {
- const { user, document } = await seed();
-
- const res = await server.post('/api/documents.update', {
- body: {
- token: user.getJwtToken(),
- id: document.id,
- title: ' ',
- text: ' ',
- lastRevision: document.revision,
- },
- });
- const body = await res.json();
-
- expect(res.status).toEqual(200);
- expect(body.data.title).toBe('Untitled document');
- expect(body.data.text).toBe('# Untitled document');
- });
-
it('should fail if document lastRevision does not match', async () => {
const { user, document } = await seed();
diff --git a/server/models/Document.js b/server/models/Document.js
index fc2fe41a..8af2f21a 100644
--- a/server/models/Document.js
+++ b/server/models/Document.js
@@ -17,7 +17,7 @@ import Revision from './Revision';
const Op = Sequelize.Op;
const Markdown = new MarkdownSerializer();
const URL_REGEX = /^[a-zA-Z0-9-]*-([a-zA-Z0-9]{10,15})$/;
-const DEFAULT_TITLE = 'Untitled document';
+const DEFAULT_TITLE = 'Untitled';
slug.defaults.mode = 'rfc3986';
const slugify = text =>
@@ -55,10 +55,9 @@ const beforeSave = async doc => {
// emoji in the title is split out for easier display
doc.emoji = emoji;
- // ensure document has a title
+ // ensure documents have a title
if (!title) {
doc.title = DEFAULT_TITLE;
- doc.text = doc.text.replace(/^.*$/m, `# ${DEFAULT_TITLE}`);
}
// add the current user as a collaborator on this doc
diff --git a/server/presenters/document.js b/server/presenters/document.js
index 84ba31bf..83f10af6 100644
--- a/server/presenters/document.js
+++ b/server/presenters/document.js
@@ -13,11 +13,6 @@ export default async function present(document: Document, options: ?Options) {
...options,
};
- // For empty document content, return the title
- if (!document.text.trim()) {
- document.text = `# ${document.title}`;
- }
-
const data = {
id: document.id,
url: document.url,