diff --git a/package.json b/package.json
index 09f274fee..bc215ea05 100644
--- a/package.json
+++ b/package.json
@@ -89,6 +89,7 @@
"react-router-redux": "^4.0.4",
"rebass": "^0.2.6",
"redux": "^3.3.1",
+ "redux-actions": "^0.9.1",
"redux-logger": "^2.6.1",
"redux-persist": "3.0.3",
"redux-thunk": "^2.0.1",
diff --git a/server/api/documents.js b/server/api/documents.js
index 85077f66c..b96c45149 100644
--- a/server/api/documents.js
+++ b/server/api/documents.js
@@ -62,4 +62,34 @@ router.post('documents.create', auth(), async (ctx) => {
};
});
+router.post('documents.update', auth(), async (ctx) => {
+ let {
+ id,
+ title,
+ text,
+ } = ctx.request.body;
+ ctx.assertPresent(id, 'id is required');
+ ctx.assertPresent(title, 'title is required');
+ ctx.assertPresent(text, 'text is required');
+
+ const user = ctx.state.user;
+ const team = await user.getTeam();
+ let document = await Document.findOne({
+ where: {
+ id: id,
+ teamId: team.id,
+ },
+ });
+
+ if (!document) throw httpErrors.BadRequest();
+
+ document.title = title;
+ document.text = text;
+ await document.save();
+
+ ctx.body = {
+ data: await presentDocument(document, true),
+ };
+});
+
export default router;
\ No newline at end of file
diff --git a/server/models/Document.js b/server/models/Document.js
index c6995e6d9..9e8544a9b 100644
--- a/server/models/Document.js
+++ b/server/models/Document.js
@@ -22,6 +22,10 @@ const Document = sequelize.define('document', {
doc.html = convertToMarkdown(doc.text);
doc.preview = truncateMarkdown(doc.text, 160);
},
+ beforeUpdate: (doc) => {
+ doc.html = convertToMarkdown(doc.text);
+ doc.preview = truncateMarkdown(doc.text, 160);
+ },
}
});
diff --git a/src/actions/DocumentActions.js b/src/actions/DocumentActions.js
index 877985382..54a93a940 100644
--- a/src/actions/DocumentActions.js
+++ b/src/actions/DocumentActions.js
@@ -1,6 +1,9 @@
import makeActionCreator from '../utils/actions';
import { replace } from 'react-router-redux';
import { client } from 'utils/ApiClient';
+import { createAction } from 'redux-actions';
+
+export const resetDocument = createAction('RESET_DOCUMENT');
export const FETCH_DOCUMENT_PENDING = 'FETCH_DOCUMENT_PENDING';
export const FETCH_DOCUMENT_SUCCESS = 'FETCH_DOCUMENT_SUCCESS';
@@ -39,18 +42,19 @@ export function saveDocumentAsync(atlasId, documentId, title, text) {
dispatch(saveDocumentPending());
let url;
- if (documentId) {
- url = '/documents.update'
- } else {
- url = '/documents.create'
- }
-
- client.post(url, {
- atlas: atlasId,
- document: documentId,
+ let data = {
title,
text,
- })
+ };
+ if (documentId) {
+ url = '/documents.update';
+ data.id = documentId;
+ } else {
+ url = '/documents.create';
+ data.atlas = atlasId;
+ }
+
+ client.post(url, data)
.then(data => {
dispatch(saveDocumentSuccess(data.data, data.pagination));
dispatch(replace(`/documents/${data.data.id}`));
diff --git a/src/actions/EditorActions.js b/src/actions/EditorActions.js
index 2f5726e35..329230094 100644
--- a/src/actions/EditorActions.js
+++ b/src/actions/EditorActions.js
@@ -1,9 +1,7 @@
-import makeActionCreator from '../utils/actions';
+import { createAction } from 'redux-actions';
-// export const TOGGLE_PREVIEW = 'TOGGLE_PREVIEW';
-export const UPDATE_TEXT = 'UPDATE_TEXT';
-export const REPLACE_TEXT = 'REPLACE_TEXT';
+export const resetEditor = createAction('EDITOR_RESET');
+export const updateText = createAction('EDITOR_UPDATE_TEXT');
+export const updateTitle = createAction('EDITOR_UPDATE_TITLE');
+export const replaceText = createAction('EDITOR_REPLACE_TEXT');
-// export const togglePreview = makeActionCreator(TOGGLE_PREVIEW);
-export const updateText = makeActionCreator(UPDATE_TEXT, 'text');
-export const replaceText = makeActionCreator(REPLACE_TEXT, 'originalText', 'replacedText');
diff --git a/src/components/MarkdownEditor/MarkdownEditor.js b/src/components/MarkdownEditor/MarkdownEditor.js
index b0e730e71..de222b591 100644
--- a/src/components/MarkdownEditor/MarkdownEditor.js
+++ b/src/components/MarkdownEditor/MarkdownEditor.js
@@ -30,10 +30,6 @@ class MarkdownAtlas extends React.Component {
}
}
- componentDidMount = () => {
- console.log(this.props);
- }
-
onDropAccepted = (files) => {
const file = files[0];
const editor = this.getEditorInstance();
@@ -78,11 +74,17 @@ class MarkdownAtlas extends React.Component {
body: formData
})
.then(s3Response => {
- this.props.replaceText(pendingUploadTag, ``);
+ this.props.replaceText({
+ original: pendingUploadTag,
+ new: ``
+ });
editor.setCursor(newCursorPositionLine, 0);
})
.catch(err => {
- this.props.replaceText(pendingUploadTag, '');
+ this.props.replaceText({
+ original: pendingUploadTag,
+ new: '',
+ });
editor.setCursor(newCursorPositionLine, 0);
});
});
diff --git a/src/index.js b/src/index.js
index 5f121fc25..7629245f5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -19,6 +19,7 @@ import 'normalize.css/normalize.css';
import 'utils/base-styles.scss';
import 'fonts/atlas/atlas.css';
import 'assets/styles/github-gist.scss';
+import 'assets/styles/codemirror.css';
import Application from 'scenes/Application';
@@ -27,6 +28,7 @@ import Editor from 'scenes/Editor';
import Dashboard from 'scenes/Dashboard';
import Atlas from 'scenes/Atlas';
import DocumentScene from 'scenes/DocumentScene';
+import DocumentEdit from 'scenes/DocumentEdit';
import SlackAuth from 'scenes/SlackAuth';
// Redux
@@ -63,6 +65,7 @@ persistStore(store, {
+
diff --git a/src/reducers/document.js b/src/reducers/document.js
index e81293cfe..4a2fb04c6 100644
--- a/src/reducers/document.js
+++ b/src/reducers/document.js
@@ -16,6 +16,11 @@ const initialState = {
const doc = (state = initialState, action) => {
switch (action.type) {
+ case 'RESET_DOCUMENT': {
+ return {
+ ...initialState,
+ }
+ }
case FETCH_DOCUMENT_PENDING: {
return {
...state,
diff --git a/src/reducers/editor.js b/src/reducers/editor.js
index dd1ae2dc5..2c1075097 100644
--- a/src/reducers/editor.js
+++ b/src/reducers/editor.js
@@ -1,8 +1,3 @@
-import {
- UPDATE_TEXT,
- REPLACE_TEXT,
-} from 'actions/EditorActions';
-
const initialState = {
originalText: null,
text: null,
@@ -21,24 +16,38 @@ const parseHeader = (text) => {
const editor = (state = initialState, action) => {
switch (action.type) {
- case UPDATE_TEXT: {
- const title = parseHeader(action.text);
+ case 'EDITOR_RESET': {
+ return {
+ ...initialState,
+ }
+ }
+ case 'EDITOR_UPDATE_TITLE': {
+ return {
+ ...state,
+ title: action.payload,
+ }
+ }
+ case 'EDITOR_UPDATE_TEXT': {
+ const title = parseHeader(action.payload);
console.log(title);
let unsavedChanges = false;
- if (state.originalText !== action.text) {
+ if (state.originalText !== action.payload) {
unsavedChanges = true;
}
return {
...state,
unsavedChanges,
- text: action.text,
+ text: action.payload,
title: title || state.title,
};
}
- case REPLACE_TEXT: {
- const newText = state.text.replace(action.originalText, action.replacedText);
+ case 'EDITOR_REPLACE_TEXT': {
+ const newText = state.text.replace(
+ action.payload.original,
+ action.payload.new
+ );
return {
...state,
diff --git a/src/scenes/Atlas/Atlas.js b/src/scenes/Atlas/Atlas.js
index 2c174c660..307228317 100644
--- a/src/scenes/Atlas/Atlas.js
+++ b/src/scenes/Atlas/Atlas.js
@@ -34,7 +34,7 @@ class Atlas extends React.Component {
let actions;
let title;
- if (this.props.isLoading === false) {
+ if (!this.props.isLoading) {
actions = New document;
title =
{ atlas.name };
}
diff --git a/src/scenes/DocumentEdit/DocumentEdit.js b/src/scenes/DocumentEdit/DocumentEdit.js
new file mode 100644
index 000000000..5f0016ce8
--- /dev/null
+++ b/src/scenes/DocumentEdit/DocumentEdit.js
@@ -0,0 +1,134 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import {
+ resetEditor,
+ updateText,
+ updateTitle,
+ replaceText,
+} from 'actions/EditorActions';
+import {
+ resetDocument,
+ fetchDocumentAsync,
+ saveDocumentAsync,
+} from 'actions/DocumentActions';
+
+import Layout, { Title } from 'components/Layout';
+import Flex from 'components/Flex';
+import MarkdownEditor from 'components/MarkdownEditor';
+import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
+import CenteredContent from 'components/CenteredContent';
+
+import SaveAction from './components/SaveAction';
+
+class DocumentEdit extends Component {
+ static propTypes = {
+ updateText: React.PropTypes.func.isRequired,
+ updateTitle: React.PropTypes.func.isRequired,
+ replaceText: React.PropTypes.func.isRequired,
+ resetDocument: React.PropTypes.func.isRequired,
+ saveDocumentAsync: React.PropTypes.func.isRequired,
+ text: React.PropTypes.string,
+ title: React.PropTypes.string,
+ }
+
+ state = {
+ loadingDocument: false,
+ }
+
+ componentWillMount = () => {
+ this.props.resetEditor();
+ this.props.resetDocument();
+ }
+
+ componentDidMount = () => {
+ const id = this.props.routeParams.id;
+ this.props.fetchDocumentAsync(id);
+ }
+
+ componentWillReceiveProps = (nextProps) => {
+ if (!this.props.document && nextProps.document) {
+ const doc = nextProps.document;
+ this.props.updateText(doc.text);
+ this.props.updateTitle(doc.title);
+ }
+ }
+
+ onSave = () => {
+ if (this.props.title.length === 0) {
+ alert("Please add a title before saving (hint: Write a markdown header)");
+ return
+ }
+
+ this.props.saveDocumentAsync(
+ null,
+ this.props.document.id,
+ this.props.title,
+ this.props.text,
+ )
+ }
+
+ render() {
+ let title = (
+
+ { this.props.title }
+
+ );
+
+ return (
+
+
+
+ )}
+ title={ title }
+ fixed={ true }
+ >
+ { (this.props.isLoading && !this.props.document) ? (
+
+
+
+ ) : (
+
+ ) }
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ document: state.document.data,
+ text: state.editor.text,
+ title: state.editor.title,
+ isLoading: state.document.isLoading,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return bindActionCreators({
+ resetEditor,
+ updateText,
+ updateTitle,
+ replaceText,
+ resetDocument,
+ fetchDocumentAsync,
+ saveDocumentAsync,
+ }, dispatch)
+};
+
+DocumentEdit = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(DocumentEdit);
+
+export default DocumentEdit;
diff --git a/src/scenes/DocumentEdit/components/SaveAction.js b/src/scenes/DocumentEdit/components/SaveAction.js
new file mode 100644
index 000000000..cba36ddbc
--- /dev/null
+++ b/src/scenes/DocumentEdit/components/SaveAction.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import { Arrow } from 'rebass';
+
+class SaveAction extends React.Component {
+ propTypes = {
+ onClick: React.PropTypes.func.isRequired,
+ }
+
+ onClick = (event) => {
+ event.preventDefault();
+ this.props.onClick();
+ }
+
+ render() {
+ return (
+
+ );
+ }
+};
+
+export default SaveAction;
\ No newline at end of file
diff --git a/src/scenes/DocumentEdit/index.js b/src/scenes/DocumentEdit/index.js
new file mode 100644
index 000000000..548ef8a56
--- /dev/null
+++ b/src/scenes/DocumentEdit/index.js
@@ -0,0 +1,2 @@
+import DocumentEdit from './DocumentEdit';
+export default DocumentEdit;
diff --git a/src/scenes/DocumentScene/DocumentScene.js b/src/scenes/DocumentScene/DocumentScene.js
index 8e156f4a3..4c4a16584 100644
--- a/src/scenes/DocumentScene/DocumentScene.js
+++ b/src/scenes/DocumentScene/DocumentScene.js
@@ -1,5 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
+import Link from 'react-router/lib/Link';
import { bindActionCreators } from 'redux';
import { fetchDocumentAsync } from 'actions/DocumentActions';
@@ -19,13 +20,16 @@ class DocumentScene extends React.Component {
render() {
const document = this.props.document;
let title;
+ let actions;
if (document) {
+ actions = Edit;
title = `${document.atlas.name} - ${document.title}`;
}
return (
{ this.props.isLoading || !document ? (
diff --git a/src/scenes/Editor/Editor.js b/src/scenes/Editor/Editor.js
index 591a1d5e7..d7d8fb548 100644
--- a/src/scenes/Editor/Editor.js
+++ b/src/scenes/Editor/Editor.js
@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
+ resetEditor,
updateText,
replaceText,
} from 'actions/EditorActions';
@@ -10,12 +11,11 @@ import {
saveDocumentAsync,
} from 'actions/DocumentActions';
-import styles from './Editor.scss';
-import 'assets/styles/codemirror.css';
-
import Layout, { Title } from 'components/Layout';
import Flex from 'components/Flex';
import MarkdownEditor from 'components/MarkdownEditor';
+import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
+import CenteredContent from 'components/CenteredContent';
import SaveAction from './components/SaveAction';
import MoreAction from './components/MoreAction';
@@ -31,7 +31,9 @@ class Editor extends Component {
componentDidMount = () => {
const atlasId = this.props.routeParams.id;
- this.setState({ atlasId: atlasId });
+ this.setState({
+ atlasId: atlasId,
+ });
}
onSave = () => {
@@ -88,6 +90,7 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
+ resetEditor,
updateText,
replaceText,
saveDocumentAsync,
diff --git a/src/scenes/Editor/Editor.scss b/src/scenes/Editor/Editor.scss
deleted file mode 100644
index ba81774a1..000000000
--- a/src/scenes/Editor/Editor.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.container {
- display: flex;
- flex-flow: column;
- width: 100%;
- height: 100%;
-
- background-color: #fff;
- font-family: -apple-system, "Helvetica Neue", "Lucida Grande";
- color: #222;
-}
-
-.content {
-}
\ No newline at end of file