Skip to content
This repository has been archived by the owner on Feb 13, 2020. It is now read-only.

Commit

Permalink
Implemented functionality for loading documents from store
Browse files Browse the repository at this point in the history
 - Documents can be loaded from the store and displayed in an area under the searcher and editor
 - Improved query functionality so that multi-word queries now work
 - General fixes and improvements to the message process including adding callback for when messages are saved that displays a message
 - Cleaned up the document store code by making some duplicate code into a function
 - Misc. changes and fixes
 - Assigned each document a UUID that is used to save them
 - Saved user-created documents to a persistant JSON file as well as to the Tantivy store
 - Updated frontend to match the new UUID field and added disabled buttons for editing/deleting documents
  • Loading branch information
Ameobea committed Feb 24, 2017
1 parent 8462d42 commit d47910a
Show file tree
Hide file tree
Showing 19 changed files with 464 additions and 200 deletions.
2 changes: 2 additions & 0 deletions data/documents/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
tantivy_index/
user_documents/

Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"body": "# Welcome to TickGrinder\nTickGrinder is a platform.",
"tags": ["intro", "welcome", "start"],
"creation_date": "Sat Feb 18 19:37:52 CST 2017",
"modification_date": "Sat Feb 18 19:39:52 CST 2017"
"modification_date": "Sat Feb 18 19:39:52 CST 2017",
"id": "53b34794-22c8-4cdb-97af-6b28b70111ae"
}
1 change: 1 addition & 0 deletions gource-recent.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gource --start-date '2017-01-01' -s .4
1 change: 1 addition & 0 deletions gource.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gource -s .4
1 change: 1 addition & 0 deletions mm-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dva": "^1.2.1",
"dva-loading": "^0.2.0",
"eslint-plugin-promise": "^3.4.1",
"html-to-react": "^1.2.4",
"react": "^15.4.0",
"react-dom": "^15.4.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* global CKEDITOR */

import React from 'react';
import { connect } from 'dva';

/**
* After the CKEditor plugin has loaded, initialize the editor
Expand Down Expand Up @@ -35,6 +36,9 @@ class CKEditor extends React.Component {

// wait for the CKEditor script to load and then initialize the editor
awaitCk(this.props.rand);

// register our id as the active editor instance
this.props.dispatch({type: 'documents/setEditorId', id: this.props.rand});
}

shouldComponentUpdate(...args) {
Expand All @@ -52,4 +56,4 @@ CKEditor.propTypes = {
rand: React.PropTypes.number.isRequired,
};

export default CKEditor;
export default connect()(CKEditor);
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import React from 'react';
import { connect } from 'dva';
import { Input, Button } from 'antd';

import { v4 } from '../../utils/commands';
import CKEditor from './Ckeditor';
import gstyles from '../static/css/globalStyle.css';
import gstyles from '../../static/css/globalStyle.css';

/**
* Returns a function that gets the HTML content from the inner CKEditor instance and saves it to the document store
Expand All @@ -15,10 +16,11 @@ const saveDocument = (dispatch, rand) => {
return () => {
let content = CKEDITOR.instances['ckeditor-' + rand].getData();
dispatch({
type: 'platform_communication/saveDocument',
type: 'documents/saveDocument',
title: document.getElementById('ck-title-' + rand).value,
tags: document.getElementById('ck-tags-' + rand).value.split(" "),
body: content,
id: v4(), // generate a random id for this document
});
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,21 @@ const returnTrue = () => true;
class DocSearcher extends React.Component {
componentWillMount() {
// register outself as a recipient of query results and re-draw ourself when they're received
this.props.dispatch({type: 'platform_communication/registerDocQueryReceiver', cb: (results) => {
this.props.dispatch({type: 'documents/registerDocQueryReceiver', cb: (results) => {
// simulate a click on the input box to force the component to update
document.getElementById('autocompleteInput').click();
}});
}

handleDocInputSelect(dispatch) {
return (value, option) => {
console.log(value);
console.log(option);
// TODO
dispatch({type: 'documents/requestDocument', title: value});
};
}

handleDocInputChange(dispatch) {
return (value, label) => {
dispatch({type: 'platform_communication/sendDocQuery', query: value});
dispatch({type: 'documents/sendDocQuery', query: value});
};
}

Expand Down Expand Up @@ -56,7 +54,7 @@ DocSearcher.propTypes = {

function mapProps(state) {
return {
queryResults: state.platform_communication.queryResults,
queryResults: state.documents.queryResults,
};
}

Expand Down
40 changes: 40 additions & 0 deletions mm-react/src/components/docs/DocViewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Renders the selected document that is returned from the document store.

import React from 'react';
import { connect } from 'dva';
import { Button } from 'antd';
var HtmlToReactParser = require('html-to-react').Parser;

import { DocumentShape } from '../../utils/commands';
import gstyles from '../../static/css/globalStyle.css';

const DocViewer = ({dispatch, selectedDoc, editDocument}) => {
let {title, body, tags} = selectedDoc;
const htmlToReactParser = new HtmlToReactParser();
const RenderedBody = htmlToReactParser.parse('<div>' + body + '</div>');

return (
<div className='docViewer' className={gstyles.leftText}>
<br />
<h1 className={gstyles.inlineH1} >{title}</h1>
<Button disabled onClick={editDocument(dispatch)} type='primary'>Edit</Button>
<Button type='danger' disabled>Delete</Button>
<hr />
{RenderedBody}
</div>
);
};

DocViewer.propTypes = {
dispatch: React.PropTypes.func.isRequired,
selectedDoc: React.PropTypes.shape(DocumentShape).isRequired,
editDocument: React.PropTypes.func.isRequired,
};

function mapProps(state) {
return {
selectedDoc: state.documents.returnedDoc,
};
}

export default connect(mapProps)(DocViewer);
5 changes: 3 additions & 2 deletions mm-react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ let PlatformCommunication = require('./models/PlatformCommunication');
let Logging = require('./models/Logging');
let InstanceManagement = require('./models/InstanceManagement');
let Macros = require('./models/macros');
let Documents = require('./models/Documents');
// if I remove this line the compilation fails, so here it remains

// 1. Initialize
const app = dva();
Expand All @@ -18,16 +20,15 @@ const app = dva();
// app.use({});

// 3. Model
// app.model(require('./models/example'));
app.model(GlobalState);
app.model(PlatformCommunication);
app.model(Logging);
app.model(InstanceManagement);
app.model(Macros);
app.model(Documents);

// 4. Router
app.router(require('./router'));
// app.router(() => <App />)

// 5. Start
const App = app.start();
Expand Down
147 changes: 147 additions & 0 deletions mm-react/src/models/Documents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//! Functions for interacting with the document storage engine.

import { message } from 'antd';

const d = new Date();

export default {
namespace: 'documents',

state: {
queryResults: [], // list of all document titles returned in response to a query
docQueryCbs: [], // list of functions that are called with the list of matched titles every time a query response is received
returnedDoc: { // the document returned from a query for a document
title: 'Placeholder Document',
body: 'Select a document to display by using the search box above. When you select one, it will be displayed here.',
tags: [],
creation_date: '' + d.getTime(),
modification_date: '' + d.getTime(),
id: "00000000-0000-0000-0000-000000000000",
},
activeEditorId: 0, // the random numeric id of the active document CKEditor instance
},

reducers: {
/**
* Registers a callback to be executed every time a new `DocQueryResponse` is received.
*/
registerDocQueryReceiver (state, {cb}) {
return {...state,
docQueryCbs: [...state.docQueryCbs, cb]
};
},

