- {this.store.isFetching && }
+ {this.isFetching && }
{this.props.notFound &&
Not Found
@@ -125,7 +147,7 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
}
- {this.store.documents.map((document, index) => (
- index === 0 && this.setFirstDocumentRef(ref)}
- key={document.id}
- document={document}
- highlight={this.store.searchTerm}
- showCollection
- />
- ))}
+ {this.resultIds.map((documentId, index) => {
+ const document = documents.getById(documentId);
+ if (document)
+ return (
+
+ index === 0 && this.setFirstDocumentRef(ref)}
+ key={documentId}
+ document={document}
+ highlight={this.searchTerm}
+ showCollection
+ />
+ );
+ })}
@@ -152,4 +179,4 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
}
}
-export default withRouter(Search);
+export default withRouter(inject('documents')(Search));
diff --git a/frontend/scenes/Search/SearchStore.js b/frontend/scenes/Search/SearchStore.js
deleted file mode 100644
index 68088ce80..000000000
--- a/frontend/scenes/Search/SearchStore.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// @flow
-import { observable, action, runInAction } from 'mobx';
-import invariant from 'invariant';
-import { client } from 'utils/ApiClient';
-import Document from 'models/Document';
-
-class SearchStore {
- @observable documents: Array = [];
- @observable searchTerm: ?string = null;
- @observable isFetching = false;
-
- /* Actions */
-
- @action search = async (query: string) => {
- this.searchTerm = query;
- this.isFetching = true;
-
- if (query) {
- try {
- const res = await client.get('/documents.search', { query });
- invariant(res && res.data, 'res or res.data missing');
- const { data } = res;
- runInAction('search document', () => {
- this.documents = data.map(documentData => new Document(documentData));
- });
- } catch (e) {
- console.error('Something went wrong');
- }
- } else {
- this.documents = [];
- }
-
- this.isFetching = false;
- };
-}
-
-export default SearchStore;
diff --git a/frontend/static/flatpages/keyboard.md b/frontend/static/flatpages/keyboard.md
index 8d0eeeb02..56e604a0c 100644
--- a/frontend/static/flatpages/keyboard.md
+++ b/frontend/static/flatpages/keyboard.md
@@ -1,8 +1,9 @@
- `Cmd+Enter` - Save and exit document editor
-- `Cmd+S` - Save document and continue editing
+- `Cmd+s` - Save document and continue editing
- `Cmd+Esc` - Cancel edit
- `/` or `t` - Jump to search
- `d` - Jump to dashboard
- `c` - Compose within a collection
- `e` - Edit document
+- `m` - Move document
- `?` - This guide
diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js
index 7ccedcad5..c7e03e975 100644
--- a/frontend/stores/DocumentsStore.js
+++ b/frontend/stores/DocumentsStore.js
@@ -104,6 +104,14 @@ class DocumentsStore extends BaseStore {
await this.fetchAll('starred');
};
+ @action search = async (query: string): Promise<*> => {
+ const res = await client.get('/documents.search', { query });
+ invariant(res && res.data, 'res or res.data missing');
+ const { data } = res;
+ data.forEach(documentData => this.add(new Document(documentData)));
+ return data.map(documentData => documentData.id);
+ };
+
@action fetch = async (id: string): Promise<*> => {
this.isFetching = true;
@@ -138,8 +146,11 @@ class DocumentsStore extends BaseStore {
return this.data.get(id);
};
+ /**
+ * Match documents by the url ID as the title slug can change
+ */
getByUrl = (url: string): ?Document => {
- return _.find(this.data.values(), { url });
+ return _.find(this.data.values(), doc => url.endsWith(doc.urlId));
};
constructor(options: Options) {
diff --git a/frontend/utils/routeHelpers.js b/frontend/utils/routeHelpers.js
index ce11b20f3..e501f2a98 100644
--- a/frontend/utils/routeHelpers.js
+++ b/frontend/utils/routeHelpers.js
@@ -38,3 +38,19 @@ export function searchUrl(query?: string): string {
export function notFoundUrl(): string {
return '/404';
}
+
+export const matchDocumentSlug =
+ ':documentSlug([0-9a-zA-Z-]*-[a-zA-z0-9]{10,15})';
+
+export const matchDocumentEdit = `/doc/${matchDocumentSlug}/edit`;
+export const matchDocumentMove = `/doc/${matchDocumentSlug}/move`;
+
+/**
+ * Replace full url's document part with the new one in case
+ * the document slug has been updated
+ */
+export function updateDocumentUrl(oldUrl: string, newUrl: string): string {
+ // Update url to match the current one
+ const urlParts = oldUrl.split('/');
+ return [newUrl, urlParts.slice(3)].join('/');
+}
diff --git a/package.json b/package.json
index 7fc4d76ef..bd9d3faa2 100644
--- a/package.json
+++ b/package.json
@@ -96,6 +96,7 @@
"eslint-plugin-prettier": "^2.0.1",
"eslint-plugin-react": "^6.10.3",
"exports-loader": "0.6.3",
+ "extract-text-webpack-plugin": "1.0.1",
"fbemitter": "^2.1.1",
"file-loader": "0.9.0",
"flow-typed": "^2.1.2",
diff --git a/server/api/documents.js b/server/api/documents.js
index 178e590aa..2eb51b4f9 100644
--- a/server/api/documents.js
+++ b/server/api/documents.js
@@ -211,6 +211,8 @@ router.post('documents.create', auth(), async ctx => {
await ownerCollection.addDocumentToStructure(document, index);
}
+ document.collection = ownerCollection;
+
ctx.body = {
data: await presentDocument(ctx, document),
};
@@ -279,6 +281,10 @@ router.post('documents.move', auth(), async ctx => {
await collection.deleteDocument(document);
await collection.addDocumentToStructure(document, index);
}
+ // Update collection
+ document.collection = collection;
+
+ document.collection = collection;
ctx.body = {
data: await presentDocument(ctx, document),
diff --git a/server/presenters/document.js b/server/presenters/document.js
index 0ca0ea186..5927a83d9 100644
--- a/server/presenters/document.js
+++ b/server/presenters/document.js
@@ -17,6 +17,7 @@ async function present(ctx: Object, document: Document, options: ?Options) {
const data = {
id: document.id,
url: document.getUrl(),
+ urlId: document.urlId,
private: document.private,
title: document.title,
text: document.text,
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index 2a569fed1..a09773b95 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -1,6 +1,7 @@
/* eslint-disable */
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
+var ExtractTextPlugin = require('extract-text-webpack-plugin');
const commonWebpackConfig = require('./webpack.config');
@@ -18,6 +19,7 @@ const developmentWebpackConfig = Object.assign(commonWebpackConfig, {
developmentWebpackConfig.plugins.push(
new webpack.optimize.OccurenceOrderPlugin()
);
+developmentWebpackConfig.plugins.push(new ExtractTextPlugin('styles.css'));
developmentWebpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
developmentWebpackConfig.plugins.push(new webpack.NoErrorsPlugin());
developmentWebpackConfig.plugins.push(
diff --git a/webpack.config.js b/webpack.config.js
index 1be0fdfdb..f99b890cf 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,6 +1,7 @@
/* eslint-disable */
const path = require('path');
const webpack = require('webpack');
+var ExtractTextPlugin = require('extract-text-webpack-plugin');
require('dotenv').config({ silent: true });
@@ -40,7 +41,7 @@ module.exports = {
},
{
test: /\.css$/,
- loader: 'style-loader!css-loader?sourceMap',
+ loader: ExtractTextPlugin.extract('style-loader', 'css-loader'),
},
{ test: /\.md/, loader: 'raw-loader' },
],
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
index 5f593eb5c..f3e0b0dfa 100644
--- a/webpack.config.prod.js
+++ b/webpack.config.prod.js
@@ -2,6 +2,7 @@
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
+var ExtractTextPlugin = require('extract-text-webpack-plugin');
commonWebpackConfig = require('./webpack.config');
@@ -20,6 +21,9 @@ productionWebpackConfig.plugins.push(
template: 'server/static/index.html',
})
);
+productionWebpackConfig.plugins.push(
+ new ExtractTextPlugin('styles.[hash].css')
+);
productionWebpackConfig.plugins.push(
new webpack.optimize.OccurenceOrderPlugin()
);
diff --git a/yarn.lock b/yarn.lock
index f446a4752..3b83cf3ba 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -306,7 +306,7 @@ async@^0.9.0:
version "0.9.2"
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
-async@^1.3.0, async@^1.4.0:
+async@^1.3.0, async@^1.4.0, async@^1.5.0:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -3035,6 +3035,14 @@ extglob@^0.3.1:
dependencies:
is-extglob "^1.0.0"
+extract-text-webpack-plugin@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-1.0.1.tgz#c95bf3cbaac49dc96f1dc6e072549fbb654ccd2c"
+ dependencies:
+ async "^1.5.0"
+ loader-utils "^0.2.3"
+ webpack-sources "^0.1.0"
+
extsprintf@1.3.0, extsprintf@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@@ -5298,7 +5306,7 @@ load-json-file@^2.0.0:
pify "^2.0.0"
strip-bom "^3.0.0"
-loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.14, loader-utils@~0.2.5:
+loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.14, loader-utils@^0.2.3, loader-utils@~0.2.5:
version "0.2.17"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
dependencies:
@@ -8190,7 +8198,7 @@ source-map@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
-source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1:
+source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@@ -9100,6 +9108,13 @@ webpack-hot-middleware@2.x:
querystring "^0.2.0"
strip-ansi "^3.0.0"
+webpack-sources@^0.1.0:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750"
+ dependencies:
+ source-list-map "~0.1.7"
+ source-map "~0.5.3"
+
webpack@1.13.2:
version "1.13.2"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.13.2.tgz#f11a96f458eb752970a86abe746c0704fabafaf3"