diff --git a/server/api/atlases.js b/server/api/atlases.js
index b34e871c..3234d4d0 100644
--- a/server/api/atlases.js
+++ b/server/api/atlases.js
@@ -56,4 +56,26 @@ router.post('atlases.list', auth(), pagination(), async (ctx) => {
};
});
+router.post('atlases.updateNavigationTree', auth(), async (ctx) => {
+ let { id, tree } = ctx.request.body;
+ ctx.assertPresent(id, 'id is required');
+
+ const user = ctx.state.user;
+ const atlas = await Atlas.findOne({
+ where: {
+ id: id,
+ teamId: user.teamId,
+ },
+ });
+
+ if (!atlas) throw httpErrors.NotFound();
+
+ const newTree = await atlas.updateNavigationTree(tree);
+
+ ctx.body = {
+ data: await presentAtlas(atlas, true),
+ tree: newTree,
+ };
+});
+
export default router;
diff --git a/server/api/documents.js b/server/api/documents.js
index 2fef9508..9d8e0f94 100644
--- a/server/api/documents.js
+++ b/server/api/documents.js
@@ -46,6 +46,7 @@ router.post('documents.create', auth(), async (ctx) => {
atlas,
title,
text,
+ parentDocument,
} = ctx.request.body;
ctx.assertPresent(atlas, 'atlas is required');
ctx.assertPresent(title, 'title is required');
@@ -61,7 +62,18 @@ router.post('documents.create', auth(), async (ctx) => {
if (!ownerAtlas) throw httpErrors.BadRequest();
+ let parentDocumentObj;
+ if (parentDocument && ownerAtlas.type === 'atlas') {
+ parentDocumentObj = await Document.findOne({
+ where: {
+ id: parentDocument,
+ atlasId: ownerAtlas.id,
+ },
+ });
+ }
+
const document = await Document.create({
+ parentDocumentId: parentDocumentObj.id,
atlasId: ownerAtlas.id,
teamId: user.teamId,
userId: user.id,
@@ -69,6 +81,10 @@ router.post('documents.create', auth(), async (ctx) => {
text: text,
});
+ // TODO: Move to afterSave hook if possible with imports
+ ownerAtlas.addNodeToNavigationTree(document);
+ await ownerAtlas.save();
+
ctx.body = {
data: await presentDocument(document, true),
};
diff --git a/server/models/Atlas.js b/server/models/Atlas.js
index a0bcb6f6..f8af3815 100644
--- a/server/models/Atlas.js
+++ b/server/models/Atlas.js
@@ -2,6 +2,7 @@ import {
DataTypes,
sequelize,
} from '../sequelize';
+import _isEqual from 'lodash/isEqual';
import Document from './Document';
const allowedAtlasTypes = [['atlas', 'journal']];
@@ -30,7 +31,11 @@ const Atlas = sequelize.define('atlas', {
// },
},
instanceMethods: {
- async buildStructure() {
+ async getStructure() {
+ if (this.atlasStructure) {
+ return this.atlasStructure;
+ }
+
const getNodeForDocument = async (document) => {
const children = await Document.findAll({ where: {
parentDocumentId: document.id,
@@ -39,28 +44,110 @@ const Atlas = sequelize.define('atlas', {
let childNodes = []
await Promise.all(children.map(async (child) => {
- console.log(child.id)
childNodes.push(await getNodeForDocument(child));
}));
return {
- name: document.title,
+ title: document.title,
id: document.id,
url: document.getUrl(),
children: childNodes,
};
}
- const rootDocument = await Document.findOne({ where: {
- parentDocumentId: null,
- atlasId: this.id,
- }});
+ const rootDocument = await Document.findOne({
+ where: {
+ parentDocumentId: null,
+ atlasId: this.id,
+ }
+ });
if (rootDocument) {
return await getNodeForDocument(rootDocument);
} else {
return; // TODO should create a root doc
}
+ },
+ async updateNavigationTree(tree) {
+ let nodeIds = [];
+ nodeIds.push(tree.id);
+
+ const rootDocument = await Document.findOne({
+ where: {
+ id: tree.id,
+ atlasId: this.id,
+ },
+ });
+ if (!rootDocument) throw new Error;
+
+ let newTree = {
+ id: tree.id,
+ title: rootDocument.title,
+ url: rootDocument.getUrl(),
+ children: [],
+ };
+
+ const getIdsForChildren = async (children) => {
+ const childNodes = [];
+ for (const child of children) {
+ const childDocument = await Document.findOne({
+ where: {
+ id: child.id,
+ atlasId: this.id,
+ },
+ });
+ if (!childDocument) throw new Error;
+
+ childNodes.push({
+ id: childDocument.id,
+ title: childDocument.title,
+ url: childDocument.getUrl(),
+ children: await getIdsForChildren(child.children),
+ })
+ nodeIds.push(child.id);
+ }
+ return childNodes;
+ };
+ newTree.children = await getIdsForChildren(tree.children);
+
+ const documents = await Document.findAll({
+ attributes: ['id'],
+ where: {
+ atlasId: this.id,
+ }
+ });
+ const documentIds = documents.map(doc => doc.id);
+
+ if (!_isEqual(nodeIds.sort(), documentIds.sort())) {
+ throw new Error('Invalid navigation tree');
+ }
+
+ this.atlasStructure = newTree;
+ await this.save();
+
+ return newTree;
+ },
+ async addNodeToNavigationTree(document) {
+ const newNode = {
+ id: document.id,
+ title: document.title,
+ url: document.getUrl(),
+ children: [],
+ }
+
+ const insertNode = (node) => {
+ if (document.parentDocumentId === node.id) {
+ node.children.push(newNode);
+ } else {
+ node.children = node.children.map(childNode => {
+ return insertNode(childNode);
+ })
+ }
+
+ return node;
+ };
+
+ this.atlasStructure = insertNode(this.atlasStructure);
}
}
});
diff --git a/server/presenters.js b/server/presenters.js
index 05f27c18..2441dcc0 100644
--- a/server/presenters.js
+++ b/server/presenters.js
@@ -33,7 +33,7 @@ export function presentAtlas(atlas, includeRecentDocuments=false) {
if (atlas.type === 'atlas') {
// Todo replace with `.atlasStructure`
- data.structure = await atlas.buildStructure();
+ data.structure = await atlas.getStructure();
}
if (includeRecentDocuments) {
diff --git a/src/components/Tree/Node.js b/src/components/Tree/Node.js
index fc5bc6c0..ab980dcf 100644
--- a/src/components/Tree/Node.js
+++ b/src/components/Tree/Node.js
@@ -1,4 +1,5 @@
var React = require('react');
+import history from 'utils/History';
import styles from './Tree.scss';
import classNames from 'classnames/bind';
@@ -79,10 +80,10 @@ var Node = React.createClass({
{!this.props.rootNode && this.renderCollapse()}
{}}
+ onClick={() => { history.push(node.url) }}
onMouseDown={this.props.rootNode ? function(e){e.stopPropagation()} : undefined}
>
- {node.name}
+ { node.title }
{this.renderChildren()}
diff --git a/src/index.js b/src/index.js
index 3b1a83ff..38cca8e3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -44,6 +44,7 @@ render((