diff --git a/app.js b/app.js
index fe392932b..15a7a5442 100644
--- a/app.js
+++ b/app.js
@@ -3,10 +3,10 @@ const bodyParser = require('body-parser');
const morgan = require('morgan');
const path = require('path');
const helmet = require('helmet');
-const passport = require('./passport');
+const passport = require('./services/passport');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
-const redis = require('./redis');
+const redis = require('./services/redis');
const app = express();
diff --git a/bin/cli-assets b/bin/cli-assets
index 9ab36685a..4b0d65c2a 100755
--- a/bin/cli-assets
+++ b/bin/cli-assets
@@ -15,7 +15,7 @@ const pkg = require('../package.json');
const parseDuration = require('parse-duration');
const Table = require('cli-table');
const Asset = require('../models/asset');
-const mongoose = require('../mongoose');
+const mongoose = require('../services/mongoose');
const scraper = require('../services/scraper');
const util = require('../util');
diff --git a/bin/cli-jobs b/bin/cli-jobs
index bc320d14b..91a0a6e59 100755
--- a/bin/cli-jobs
+++ b/bin/cli-jobs
@@ -13,8 +13,8 @@ process.env.DEBUG = process.env.TALK_DEBUG;
const program = require('commander');
const scraper = require('../services/scraper');
const util = require('../util');
-const mongoose = require('../mongoose');
-const kue = require('../kue');
+const mongoose = require('../services/mongoose');
+const kue = require('../services/kue');
util.onshutdown([
() => mongoose.disconnect()
diff --git a/bin/cli-serve b/bin/cli-serve
index dd682f5ad..c22920eaa 100755
--- a/bin/cli-serve
+++ b/bin/cli-serve
@@ -11,7 +11,7 @@ const debug = require('debug')('talk:server');
const http = require('http');
const init = require('../init');
const scraper = require('../services/scraper');
-const mongoose = require('../mongoose');
+const mongoose = require('../services/mongoose');
const util = require('../util');
/**
diff --git a/bin/cli-settings b/bin/cli-settings
index e7ba30151..e38249199 100755
--- a/bin/cli-settings
+++ b/bin/cli-settings
@@ -11,7 +11,7 @@ process.env.DEBUG = process.env.TALK_DEBUG;
*/
const program = require('commander');
-const mongoose = require('../mongoose');
+const mongoose = require('../services/mongoose');
const Setting = require('../models/setting');
const util = require('../util');
diff --git a/bin/cli-users b/bin/cli-users
index adfe3bf23..e212a477f 100755
--- a/bin/cli-users
+++ b/bin/cli-users
@@ -14,7 +14,7 @@ const program = require('commander');
const pkg = require('../package.json');
const prompt = require('prompt');
const User = require('../models/user');
-const mongoose = require('../mongoose');
+const mongoose = require('../services/mongoose');
const util = require('../util');
const Table = require('cli-table');
diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js
index 71106e1f7..1dfda51b9 100644
--- a/client/coral-admin/src/actions/settings.js
+++ b/client/coral-admin/src/actions/settings.js
@@ -26,7 +26,10 @@ export const updateSettings = settings => {
};
export const saveSettingsToServer = () => (dispatch, getState) => {
- const settings = getState().settings.toJS().settings;
+ let settings = getState().settings.toJS().settings;
+ if (settings.charCount) {
+ settings.charCount = parseInt(settings.charCount);
+ }
dispatch({type: SAVE_SETTINGS_LOADING});
coralApi('/settings', {method: 'PUT', body: settings})
.then(() => {
diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js
index 380a001c8..4a21b0846 100644
--- a/client/coral-admin/src/components/Comment.js
+++ b/client/coral-admin/src/components/Comment.js
@@ -14,18 +14,18 @@ const linkify = new Linkify();
// Render a single comment for the list
export default props => {
- const authorStatus = props.author.get('status');
const {comment, author} = props;
- const links = linkify.getMatches(comment.get('body'));
+ let authorStatus = author.status;
+ const links = linkify.getMatches(comment.body);
return (
person
-
{author.get('displayName') || lang.t('comment.anon')}
-
{timeago().format(comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
- {comment.get('flagged') ?
{lang.t('comment.flagged')}
: null}
+
{author.displayName || lang.t('comment.anon')}
+
{timeago().format(comment.createdAt || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
+ {comment.flagged ?
{lang.t('comment.flagged')}
: null}
{links ?
@@ -42,7 +42,7 @@ export default props => {
- {comment.get('body')}
+ {comment.body}
@@ -52,9 +52,10 @@ export default props => {
// Get the button of the action performed over a comment if any
const getActionButton = (action, i, props) => {
- const status = props.comment.get('status');
- const flagged = props.comment.get('flagged');
- const banned = (props.author.get('status') === 'banned');
+ const {comment, author} = props;
+ const status = comment.status;
+ const flagged = comment.flagged;
+ const banned = (author.status === 'banned');
if (action === 'flag' && (status || flagged === true)) {
return null;
@@ -64,17 +65,19 @@ const getActionButton = (action, i, props) => {
);
}
return (
-
props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
+ onClick={() => props.onClickAction(props.actionsMap[action].status, comment.id)}
/>
);
};
diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js
index b4547335e..8c6655bb5 100644
--- a/client/coral-admin/src/components/CommentList.js
+++ b/client/coral-admin/src/components/CommentList.js
@@ -37,7 +37,7 @@ export default class CommentList extends React.Component {
// If entering to singleview and no active, active is the first eleement
componentWillReceiveProps (nextProps) {
if (nextProps.singleView && !this.state.active) {
- this.setState({active: nextProps.commentIds.get(0)});
+ this.setState({active: nextProps.commentIds[0]});
}
}
@@ -81,12 +81,12 @@ export default class CommentList extends React.Component {
const {commentIds} = this.props;
const {active} = this.state;
// check boundaries
- if (active === null || !commentIds.size) {
- this.setState({active: commentIds.get(0)});
- } else if (direction === 'up' && active !== commentIds.first()) {
- this.setState({active: commentIds.get(commentIds.indexOf(active) - 1)});
- } else if (direction === 'down' && active !== commentIds.last()) {
- this.setState({active: commentIds.get(commentIds.indexOf(active) + 1)});
+ if (active === null || !commentIds.length) {
+ this.setState({active: commentIds[0]});
+ } else if (direction === 'up' && active !== commentIds[0]) {
+ this.setState({active: commentIds[commentIds.indexOf(active) - 1]});
+ } else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) {
+ this.setState({active: commentIds[commentIds.indexOf(active) + 1]});
}
// scroll to the position
@@ -105,10 +105,10 @@ export default class CommentList extends React.Component {
// activate the next comment
if (id === this.state.active) {
const {commentIds} = this.props;
- if (commentIds.last() === this.state.active) {
- this.setState({active: commentIds.get(commentIds.size - 2)});
+ if (commentIds[commentIds.length - 1] === this.state.active) {
+ this.setState({active: commentIds[commentIds.length - 2]});
} else {
- this.setState({active: commentIds.get(Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.size - 1))});
+ this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]});
}
}
this.props.onClickAction(action, id, author_id);
@@ -125,10 +125,10 @@ export default class CommentList extends React.Component {
return (
{commentIds.map((commentId, index) => {
- const comment = comments.get(commentId);
+ const comment = comments[commentId];
+ const author = users[comment.author_id];
return { if (el && commentId === active) { this._active = el; } }}
+ author={author}
key={index}
index={index}
onClickAction={this.onClickAction}
@@ -137,7 +137,7 @@ export default class CommentList extends React.Component {
actionsMap={actions}
isActive={commentId === active}
hideActive={hideActive} />;
- }).toArray()}
+ })}
);
}
diff --git a/client/coral-admin/src/containers/CommentStream/CommentStream.js b/client/coral-admin/src/containers/CommentStream/CommentStream.js
index 113a7bc86..556e50463 100644
--- a/client/coral-admin/src/containers/CommentStream/CommentStream.js
+++ b/client/coral-admin/src/containers/CommentStream/CommentStream.js
@@ -46,9 +46,9 @@ class CommentStream extends React.Component {
@@ -58,4 +58,9 @@ class CommentStream extends React.Component {
}
}
-export default connect(({comments, users}) => ({comments, users}))(CommentStream);
+const mapStateToProps = state => ({
+ comments: state.comments.toJS(),
+ users: state.users.toJS()
+});
+
+export default connect(mapStateToProps)(CommentStream);
diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js
index 194af720c..3a6a796e4 100644
--- a/client/coral-admin/src/containers/Configure/CommentSettings.js
+++ b/client/coral-admin/src/containers/Configure/CommentSettings.js
@@ -8,9 +8,25 @@ import {
ListItemContent,
ListItemAction,
Textfield,
- Checkbox
+ Checkbox,
+ Icon
} from 'react-mdl';
+const updateCharCountEnable = (updateSettings, charCountChecked) => () => {
+ const charCountEnable = !charCountChecked;
+ updateSettings({charCountEnable});
+};
+
+const updateCharCount = (updateSettings, settingsError) => (event) => {
+ const charCount = event.target.value;
+ if (charCount.match(/[^0-9]/) || charCount.length === 0) {
+ settingsError('charCount', true);
+ } else {
+ settingsError('charCount', false);
+ }
+ updateSettings({charCount: charCount});
+};
+
const updateModeration = (updateSettings, mod) => () => {
const moderation = mod === 'pre' ? 'post' : 'pre';
updateSettings({moderation});
@@ -31,48 +47,80 @@ const updateClosedMessage = (updateSettings) => (event) => {
updateSettings({closedMessage});
};
-const CommentSettings = (props) =>
-
-
-
-
- {lang.t('configure.enable-pre-moderation')}
-
-
-
-
-
-
- {lang.t('configure.include-comment-stream')}
-
- {lang.t('configure.include-comment-stream-desc')}
+const CommentSettings = ({updateSettings, settingsError, settings, errors}) =>
+
+
+
+
+
+ {lang.t('configure.enable-pre-moderation')}
+
+ {lang.t('configure.enable-pre-moderation-text')}
-
-
-
-
-
-
-
-
- {lang.t('configure.closed-comments-desc')}
-
-
-
-
;
+
+
+
+
+
+
+ {lang.t('configure.comment-count-header')}
+
+ {lang.t('configure.comment-count-text-pre')}
+
+ {lang.t('configure.comment-count-text-post')}
+ {
+ errors.charCount &&
+
+
+
+ {lang.t('configure.comment-count-error')}
+
+ }
+
+
+
+
+
+
+
+
+ {lang.t('configure.include-comment-stream')}
+
+ {lang.t('configure.include-comment-stream-desc')}
+
+
+
+
+
+
+
+
+
+
+ {lang.t('configure.closed-comments-desc')}
+
+
+
+
;
export default CommentSettings;
diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css
index 388813a64..667bb4744 100644
--- a/client/coral-admin/src/containers/Configure/Configure.css
+++ b/client/coral-admin/src/containers/Configure/Configure.css
@@ -18,9 +18,27 @@
.configSetting {
border: 1px solid #ccc;
border-radius: 4px;
- height: 90px;
+ height: 95px;
margin-bottom: 10px;
- cursor: pointer;
+ align-items: flex-start;
+}
+
+.settingsError {
+ color: #d50000;
+}
+
+.settingsError i {
+ font-size: 14px;
+ margin-right: 3px;
+}
+
+.settingsHeader {
+ margin-top: 3px;
+ margin-bottom: 10px;
+}
+
+.disabledSettingText {
+ color: #ccc;
}
.configSettingInfoBox {
@@ -45,6 +63,22 @@
display: block;
}
+.charCountTexfield {
+ width: 4em;
+ padding: 0px;
+ border-color: #ccc;
+ border-style: solid;
+ border-width: 0px 0px 1px 0px;
+}
+
+.charCountTexfieldEnabled {
+ border-color: #4caf50;
+}
+
+.charCountTexfield:focus {
+ outline: none;
+}
+
.changedSave {
background-color:#4caf50;
}
@@ -84,7 +118,15 @@
margin-bottom:3px;
}
+.enabledSetting {
+ border-left-color: #4caf50;
+ border-left-style: solid;
+ border-left-width: 7px;
+}
+.disabledSetting {
+ padding-left: 22px;
+}
.hidden {
display: none;
diff --git a/client/coral-admin/src/containers/Configure/Configure.js b/client/coral-admin/src/containers/Configure/Configure.js
index db3bf6f20..8dd106cd0 100644
--- a/client/coral-admin/src/containers/Configure/Configure.js
+++ b/client/coral-admin/src/containers/Configure/Configure.js
@@ -22,7 +22,8 @@ class Configure extends React.Component {
this.state = {
activeSection: 'comments',
wordlist: [],
- changed: false
+ changed: false,
+ errors: {}
};
}
@@ -64,12 +65,23 @@ class Configure extends React.Component {
this.props.dispatch(updateSettings(setting));
}
+ // Sets an arbitrary error string and a boolean state.
+ // This allows the system to track multiple errors.
+ onSettingError = (error, state) => {
+ this.setState((prevState) => {
+ prevState.errors[error] = state;
+ return prevState;
+ });
+ }
+
getSection = (section) => {
switch(section){
case 'comments':
return ;
+ updateSettings={this.onSettingUpdate}
+ errors={this.state.errors}
+ settingsError={this.onSettingError}/>;
case 'embed':
return ;
case 'wordlist':
@@ -94,6 +106,9 @@ class Configure extends React.Component {
let pageTitle = this.getPageTitle(this.state.activeSection);
const section = this.getSection(this.state.activeSection);
+ const showSave = Object.keys(this.state.errors).reduce(
+ (bool, error) => this.state.errors[error] ? false : bool, this.state.changed);
+
if (this.props.fetchingSettings) {
pageTitle += ' - Loading...';
}
@@ -119,7 +134,7 @@ class Configure extends React.Component {
{
- this.state.changed ?
+ showSave ?