/**
* Sets the ID of the active editor instance
*/
setEditorId (state, {id}) {
return {...state,
activeEditorId: id,
};
},

/**
* Called when a doc query response is received from the spawner. Updates the stored list of matched titles and
* executes all stored callbacks.
*/
docQueryResponseReceived (state, {msg}) {
let matchedDocs;
if(msg.res.DocumentQueryResult) {
matchedDocs = msg.res.DocumentQueryResult.results.map(o => JSON.parse(o).title);

// execute all registered callbacks
for(var i=0; i<state.docQueryCbs.length; i++) {
state.docQueryCbs[i](matchedDocs);
}

return {...state,
queryResults: matchedDocs,
};
} else if(msg.res.Error) {
message.error('Error while processing query: ' + msg.res.Error.status);
} else {
message.error('Unknown error occured while processing query: ' + JSON.stringify(msg));
}

return {...state};
},

/**
* Called when a response is received from a request to save a document.
*/
documentStoreResponseReceived (state, {msg}) {
if(msg.res == 'Ok') {
message.success('Document successfully saved.');
} else if(msg.res.Error) {
message.error('Error saving document: ' + msg.res.Error.status);
} else {
message.error('Unhandled error occured while tring to save document: ' + JSON.stringify(msg));
}

return {...state};
},

/**
* Called when the response of a request for a document is received.
*/
documentRequestResultReceived (state, {msg}) {
if(msg.res.Document) {
return {...state,
returnedDoc: msg.res.Document.doc,
};
} else if(msg.res.Error) {
message.error('Error while fetching document from store: ' + msg.res.Error.status);
} else {
message.error('Unexpected response received while fetching document from store: ' + JSON.stringify(msg));
}

return {...state};
}
},

effects: {
/**
* Sends a document query to the Tantivy-backed document store. Registers interest in responses with the UUID
* of the sent query and handles the responses by updating the `queryResults` state.
*/
*sendDocQuery ({query}, {call, put}) {
let cmd = {QueryDocumentStore: {query: query}};
yield put({
type: 'platform_communication/sendCommandToInstance',
cb_action: 'documents/docQueryResponseReceived',
cmd: cmd,
instance_name: 'Spawner',
});
},

/**
* Given the content of a CKEditor document, saves it in the document store
*/
*saveDocument ({title, tags, body, id}, {call, put}) {
let d = new Date();
let doc = {title: title, tags: tags, body: body, id: id, creation_date: d.getTime() + '', modification_date: d.getTime() + ''};
let cmd = {InsertIntoDocumentStore: {doc: JSON.stringify(doc)}};

yield put({
type: 'platform_communication/sendCommandToInstance',
cb_action: 'documents/documentStoreResponseReceived',
cmd: cmd,
instance_name: 'Spawner',
});
},

/**
* Asks the document store to return the document with a certain title.
*/
*requestDocument ({title}, {call, put}) {
let cmd = {GetDocument: {title: title}};

yield put({
type: 'platform_communication/sendCommandToInstance',
cb_action: 'documents/documentRequestResultReceived',
cmd: cmd,
instance_name: 'Spawner',
});
}
},
};
Loading

0 comments on commit d47910a

Please sign in to comment.