@@ -94,41 +98,26 @@ class ModerationQueue extends React.Component {
- comments
- .get('byId')
- .get(id)
- .get('status') === 'premod')
- }
- comments={comments.get('byId')}
- users={users.get('byId')}
+ commentIds={premodIds}
+ comments={comments.byId}
+ users={users.byId}
onClickAction={(action, commentId) => this.onCommentAction(action, commentId)}
onClickShowBanDialog={(userId, userName, commentId) => this.showBanUserDialog(userId, userName, commentId)}
actions={['reject', 'approve', 'ban']}
loading={comments.loading} />
this.hideBanUserDialog()}
onClickBanUser={(userId, commentId) => this.banUser(userId, commentId)}
- user={comments.get('banUser')}/>
+ user={comments.banUser}/>
- comments
- .get('byId')
- .get(id)
- .get('status') === 'rejected')
- }
- comments={comments.get('byId')}
- users={users.get('byId')}
+ commentIds={rejectedIds}
+ comments={comments.byId}
+ users={users.byId}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['approve']}
loading={comments.loading} />
@@ -137,12 +126,9 @@ class ModerationQueue extends React.Component {
{
- const data = comments.get('byId').get(id);
- return !data.get('status') && data.get('flagged') === true;
- })}
- comments={comments.get('byId')}
- users={users.get('byId')}
+ commentIds={flaggedIds}
+ comments={comments.byId}
+ users={users.byId}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['reject', 'approve']}
loading={comments.loading} />
@@ -155,6 +141,11 @@ class ModerationQueue extends React.Component {
}
}
-export default connect(({comments, users}) => ({comments, users}))(ModerationQueue);
+const mapStateToProps = state => ({
+ comments: state.comments.toJS(),
+ users: state.users.toJS()
+});
+
+export default connect(mapStateToProps)(ModerationQueue);
const lang = new I18n(translations);
diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json
index 013c159a8..e462f7567 100644
--- a/client/coral-admin/src/translations.json
+++ b/client/coral-admin/src/translations.json
@@ -41,6 +41,7 @@
},
"configure": {
"enable-pre-moderation": "Enable pre-moderation",
+ "enable-pre-moderation-text": "Moderators must approve any comment before it is published.",
"include-comment-stream": "Include Comment Stream Description for Readers.",
"include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.",
"include-text": "Include your text here.",
@@ -55,7 +56,11 @@
"configure": "Configure",
"community": "Community",
"closed-comments-desc": "Write a message for closed threads",
- "closed-comments-label": "Write a message..."
+ "closed-comments-label": "Write a message...",
+ "comment-count-header": "Limit Comment Length",
+ "comment-count-text-pre": "Comments will be limited to ",
+ "comment-count-text-post": " characters.",
+ "comment-count-error": "Please enter a valid number."
},
"bandialog": {
"ban_user": "Ban User?",
@@ -96,6 +101,7 @@
},
"configure": {
"enable-pre-moderation": "Habilitar pre-moderación",
+ "enable-pre-moderation-text": "Los moderadores deben aprobar cada comentario antes de que sea publicado.",
"include-comment-stream": "Incluir la Descripción a un Hilo de Comentario para los y las Lectoras.",
"include-comment-stream-desc": "Escribir un mensaje que será agregado a la parte de arriba del tu hilo de comentarios. Por ejemplo, un tema, guias de comunidad, etc.",
"include-text": "Incluir tu texto aqui.",
@@ -110,7 +116,11 @@
"configure": "Configurar",
"community": "Comunidad",
"closed-comments-desc": "Escribe un mensaje para cuando los comentarios se encuentran cerrados",
- "closed-comments-label": "Escribe un mensaje..."
+ "closed-comments-label": "Escribe un mensaje...",
+ "comment-count-header": "Limitar el largo del comentario",
+ "comment-count-text-pre": "El largo de comentarios será ",
+ "comment-count-text-post": " caracteres",
+ "comment-count-error": "Por favor escribe un número válido."
},
"bandialog": {
"ban_user": "Quieres suspender el Usuario?",
diff --git a/client/coral-configure/translations.json b/client/coral-configure/translations.json
index e597c9c9a..5aa7f72b6 100644
--- a/client/coral-configure/translations.json
+++ b/client/coral-configure/translations.json
@@ -3,7 +3,7 @@
"configureCommentStream": {
"apply": "Apply",
"title": "Configure Comment Stream",
- "description": "As an admin you may customize the settings for the comment stream for this article",
+ "description": "As an admin you may customize the settings for the comment stream for this asset",
"enablePremod": "Enable Premoderation",
"enablePremodDescription": "Moderators must approve any comment before its published.",
"enablePremodLinks": "Pre-Moderate Comments Containing Links",
diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js
index b53832ce5..c052e5aa1 100644
--- a/client/coral-embed-stream/src/CommentStream.js
+++ b/client/coral-embed-stream/src/CommentStream.js
@@ -90,7 +90,7 @@ class CommentStream extends Component {
const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0];
const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
const {actions, users, comments} = this.props.items;
- const {status, moderation, closedMessage} = this.props.config;
+ const {status, moderation, closedMessage, charCount, charCountEnable} = this.props.config;
const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth;
const {activeTab} = this.state;
const banned = (this.props.userData.status === 'banned');
@@ -128,7 +128,7 @@ class CommentStream extends Component {
currentUser={this.props.auth.user}
banned={banned}
author={user}
- />
+ charCount={charCountEnable && charCount}/>
:
{closedMessage}
@@ -198,6 +198,7 @@ class CommentStream extends Component {
parent_id={commentId}
premod={moderation}
currentUser={user}
+ charCount={charCountEnable && charCount}
showReply={comment.showReply}/>
{
comment.children &&
@@ -257,6 +258,7 @@ class CommentStream extends Component {
premod={moderation}
banned={banned}
currentUser={user}
+ charCount={charCountEnable && charCount}
showReply={reply.showReply}/>
;
})
diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css
index c27baf406..463c8dc68 100644
--- a/client/coral-embed-stream/style/default.css
+++ b/client/coral-embed-stream/style/default.css
@@ -95,7 +95,7 @@ hr {
margin-top: 10px;
}
-#coralStream .coral-plugin-commentbox-button {
+.coral-plugin-commentbox-button {
float: right;
margin-top: 10px;
padding: 5px 10px;
@@ -111,6 +111,16 @@ hr {
margin-bottom: 5px;
}
+.coral-plugin-commentbox-char-count {
+ color: #ccc;
+ text-align: right;
+ font-size: 12px;
+}
+
+.coral-plugin-commentbox-char-max {
+ color: #d50000;
+}
+
/* Comment styles */
.comment {
margin-bottom: 10px;
diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js
index f85e609eb..77e7f5af5 100644
--- a/client/coral-framework/actions/config.js
+++ b/client/coral-framework/actions/config.js
@@ -6,14 +6,6 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from './../translations';
const lang = new I18n(translations);
-export const updateOpenStatus = status => (dispatch, getState) => {
- const assetId = getState().items.get('assets')
- .keySeq()
- .toArray()[0];
- return coralApi(`/asset/${assetId}/status?status=${status}`, {method: 'PUT'})
- .then(() => dispatch({type: status === 'open' ? actions.OPEN_COMMENTS : actions.CLOSE_COMMENTS}));
-};
-
const updateConfigRequest = () => ({type: actions.UPDATE_CONFIG_REQUEST});
const updateConfigSuccess = config => ({type: actions.UPDATE_CONFIG_SUCCESS, config});
const updateConfigFailure = () => ({type: actions.UPDATE_CONFIG_FAILURE});
@@ -24,10 +16,38 @@ export const updateConfiguration = newConfig => (dispatch, getState) => {
.toArray()[0];
dispatch(updateConfigRequest());
- coralApi(`/asset/${assetId}/settings`, {method: 'PUT', body: newConfig})
+ coralApi(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
.then(() => {
dispatch(addNotification('success', lang.t('successUpdateSettings')));
dispatch(updateConfigSuccess(newConfig));
})
.catch(error => dispatch(updateConfigFailure(error)));
};
+
+export const updateOpenStream = closedBody => (dispatch, getState) => {
+ const assetId = getState().items.get('assets')
+ .keySeq()
+ .toArray()[0];
+
+ dispatch(updateConfigRequest());
+
+ coralApi(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
+ .then(() => {
+ dispatch(addNotification('success', lang.t('successUpdateSettings')));
+ dispatch(updateConfigSuccess(closedBody));
+ })
+ .catch(error => dispatch(updateConfigFailure(error)));
+};
+
+const openStream = () => ({type: actions.OPEN_COMMENTS});
+const closeStream = () => ({type: actions.CLOSE_COMMENTS});
+
+export const updateOpenStatus = status => dispatch => {
+ if (status === 'open') {
+ dispatch(openStream());
+ dispatch(updateOpenStream({closedAt: null}));
+ } else {
+ dispatch(closeStream());
+ dispatch(updateOpenStream({closedAt: new Date().getTime()}));
+ }
+};
diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js
index 8f0e789af..cd798c6b9 100644
--- a/client/coral-framework/actions/items.js
+++ b/client/coral-framework/actions/items.js
@@ -2,6 +2,10 @@ import coralApi from '../helpers/response';
import {fromJS} from 'immutable';
import {UPDATE_CONFIG} from '../constants/config';
+/**
+* Action name constants
+*/
+
export const ADD_ITEM = 'ADD_ITEM';
export const UPDATE_ITEM = 'UPDATE_ITEM';
export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY';
diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js
index cda2d765d..69f4882bb 100644
--- a/client/coral-framework/actions/user.js
+++ b/client/coral-framework/actions/user.js
@@ -1,5 +1,7 @@
import * as actions from '../constants/user';
+import * as assetActions from '../constants/assets';
import {addNotification} from '../actions/notification';
+import {addItem} from '../actions/items';
import coralApi from '../helpers/response';
import I18n from 'coral-framework/modules/i18n/i18n';
@@ -19,3 +21,30 @@ export const saveBio = (user_id, formData) => dispatch => {
})
.catch(error => dispatch(saveBioFailure(error)));
};
+
+/**
+ *
+ * Get a list of comments by a single user
+ *
+ * @param {string} user_id
+ * @returns Promise
+ */
+export const fetchCommentsByUserId = userId => {
+ return (dispatch) => {
+ dispatch({type: actions.COMMENTS_BY_USER_REQUEST});
+ return coralApi(`/comments?user_id${userId}`)
+ .then(({comments, assets}) => {
+ comments.forEach(comment => dispatch(addItem(comment, 'comments')));
+
+ assets.forEach(asset => dispatch(addItem(asset, 'assets')));
+
+ dispatch({type: actions.COMMENTS_BY_USER_SUCCESS, comments: comments.map(comment => comment.id)});
+ dispatch({type: assetActions.MULTIPLE_ASSETS_SUCCESS, assets: assets.map(asset => asset.id)});
+ })
+ .catch(error => {
+ console.error(error.stack);
+ console.error('FAILURE_COMMENTS_BY_USER', error);
+ dispatch({type: actions.COMMENTS_BY_USER_FAILURE, error});
+ });
+ };
+};
diff --git a/client/coral-framework/constants/assets.js b/client/coral-framework/constants/assets.js
new file mode 100644
index 000000000..0224e0945
--- /dev/null
+++ b/client/coral-framework/constants/assets.js
@@ -0,0 +1,3 @@
+export const MULTIPLE_ASSETS_REQUEST = 'MULTIPLE_ASSETS_REQUEST';
+export const MULTIPLE_ASSETS_SUCCESS = 'MULTIPLE_ASSETS_SUCCESS';
+export const MULTIPLE_ASSSETS_FAILURE = 'MULTIPLE_ASSSETS_FAILURE';
\ No newline at end of file
diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js
index 0c316d48a..6e09726d3 100644
--- a/client/coral-framework/constants/user.js
+++ b/client/coral-framework/constants/user.js
@@ -1,3 +1,6 @@
export const SAVE_BIO_REQUEST = 'SAVE_BIO_REQUEST';
export const SAVE_BIO_SUCCESS = 'SAVE_BIO_SUCCESS';
export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE';
+export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST';
+export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS';
+export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE';
diff --git a/client/coral-framework/reducers/config.js b/client/coral-framework/reducers/config.js
index 8266fbfa1..362608d6d 100644
--- a/client/coral-framework/reducers/config.js
+++ b/client/coral-framework/reducers/config.js
@@ -22,7 +22,7 @@ export default (state = initialState, action) => {
return state
.set('status', 'closed');
case actions.ADD_ITEM:
- return action.item_type === 'assets' ? state.set('status', action.item.status) : state;
+ return action.item_type === 'assets' ? state.set('status', (action.item && action.item.closedAt && new Date(action.item.closedAt).getTime() <= new Date().getTime()) ? 'closed' : 'open') : state;
default:
return state;
}
diff --git a/client/coral-framework/reducers/items.js b/client/coral-framework/reducers/items.js
index 93388c1ab..268557809 100644
--- a/client/coral-framework/reducers/items.js
+++ b/client/coral-framework/reducers/items.js
@@ -6,6 +6,7 @@ import * as actions from '../actions/items';
const initialState = fromJS({
comments: {},
users: {},
+ assets: {},
actions: {}
});
diff --git a/client/coral-framework/reducers/user.js b/client/coral-framework/reducers/user.js
index 11b57fc15..6e9f3529c 100644
--- a/client/coral-framework/reducers/user.js
+++ b/client/coral-framework/reducers/user.js
@@ -1,11 +1,14 @@
-import {Map} from 'immutable';
+import {Map, fromJS} from 'immutable';
import * as authActions from '../constants/auth';
import * as actions from '../constants/user';
+import * as assetActions from '../constants/assets';
const initialState = Map({
displayName: '',
profiles: [],
- settings: {}
+ settings: {},
+ myComments: [],
+ myAssets: [] // the assets from which myComments (above) originated
});
const purge = user => {
@@ -30,6 +33,10 @@ export default function user (state = initialState, action) {
case actions.SAVE_BIO_SUCCESS:
return state
.set('settings', action.settings);
+ case actions.COMMENTS_BY_USER_SUCCESS:
+ return state.set('myComments', fromJS(action.comments));
+ case assetActions.MULTIPLE_ASSETS_SUCCESS:
+ return state.set('myAssets', fromJS(action.assets));
default :
return state;
}
diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js
index 7a27c1982..87f656c22 100644
--- a/client/coral-plugin-comment-count/CommentCount.js
+++ b/client/coral-plugin-comment-count/CommentCount.js
@@ -1,20 +1,23 @@
import React from 'react';
import {I18n} from '../coral-framework';
import translations from './translations.json';
+import has from 'lodash/has';
+import reduce from 'lodash/reduce';
const name = 'coral-plugin-comment-count';
const CommentCount = ({items, id}) => {
let count = 0;
- if (items.assets[id] && items.assets[id].comments) {
+ if (has(items, `assets.${id}.comments`)) {
count += items.assets[id].comments.length;
}
- const itemKeys = Object.keys(items.comments);
- for (let i = 0; i < itemKeys.length; i++) {
- const item = items.comments[itemKeys[i]];
- if (item.children) {
- count += item.children.length;
+
+ // lodash reduce works on {}
+ count += reduce(items.comments, (accum, comment) => {
+ if (comment.children) {
+ accum += comment.children.length;
}
- }
+ return accum;
+ }, 0);
return
{`${count} ${count === 1 ? lang.t('comment') : lang.t('comment-plural')}`}
diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js
index 0167d517f..086a0cf36 100644
--- a/client/coral-plugin-commentbox/CommentBox.js
+++ b/client/coral-plugin-commentbox/CommentBox.js
@@ -23,7 +23,18 @@ class CommentBox extends Component {
}
postComment = () => {
- const {postItem, updateItem, id, parent_id, child_id, addNotification, appendItemArray, premod, author} = this.props;
+ const {
+ postItem,
+ updateItem,
+ id,
+ parent_id,
+ child_id,
+ addNotification,
+ appendItemArray,
+ premod,
+ author
+ } = this.props;
+
let comment = {
body: this.state.body,
asset_id: id,
@@ -42,11 +53,14 @@ class CommentBox extends Component {
if (child_id || parent_id) {
updateItem(child_id || parent_id, 'showReply', false, 'comments');
}
+
+ if (this.props.charCount && this.state.body.length > this.props.charCount) {
+ return;
+ }
postItem(comment, 'comments')
.then((postedComment) => {
const commentId = postedComment.id;
- const status = postedComment.status;
- if (status[0] && status[0].type === 'rejected') {
+ if (postedComment.status === 'rejected') {
addNotification('error', lang.t('comment-post-banned-word'));
} else if (premod === 'pre') {
addNotification('success', lang.t('comment-post-notif-premod'));
@@ -60,8 +74,8 @@ class CommentBox extends Component {
}
render () {
- const {styles, reply, author} = this.props;
- // How to handle language in plugins? Should we have a dependency on our central translation file?
+ const {styles, reply, author, charCount} = this.props;
+ const length = this.state.body.length;
return
@@ -80,10 +94,16 @@ class CommentBox extends Component {
onChange={(e) => this.setState({body: e.target.value})}
rows={3}/>
+
charCount ? `${name}-char-max` : ''}`}>
+ {
+ charCount &&
+ `${charCount - length} ${lang.t('characters-remaining')}`
+ }
+
{ author && (