diff --git a/frontend/components/AtlasPreview/AtlasPreview.js b/frontend/components/AtlasPreview/AtlasPreview.js
index 000be7efc..6eca19c38 100644
--- a/frontend/components/AtlasPreview/AtlasPreview.js
+++ b/frontend/components/AtlasPreview/AtlasPreview.js
@@ -5,8 +5,8 @@ import Link from 'react-router/lib/Link';
import DocumentLink from './components/DocumentLink';
import styles from './AtlasPreview.scss';
-import classNames from 'classnames/bind';
-const cx = classNames.bind(styles);
+// import classNames from 'classnames/bind';
+// const cx = classNames.bind(styles);
@observer
class AtlasPreview extends React.Component {
@@ -19,18 +19,21 @@ class AtlasPreview extends React.Component {
return (
-
{ data.name }
+
{ data.name }
{ data.recentDocuments.length > 0 ?
data.recentDocuments.map(document => {
return (
-
)
+
+ );
})
: (
-
No documents. Why not create one?
+
+ No documents. Why not create one?
+
) }
);
}
-};
+}
export default AtlasPreview;
diff --git a/frontend/components/AtlasPreview/components/DocumentLink/DocumentLink.js b/frontend/components/AtlasPreview/components/DocumentLink/DocumentLink.js
index 6b8f452d7..b413a23bf 100644
--- a/frontend/components/AtlasPreview/components/DocumentLink/DocumentLink.js
+++ b/frontend/components/AtlasPreview/components/DocumentLink/DocumentLink.js
@@ -8,7 +8,7 @@ import styles from './DocumentLink.scss';
const DocumentLink = observer((props) => {
return (
-
+
{ props.document.title }
{ moment(props.document.updatedAt).fromNow() }
diff --git a/frontend/components/DocumentPreview/DocumentPreview.js b/frontend/components/DocumentPreview/DocumentPreview.js
index 8ecc55033..3c9c6fce5 100644
--- a/frontend/components/DocumentPreview/DocumentPreview.js
+++ b/frontend/components/DocumentPreview/DocumentPreview.js
@@ -24,7 +24,7 @@ class Document extends React.Component {
/>
{ this.props.document.title }
@@ -34,7 +34,7 @@ class Document extends React.Component {
Continue reading...
diff --git a/frontend/index.js b/frontend/index.js
index 2a8b070cd..83636a3fb 100644
--- a/frontend/index.js
+++ b/frontend/index.js
@@ -57,10 +57,10 @@ render((
onEnter={ requireAuth }
newDocument
/>
-
-
+
+
{
if (event) event.preventDefault();
- browserHistory.push(`/collections/${store.collection.id}/new`);
+ browserHistory.push(`${store.collection.url}/new`);
}
render() {
diff --git a/frontend/scenes/DocumentEdit/DocumentEditStore.js b/frontend/scenes/DocumentEdit/DocumentEditStore.js
index 3bda46f80..3e8ed2a51 100644
--- a/frontend/scenes/DocumentEdit/DocumentEditStore.js
+++ b/frontend/scenes/DocumentEdit/DocumentEditStore.js
@@ -67,10 +67,10 @@ class DocumentEditStore {
title: this.title,
text: this.text,
}, { cache: true });
- const { id } = data.data;
+ const { url } = data.data;
this.hasPendingChanges = false;
- browserHistory.push(`/documents/${id}`);
+ browserHistory.push(url);
} catch (e) {
console.error("Something went wrong");
}
@@ -83,14 +83,15 @@ class DocumentEditStore {
this.isSaving = true;
try {
- await client.post('/documents.update', {
+ const data = await client.post('/documents.update', {
id: this.documentId,
title: this.title,
text: this.text,
}, { cache: true });
+ const { url } = data.data;
this.hasPendingChanges = false;
- browserHistory.push(`/documents/${this.documentId}`);
+ browserHistory.push(url);
} catch (e) {
console.error("Something went wrong");
}
diff --git a/frontend/scenes/DocumentScene/DocumentScene.js b/frontend/scenes/DocumentScene/DocumentScene.js
index fde2f1214..e1fd7e9c4 100644
--- a/frontend/scenes/DocumentScene/DocumentScene.js
+++ b/frontend/scenes/DocumentScene/DocumentScene.js
@@ -90,12 +90,12 @@ class DocumentScene extends React.Component {
}
onEdit = () => {
- const url = `/documents/${this.store.document.id}/edit`;
+ const url = `${this.store.document.url}/edit`;
browserHistory.push(url);
}
onCreate = () => {
- const url = `/documents/${this.store.document.id}/new`;
+ const url = `${this.store.document.url}/new`;
browserHistory.push(url);
}
@@ -147,7 +147,7 @@ class DocumentScene extends React.Component {
title = (
/
- { doc.collection.name }
+ { doc.collection.name }
{ ` / ${doc.title}` }
);
diff --git a/frontend/scenes/DocumentScene/DocumentSceneStore.js b/frontend/scenes/DocumentScene/DocumentSceneStore.js
index ff7d8ebab..3db364ba9 100644
--- a/frontend/scenes/DocumentScene/DocumentSceneStore.js
+++ b/frontend/scenes/DocumentScene/DocumentSceneStore.js
@@ -72,7 +72,7 @@ class DocumentSceneStore {
try {
await client.post('/documents.delete', { id: this.document.id });
- browserHistory.push(`/collections/${this.document.collection.id}`);
+ browserHistory.push(this.document.collection.url);
} catch (e) {
console.error("Something went wrong");
}
diff --git a/frontend/scenes/DocumentScene/components/Sidebar/Sidebar.js b/frontend/scenes/DocumentScene/components/Sidebar/Sidebar.js
index 4998f3fbb..a7de8daf5 100644
--- a/frontend/scenes/DocumentScene/components/Sidebar/Sidebar.js
+++ b/frontend/scenes/DocumentScene/components/Sidebar/Sidebar.js
@@ -53,7 +53,7 @@ class Sidebar extends React.Component {
Add document
diff --git a/server/api/documents.js b/server/api/documents.js
index 15bd98840..3a8ba59fa 100644
--- a/server/api/documents.js
+++ b/server/api/documents.js
@@ -4,6 +4,8 @@ import {
sequelize,
} from '../sequelize';
+const URL_REGEX = /^[a-zA-Z0-9-]*-([a-zA-Z0-9]{15})$/;
+
import auth from './authentication';
// import pagination from './middlewares/pagination';
import { presentDocument } from '../presenters';
@@ -11,16 +13,29 @@ import { Document, Atlas } from '../models';
const router = new Router();
+const getDocumentForId = async (id) => {
+ let document;
+ if (id.match(URL_REGEX)) {
+ document = await Document.findOne({
+ where: {
+ urlId: id.match(URL_REGEX)[1],
+ },
+ });
+ } else {
+ document = await Document.findOne({
+ where: {
+ id,
+ },
+ });
+ }
+ return document;
+};
+
// FIXME: This really needs specs :/
-router.post('documents.info', auth({ require: false }), async (ctx) => {
+router.post('documents.info', auth(), async (ctx) => {
const { id } = ctx.body;
ctx.assertPresent(id, 'id is required');
-
- const document = await Document.findOne({
- where: {
- id,
- },
- });
+ const document = await getDocumentForId(id);
if (!document) throw httpErrors.NotFound();
@@ -156,14 +171,9 @@ router.post('documents.update', auth(), async (ctx) => {
ctx.assertPresent(text, 'text is required');
const user = ctx.state.user;
- const document = await Document.findOne({
- where: {
- id,
- teamId: user.teamId,
- },
- });
+ const document = await getDocumentForId(id);
- if (!document) throw httpErrors.BadRequest();
+ if (!document || document.teamId !== user.teamId) throw httpErrors.BadRequest();
// Update document
document.title = title;
@@ -192,15 +202,10 @@ router.post('documents.delete', auth(), async (ctx) => {
ctx.assertPresent(id, 'id is required');
const user = ctx.state.user;
- const document = await Document.findOne({
- where: {
- id,
- teamId: user.teamId,
- },
- });
+ const document = await getDocumentForId(id);
const collection = await Atlas.findById(document.atlasId);
- if (!document) throw httpErrors.BadRequest();
+ if (!document || document.teamId !== user.teamId) throw httpErrors.BadRequest();
if (collection.type === 'atlas') {
// Don't allow deletion of root docs
diff --git a/server/migrations/20160815142720-app-collection-urlId.js b/server/migrations/20160815142720-app-collection-urlId.js
new file mode 100644
index 000000000..bd9c1aeff
--- /dev/null
+++ b/server/migrations/20160815142720-app-collection-urlId.js
@@ -0,0 +1,18 @@
+'use strict';
+
+module.exports = {
+ up: function (queryInterface, Sequelize) {
+ queryInterface.addColumn(
+ 'atlases',
+ 'urlId',
+ {
+ type: Sequelize.STRING,
+ unique: true,
+ }
+ );
+ },
+
+ down: function (queryInterface, Sequelize) {
+ queryInterface.removeColumn('atlases', 'urlId');
+ }
+};
diff --git a/server/models/Atlas.js b/server/models/Atlas.js
index 5938804bd..b4b0672b5 100644
--- a/server/models/Atlas.js
+++ b/server/models/Atlas.js
@@ -1,3 +1,5 @@
+import slug from 'slug';
+import randomstring from 'randomstring';
import {
DataTypes,
sequelize,
@@ -5,10 +7,13 @@ import {
import _ from 'lodash';
import Document from './Document';
+slug.defaults.mode = 'rfc3986';
+
const allowedAtlasTypes = [['atlas', 'journal']];
const Atlas = sequelize.define('atlas', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
+ urlId: { type: DataTypes.STRING, unique: true },
name: DataTypes.STRING,
description: DataTypes.STRING,
type: { type: DataTypes.STRING, validate: { isIn: allowedAtlasTypes } },
@@ -20,6 +25,9 @@ const Atlas = sequelize.define('atlas', {
tableName: 'atlases',
paranoid: true,
hooks: {
+ beforeValidate: (collection) => {
+ collection.urlId = collection.urlId || randomstring.generate(10);
+ },
afterCreate: async (collection) => {
if (collection.type !== 'atlas') return;
@@ -38,8 +46,12 @@ const Atlas = sequelize.define('atlas', {
},
},
instanceMethods: {
+ getUrl() {
+ // const slugifiedName = slug(this.name);
+ // return `/${slugifiedName}-c${this.urlId}`;
+ return `/collections/${this.id}`;
+ },
async buildStructure() {
- console.log('start');
if (this.navigationTree) return this.navigationTree;
const getNodeForDocument = async (document) => {
diff --git a/server/models/Document.js b/server/models/Document.js
index 830ae14ba..43ea21899 100644
--- a/server/models/Document.js
+++ b/server/models/Document.js
@@ -16,11 +16,6 @@ import Revision from './Revision';
slug.defaults.mode = 'rfc3986';
-const generateSlug = (title, urlId) => {
- const slugifiedTitle = slug(title);
- return `${slugifiedTitle}-${urlId}`;
-};
-
const createRevision = async (doc) => {
// Create revision of the current (latest)
await Revision.create({
@@ -80,7 +75,7 @@ const Document = sequelize.define('document', {
paranoid: true,
hooks: {
beforeValidate: (doc) => {
- doc.urlId = randomstring.generate(15);
+ doc.urlId = doc.urlId || randomstring.generate(10);
},
beforeCreate: documentBeforeSave,
beforeUpdate: documentBeforeSave,
@@ -88,12 +83,9 @@ const Document = sequelize.define('document', {
afterUpdate: async (doc) => await createRevision(doc),
},
instanceMethods: {
- buildUrl() {
- const slugifiedTitle = slug(this.title);
- return `${slugifiedTitle}-${this.urlId}`;
- },
getUrl() {
- return `/documents/${this.id}`;
+ const slugifiedTitle = slug(this.title);
+ return `/d/${slugifiedTitle}-${this.urlId}`;
},
},
});
diff --git a/server/presenters.js b/server/presenters.js
index ada922646..83e64f87b 100644
--- a/server/presenters.js
+++ b/server/presenters.js
@@ -37,7 +37,7 @@ export async function presentDocument(ctx, document, options) {
const data = {
id: document.id,
- url: document.buildUrl(),
+ url: document.getUrl(),
private: document.private,
title: document.title,
text: document.text,
@@ -96,6 +96,7 @@ export function presentCollection(ctx, collection, includeRecentDocuments=false)
return new Promise(async (resolve, _reject) => {
const data = {
id: collection.id,
+ url: collection.getUrl(),
name: collection.name,
description: collection.description,
type: collection.type,