diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 9094339..95c46a9 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -3315,14 +3315,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3337,20 +3335,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3467,8 +3462,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"ini": {
"version": "1.3.5",
@@ -3480,7 +3474,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3495,7 +3488,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -3503,14 +3495,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -3529,7 +3519,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3610,8 +3599,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -3623,7 +3611,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3745,7 +3732,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -4638,6 +4624,11 @@
"sshpk": "^1.7.0"
}
},
+ "http2": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/http2/-/http2-3.3.7.tgz",
+ "integrity": "sha512-puSi8M8WNlFJm9Pk4c/Mbz9Gwparuj3gO9/RRO5zv6piQ0FY+9Qywp0PdWshYgsMJSalixFY7eC6oPu0zRxLAQ=="
+ },
"https-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index d964871..70f974a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -20,6 +20,7 @@
"dependencies": {
"@blueprintjs/core": "^3.13.0",
"@blueprintjs/icons": "^3.6.0",
+ "http2": "^3.3.7",
"rea": "0.0.1",
"react": "^16.8.1",
"react-dom": "^16.8.1",
diff --git a/frontend/src/Documents/Docs.scss b/frontend/src/Documents/Docs.scss
index b10d22c..6974e05 100644
--- a/frontend/src/Documents/Docs.scss
+++ b/frontend/src/Documents/Docs.scss
@@ -34,6 +34,13 @@
color: $dark-gray5;
}
}
+ .card.newDocumentCard {
+ background-color: $light-gray5;
+ color: $dark-gray2;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
}
}
@@ -64,7 +71,6 @@
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
-
}
.bp3-dark {
diff --git a/frontend/src/Documents/DocsList.tsx b/frontend/src/Documents/DocsList.tsx
deleted file mode 100644
index a5758d5..0000000
--- a/frontend/src/Documents/DocsList.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import * as React from "react";
-import { IDocumentJSON } from "~../../src/entity/Document";
-
-import { DocumentCard } from "./DocumentCard";
-
-export function DocsList({ docs }: { docs: IDocumentJSON[] }) {
- const cards = docs.map(doc => );
- return
{cards}
;
-}
diff --git a/frontend/src/Documents/DocumentsList.tsx b/frontend/src/Documents/DocumentsList.tsx
new file mode 100644
index 0000000..c6f7399
--- /dev/null
+++ b/frontend/src/Documents/DocumentsList.tsx
@@ -0,0 +1,25 @@
+import * as React from "react";
+import { IDocumentJSON } from "~../../src/entity/Document";
+
+import { DocumentCard } from "./DocumentCard";
+import { NewDocumentCard } from "./NewDocumentCard";
+
+export interface IDocumentListProps {
+ docs: IDocumentJSON[];
+ newDocument?: boolean;
+}
+
+export function DocumentsList(props: IDocumentListProps) {
+ const cards = props.docs.map(doc => (
+
+ ));
+ if (props.newDocument) {
+ return (
+
+ {}
+ {cards}
+
+ );
+ }
+ return {cards}
;
+}
diff --git a/frontend/src/Documents/NewDocumentCard.tsx b/frontend/src/Documents/NewDocumentCard.tsx
new file mode 100644
index 0000000..bc837f1
--- /dev/null
+++ b/frontend/src/Documents/NewDocumentCard.tsx
@@ -0,0 +1,35 @@
+import { Card, Icon } from "@blueprintjs/core";
+import * as React from "react";
+import { connect } from "react-redux";
+import { RouteChildrenProps, withRouter } from "react-router";
+import { Dispatch } from "redux";
+import { newDocStart } from "~redux/docs/actions";
+
+export interface INewDocumentCardComponentProps extends RouteChildrenProps {
+ createNewDoc: () => void;
+}
+
+export function NewDocumentCardComponent(
+ props: INewDocumentCardComponentProps,
+) {
+ return (
+ props.createNewDoc()}
+ >
+
+
+ );
+}
+
+function mapDispatchToProps(dispatch: Dispatch) {
+ return { createNewDoc: () => dispatch(newDocStart()) };
+}
+
+export const NewDocumentCard = withRouter(
+ connect(
+ null,
+ mapDispatchToProps,
+ )(NewDocumentCardComponent),
+);
diff --git a/frontend/src/Documents/Overview.tsx b/frontend/src/Documents/Overview.tsx
index 5f9d4cf..6aed2d1 100644
--- a/frontend/src/Documents/Overview.tsx
+++ b/frontend/src/Documents/Overview.tsx
@@ -9,7 +9,7 @@ import { LoadingStub } from "~LoadingStub";
import { fetchDocsStart } from "~redux/docs/actions";
import { IAppState } from "~redux/reducers";
-import { DocsList } from "./DocsList";
+import { DocumentsList } from "./DocumentsList";
export interface IOverviewComponentProps {
allDocs: { [key: number]: IDocumentJSON };
@@ -43,12 +43,12 @@ export class OverviewComponent extends React.PureComponent<
Recent
-
+
All documents
-
+
);
diff --git a/frontend/src/redux/api/docs/index.ts b/frontend/src/redux/api/docs/index.ts
index 7937f07..f297834 100644
--- a/frontend/src/redux/api/docs/index.ts
+++ b/frontend/src/redux/api/docs/index.ts
@@ -16,7 +16,7 @@ export async function fetchAllDocs(): Promise> {
export async function fetchDoc(
id: number,
): Promise> {
- return fetchJSONAuth(`docs/byID/${id}`, "GET");
+ return fetchJSONAuth(`/docs/byID/${id}`, "GET");
}
export async function patchDoc(
@@ -24,9 +24,13 @@ export async function patchDoc(
name?: string,
content?: string,
): Promise> {
- return fetchJSONAuth(`docs/byID/${id}`, "PATCH", { name, content });
+ return fetchJSONAuth(`/docs/byID/${id}`, "PATCH", { name, content });
}
export async function deleteDoc(id: number): Promise> {
- return fetchJSONAuth(`docs/byID/${id}`, "DELETE");
+ return fetchJSONAuth(`/docs/byID/${id}`, "DELETE");
+}
+
+export async function createNewDoc(): Promise> {
+ return fetchJSONAuth(`/docs/new`, "POST", { name: "New Document" });
}
diff --git a/frontend/src/redux/docs/actions.ts b/frontend/src/redux/docs/actions.ts
index a8f5261..de8cb66 100644
--- a/frontend/src/redux/docs/actions.ts
+++ b/frontend/src/redux/docs/actions.ts
@@ -5,6 +5,11 @@ export enum DocsTypes {
DOCS_FETCH_START = "DOCS_FETCH_START",
DOCS_FETCH_FAIL = "DOCS_FETCH_FAIL",
DOCS_FETCH_SUCCESS = "DOCS_FETCH_SUCCESS",
+
+ DOC_NEW_START = "DOC_NEW_START",
+ DOC_NEW_FAIL = "DOC_NEW_FAIL",
+ DOC_NEW_SUCCESS = "DOC_NEW_SUCCESS",
+
DOCS_SHOW_SPINNER = "DOCS_SHOW_SPINNER",
}
@@ -48,8 +53,41 @@ export function fetchDocsSuccess(
return { type: DocsTypes.DOCS_FETCH_SUCCESS, payload: { all } };
}
+export interface IDocNewStartAction extends Action {
+ type: DocsTypes.DOC_NEW_START;
+}
+
+export interface IDocNewFailAction extends Action {
+ type: DocsTypes.DOC_NEW_FAIL;
+ payload: {
+ error: string;
+ };
+}
+
+export interface IDocNewSuccessAction extends Action {
+ type: DocsTypes.DOC_NEW_SUCCESS;
+ payload: {
+ doc: IDocumentJSON;
+ };
+}
+
+export function newDocStart(): IDocNewStartAction {
+ return { type: DocsTypes.DOC_NEW_START };
+}
+
+export function newDocFail(error: string): IDocNewFailAction {
+ return { type: DocsTypes.DOC_NEW_FAIL, payload: { error } };
+}
+
+export function newDocSuccess(doc: IDocumentJSON): IDocNewSuccessAction {
+ return { type: DocsTypes.DOC_NEW_SUCCESS, payload: { doc } };
+}
+
export type DocsAction =
| IDocsFetchStartAction
| IDocsFetchFailAction
| IDocsFetchSuccessAction
- | IDocsShowSpinnerAction;
+ | IDocsShowSpinnerAction
+ | IDocNewFailAction
+ | IDocNewStartAction
+ | IDocNewSuccessAction;
diff --git a/frontend/src/redux/docs/reducer.ts b/frontend/src/redux/docs/reducer.ts
index 1a78c7f..af6d741 100644
--- a/frontend/src/redux/docs/reducer.ts
+++ b/frontend/src/redux/docs/reducer.ts
@@ -26,12 +26,19 @@ export const docsReducer: Reducer = (
return { ...state, spinner: true };
case DocsTypes.DOCS_FETCH_START:
return { ...defaultDocsState, fetching: true };
- case DocsTypes.DOCS_FETCH_SUCCESS:
+ case DocsTypes.DOCS_FETCH_SUCCESS: {
const all: { [key: number]: IDocumentJSON } = {};
action.payload.all.forEach(doc => {
all[doc.id] = doc;
});
return { ...defaultDocsState, all };
+ }
+ case DocsTypes.DOC_NEW_SUCCESS: {
+ const all = { ...state.all };
+ const doc = action.payload.doc;
+ all[doc.id] = doc;
+ return { ...state, all };
+ }
case DocsTypes.DOCS_FETCH_FAIL:
return { ...defaultDocsState, ...action.payload };
default:
diff --git a/frontend/src/redux/docs/sagas.ts b/frontend/src/redux/docs/sagas.ts
index e4071a2..a45740b 100644
--- a/frontend/src/redux/docs/sagas.ts
+++ b/frontend/src/redux/docs/sagas.ts
@@ -8,13 +8,16 @@ import {
race,
takeLatest,
} from "redux-saga/effects";
-import { fetchAllDocs } from "~redux/api/docs";
+import { createNewDoc, fetchAllDocs } from "~redux/api/docs";
import {
DocsTypes,
fetchDocsFail,
fetchDocsSuccess,
+ IDocNewStartAction,
IDocsFetchStartAction,
+ newDocFail,
+ newDocSuccess,
showDocsSpinner,
} from "./actions";
@@ -52,6 +55,38 @@ function* docsFetchStart(action: IDocsFetchStartAction) {
}
}
-export function* docsSaga() {
- yield all([takeLatest(DocsTypes.DOCS_FETCH_START, docsFetchStart)]);
+function* docNewStart(action: IDocNewStartAction) {
+ try {
+ const spinner = yield fork(startSpinner);
+
+ const { response, timeout } = yield race({
+ response: call(createNewDoc),
+ timeout: delay(10000),
+ });
+
+ yield cancel(spinner);
+
+ if (timeout) {
+ yield put(newDocFail("Timeout"));
+ return;
+ }
+
+ if (response) {
+ if (response.data == null) {
+ yield put(newDocFail(response.error));
+ } else {
+ const newDoc = response.data;
+ yield put(newDocSuccess(newDoc));
+ }
+ }
+ } catch (e) {
+ yield put(newDocFail("Internal error"));
+ }
+}
+
+export function* docsSaga() {
+ yield all([
+ takeLatest(DocsTypes.DOCS_FETCH_START, docsFetchStart),
+ takeLatest(DocsTypes.DOC_NEW_START, docNewStart),
+ ]);
}
diff --git a/src/entity/Document.ts b/src/entity/Document.ts
index 0ba71f5..be884f0 100644
--- a/src/entity/Document.ts
+++ b/src/entity/Document.ts
@@ -28,7 +28,7 @@ export class Document extends BaseEntity {
@Column()
public name: string;
- @Column({ type: "text" })
+ @Column({ type: "text", default: "" })
public content: string;
@Column({ type: "timestamp", default: null })
diff --git a/src/routes/docs.ts b/src/routes/docs.ts
index f3a7393..8e2d163 100644
--- a/src/routes/docs.ts
+++ b/src/routes/docs.ts
@@ -15,7 +15,7 @@ docsRouter.post("/docs/new", async ctx => {
content: string | undefined;
};
- if (!(name && content)) {
+ if (!name) {
ctx.throw(400);
}