From 71d4ddf03053b814668f4bc1d8cf5aa418231c99 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 23 Nov 2016 13:28:20 -0700 Subject: [PATCH 01/53] create comment history class --- .../CommentHistory.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 client/coral-plugin-comment-history/CommentHistory.js diff --git a/client/coral-plugin-comment-history/CommentHistory.js b/client/coral-plugin-comment-history/CommentHistory.js new file mode 100644 index 000000000..492c1545b --- /dev/null +++ b/client/coral-plugin-comment-history/CommentHistory.js @@ -0,0 +1,20 @@ +import React from 'react'; +import {connect} from 'react-redux'; + +const mapStateToProps = state => { + return { + config: state.config.toJS(), + items: state.items.toJS(), + auth: state.auth.toJS() + }; +}; + +class CommentHistory extends React.Component { + render () { + return ( +
Comment History
+ ); + } +} + +export default connect(mapStateToProps)(CommentHistory); From 28520bfd4aec38692dac9422d5c1b328730f21d6 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 23 Nov 2016 17:14:51 -0700 Subject: [PATCH 02/53] add get comments by user endpoint. refactor getStream to be functional programming --- .../coral-embed-stream/src/CommentStream.js | 2 + client/coral-framework/actions/items.js | 59 +++++++++++++------ client/coral-framework/reducers/items.js | 4 +- .../CommentCount.js | 17 +++--- .../CommentHistory.js | 0 models/comment.js | 9 +++ routes/api/comments/index.js | 2 + 7 files changed, 67 insertions(+), 26 deletions(-) rename client/{coral-plugin-comment-history => coral-plugin-history}/CommentHistory.js (100%) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index a0eaca044..6da2bd22b 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -19,6 +19,7 @@ import LikeButton from '../../coral-plugin-likes/LikeButton'; import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; import UserBox from '../../coral-sign-in/components/UserBox'; +import CommentHistory from '../../coral-plugin-history/CommentHistory'; const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; @@ -87,6 +88,7 @@ class CommentStream extends Component { const {actions, users, comments} = this.props.items; const {loggedIn, user, showSignInDialog} = this.props.auth; return
+ { rootItem ?
diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index f922280c1..148dabbfa 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -1,5 +1,11 @@ +import sortBy from 'lodash/sortBy'; + /* Item Actions */ +export const REQUEST_COMMENTS_BY_USER = 'REQUEST_COMMENTS_BY_USER'; +export const RECEIVE_COMMENTS_BY_USER = 'RECEIVE_COMMENTS_BY_USER'; +export const FAILURE_COMMENTS_BY_USER = 'FAILURE_COMMENTS_BY_USER'; + /** * Action name constants */ @@ -84,6 +90,27 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => }; }; +/** + * + * Get a list of comments by a single user + * + * @param {string} user_id + * @returns Promise + */ +export const fetchCommentsByUserId = userId => { + return (dispatch) => { + dispatch({type: REQUEST_COMMENTS_BY_USER}); + return fetch(`/api/v1/comments?user_id=${userId}`, getInit('GET')) + .then(responseHandler) + .then(comments => { + dispatch({type: RECEIVE_COMMENTS_BY_USER, comments}); + }) + .catch(error => { + dispatch({type: FAILURE_COMMENTS_BY_USER, error}); + }); + }; +}; + /* * Get Items from Query * Gets a set of items from a predefined query @@ -104,25 +131,24 @@ export function getStream (assetUrl) { .then((json) => { /* Add items to the store */ - const itemTypes = Object.keys(json); - for (let i = 0; i < itemTypes.length; i++ ) { - if (itemTypes[i] === 'actions') { - for (let j = 0; j < json[itemTypes[i]].length; j++ ) { - let action = json[itemTypes[i]][j]; + Object.keys(json).forEach(type => { + if (type === 'actions') { + json[type].forEach(action => { action.id = `${action.action_type}_${action.item_id}`; dispatch(addItem(action, 'actions')); - } + }); } else { - for (let j = 0; j < json[itemTypes[i]].length; j++ ) { - dispatch(addItem(json[itemTypes[i]][j], itemTypes[i])); - } + json[type].forEach(item => { + dispatch(addItem(item, type)); + }); } - } + }); const assetId = json.assets[0].id; /* Sort comments by date*/ json.comments.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + const rels = json.comments.reduce((h, item) => { /* Check for root and child comments. */ if ( @@ -140,15 +166,14 @@ export function getStream (assetUrl) { dispatch(updateItem(assetId, 'comments', rels.rootComments, 'assets')); - const childKeys = Object.keys(rels.childComments); - for (let i = 0; i < childKeys.length; i++ ) { - dispatch(updateItem(childKeys[i], 'children', rels.childComments[childKeys[i]].reverse(), 'comments')); - } + Object.keys(rels.childComments).forEach(key => { + dispatch(updateItem(key, 'children', rels.childComments[key].reverse(), 'comments')); + }); /* Hydrate actions on comments */ - for (let i = 0; i < json.actions.length; i++ ) { - dispatch(updateItem(json.actions[i].item_id, json.actions[i].action_type, json.actions[i].id, 'comments')); - } + json.actions.forEach(action => { + dispatch(updateItem(action.item_id, action.action_type, action.id, 'comments')); + }); return (json); }); diff --git a/client/coral-framework/reducers/items.js b/client/coral-framework/reducers/items.js index 17569c545..fa14085f3 100644 --- a/client/coral-framework/reducers/items.js +++ b/client/coral-framework/reducers/items.js @@ -6,7 +6,7 @@ import * as actions from '../actions/items'; const initialState = fromJS({ comments: {}, users: {}, - actions: {} + actions: {} }); export default (state = initialState, action) => { @@ -17,7 +17,7 @@ export default (state = initialState, action) => { return state.setIn([action.item_type, action.id, action.property], fromJS(action.value)); case actions.APPEND_ITEM_ARRAY: return state.updateIn([action.item_type, action.id, action.property], (prop) => { - console.log(prop); + console.log(action, prop); if (action.add_to_front) { return prop ? prop.unshift(fromJS(action.value)) : fromJS([action.value]); } else { 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-comment-history/CommentHistory.js b/client/coral-plugin-history/CommentHistory.js similarity index 100% rename from client/coral-plugin-comment-history/CommentHistory.js rename to client/coral-plugin-history/CommentHistory.js diff --git a/models/comment.js b/models/comment.js index 2ac130978..2aedefe62 100644 --- a/models/comment.js +++ b/models/comment.js @@ -210,6 +210,15 @@ CommentSchema.statics.all = () => { return Comment.find(); }; +/** + * Returns all the comments by user + * probably to be paginated at some point in the future + * @return {Promise} array resolves to an array of comments by that user + */ +CommentSchema.statics.findByUserId = function (author_id) { + return Comment.find({author_id}); +}; + // Comment model. const Comment = mongoose.model('Comment', CommentSchema); diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index d318857d6..3fdbca334 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -11,6 +11,8 @@ router.get('/', (req, res, next) => { query = Comment.findByStatus(req.query.status); } else if (req.query.action_type) { query = Comment.findByActionType(req.query.action_type); + } else if (req.query.user_id) { + query = Comment.findByUserId(req.query.user_id); } else { query = Comment.all(); } From d10870c6bbb73906357ca9efaf2ce2ce1743c497 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 28 Nov 2016 10:54:10 -0700 Subject: [PATCH 03/53] dispatch update for adding comemnts --- client/coral-framework/actions/items.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index 148dabbfa..b4e832bd3 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -104,6 +104,11 @@ export const fetchCommentsByUserId = userId => { .then(responseHandler) .then(comments => { dispatch({type: RECEIVE_COMMENTS_BY_USER, comments}); + + comments.forEach(comment => { + dispatch(addItem(comment, 'comments')); + }); + }) .catch(error => { dispatch({type: FAILURE_COMMENTS_BY_USER, error}); From 78448b85c180a67f0e64b234146b15630003f6b4 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 9 Dec 2016 15:11:13 -0500 Subject: [PATCH 04/53] Updating to reflect new comment api. --- client/coral-plugin-commentbox/CommentBox.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 0167d517f..501d2ecc9 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -45,8 +45,7 @@ class CommentBox extends Component { 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')); From 1c2ef760eff744f93121ac5708bfbdabff006730 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 11:46:04 -1000 Subject: [PATCH 05/53] remove cruft by pulling out some immutable.js stuff --- client/coral-admin/src/components/Comment.js | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 380a001c8..be656c22d 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -14,18 +14,19 @@ 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')); + const comment = props.comment.toJS(); + const author = props.author.toJS(); + 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 +43,7 @@ export default props => {
    - {comment.get('body')} + {comment.body}
    @@ -52,9 +53,11 @@ 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 = props.comment.toJS(); + const author = props.author.toJS(); + const status = comment.status; + const flagged = comment.flagged; + const banned = (author.status === 'banned'); if (action === 'flag' && (status || flagged === true)) { return null; @@ -64,7 +67,7 @@ const getActionButton = (action, i, props) => { @@ -74,7 +77,7 @@ const getActionButton = (action, i, props) => { props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))} + onClick={() => props.onClickAction(props.actionsMap[action].status, comment.id)} /> ); }; From a80dd953c82c73095e5661351a048de2f4d92fd3 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:13:48 -1000 Subject: [PATCH 06/53] update ModerationQueue and CommentStream to not use immutable.js deep in the stack --- client/coral-admin/src/components/Comment.js | 10 ++--- .../coral-admin/src/components/CommentList.js | 38 +++++++++++-------- .../containers/CommentStream/CommentStream.js | 13 +++++-- .../ModerationQueue/ModerationQueue.js | 29 ++++---------- 4 files changed, 44 insertions(+), 46 deletions(-) diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index be656c22d..4a21b0846 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -14,8 +14,7 @@ const linkify = new Linkify(); // Render a single comment for the list export default props => { - const comment = props.comment.toJS(); - const author = props.author.toJS(); + const {comment, author} = props; let authorStatus = author.status; const links = linkify.getMatches(comment.body); @@ -53,8 +52,7 @@ export default props => { // Get the button of the action performed over a comment if any const getActionButton = (action, i, props) => { - const comment = props.comment.toJS(); - const author = props.author.toJS(); + const {comment, author} = props; const status = comment.status; const flagged = comment.flagged; const banned = (author.status === 'banned'); @@ -74,7 +72,9 @@ const getActionButton = (action, i, props) => { ); } return ( - 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..eab2e637c 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -4,6 +4,8 @@ import styles from './CommentList.css'; import key from 'keymaster'; import Hammer from 'hammerjs'; import Comment from 'components/Comment'; +import head from 'lodash/head'; +import last from 'lodash/last'; // Each action has different meaning and configuration const actions = { @@ -37,7 +39,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 +83,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: head(commentIds)}); + } else if (direction === 'up' && active !== head(commentIds)) { + this.setState({active: commentIds[commentIds.indexOf(active) - 1]}); + } else if (direction === 'down' && active !== last(commentIds)) { + this.setState({active: commentIds[commentIds.indexOf(active) + 1]}); } // scroll to the position @@ -105,10 +107,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 (last(commentIds) === 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.l - 1)]}); } } this.props.onClickAction(action, id, author_id); @@ -119,16 +121,20 @@ export default class CommentList extends React.Component { } render () { - const {singleView, commentIds, comments, users, hideActive, key} = this.props; - const {active} = this.state; + const {singleView, commentIds, hideActive, key} = this.props; + let {active} = this.state; + + const users = this.props.users.toJS(); + const comments = this.props.comments.toJS(); return (
      {commentIds.map((commentId, index) => { - const comment = comments.get(commentId); + console.log('inside the map', typeof commentId, commentId, typeof active, active); + 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 +143,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/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 34b418a57..ca3465903 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -79,6 +79,11 @@ class ModerationQueue extends React.Component { const {comments, users} = this.props; const {activeTab, singleView, modalOpen} = this.state; + const c = comments.toJS(); + const premodIds = c.ids.filter(id => c.byId[id].status === 'premod'); + const rejectedIds = c.ids.filter(id => c.byId[id].status === 'rejected'); + const flaggedIds = c.ids.filter(id => c.byId[id].flagged === true); + return (
    @@ -94,14 +99,7 @@ class ModerationQueue extends React.Component { - comments - .get('byId') - .get(id) - .get('status') === 'premod') - } + commentIds={premodIds} comments={comments.get('byId')} users={users.get('byId')} onClickAction={(action, commentId) => this.onCommentAction(action, commentId)} @@ -118,15 +116,7 @@ class ModerationQueue extends React.Component { - comments - .get('byId') - .get(id) - .get('status') === 'rejected') - } + commentIds={rejectedIds} comments={comments.get('byId')} users={users.get('byId')} onClickAction={(action, id) => this.onCommentAction(action, id)} @@ -137,10 +127,7 @@ class ModerationQueue extends React.Component { { - const data = comments.get('byId').get(id); - return !data.get('status') && data.get('flagged') === true; - })} + commentIds={flaggedIds} comments={comments.get('byId')} users={users.get('byId')} onClickAction={(action, id) => this.onCommentAction(action, id)} From 7732aa4abe251ce305052c1055e21e7bbaee9e91 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:20:46 -1000 Subject: [PATCH 07/53] typo --- client/coral-admin/src/components/CommentList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index eab2e637c..be97a6ead 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -110,7 +110,7 @@ export default class CommentList extends React.Component { if (last(commentIds) === this.state.active) { this.setState({active: commentIds[commentIds.length - 2]}); } else { - this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.l - 1)]}); + this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]}); } } this.props.onClickAction(action, id, author_id); From b05f4cfb16382fd09ee83c781992573dd83b9946 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:25:24 -1000 Subject: [PATCH 08/53] remove logs --- client/coral-admin/src/components/CommentList.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index be97a6ead..638a45adb 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -130,7 +130,6 @@ export default class CommentList extends React.Component { return (
      {commentIds.map((commentId, index) => { - console.log('inside the map', typeof commentId, commentId, typeof active, active); const comment = comments[commentId]; const author = users[comment.author_id]; return Date: Fri, 9 Dec 2016 14:38:29 -1000 Subject: [PATCH 09/53] have moderation queue use proper mapStateToProps --- .../coral-admin/src/components/CommentList.js | 5 +--- .../ModerationQueue/ModerationQueue.js | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index 638a45adb..55d6669d0 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -121,12 +121,9 @@ export default class CommentList extends React.Component { } render () { - const {singleView, commentIds, hideActive, key} = this.props; + const {singleView, commentIds, comments, users, hideActive, key} = this.props; let {active} = this.state; - const users = this.props.users.toJS(); - const comments = this.props.comments.toJS(); - return (
        {commentIds.map((commentId, index) => { diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index ca3465903..e0e3c636f 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -79,10 +79,9 @@ class ModerationQueue extends React.Component { const {comments, users} = this.props; const {activeTab, singleView, modalOpen} = this.state; - const c = comments.toJS(); - const premodIds = c.ids.filter(id => c.byId[id].status === 'premod'); - const rejectedIds = c.ids.filter(id => c.byId[id].status === 'rejected'); - const flaggedIds = c.ids.filter(id => c.byId[id].flagged === true); + const premodIds = comments.ids.filter(id => comments.byId[id].status === 'premod'); + const rejectedIds = comments.ids.filter(id => comments.byId[id].status === 'rejected'); + const flaggedIds = comments.ids.filter(id => comments.byId[id].flagged === true); return (
        @@ -100,25 +99,25 @@ class ModerationQueue extends React.Component { isActive={activeTab === 'pending'} singleView={singleView} commentIds={premodIds} - comments={comments.get('byId')} - users={users.get('byId')} + 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}/>
        this.onCommentAction(action, id)} actions={['approve']} loading={comments.loading} /> @@ -128,8 +127,8 @@ class ModerationQueue extends React.Component { isActive={activeTab === 'rejected'} singleView={singleView} commentIds={flaggedIds} - comments={comments.get('byId')} - users={users.get('byId')} + comments={comments.byId} + users={users.byId} onClickAction={(action, id) => this.onCommentAction(action, id)} actions={['reject', 'approve']} loading={comments.loading} /> @@ -142,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); From 74c4d812cddfa54614504bd183f5ac3983e6d95e Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:39:16 -1000 Subject: [PATCH 10/53] less changes --- client/coral-admin/src/components/CommentList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index 55d6669d0..8f5754182 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -122,7 +122,7 @@ export default class CommentList extends React.Component { render () { const {singleView, commentIds, comments, users, hideActive, key} = this.props; - let {active} = this.state; + const {active} = this.state; return (
          From 7bfd9e2e714db5ee2cfb59fd9e6734d5f3600988 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:48:29 -1000 Subject: [PATCH 11/53] remove lodash implementation --- client/coral-admin/src/components/CommentList.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index 8f5754182..8c6655bb5 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -4,8 +4,6 @@ import styles from './CommentList.css'; import key from 'keymaster'; import Hammer from 'hammerjs'; import Comment from 'components/Comment'; -import head from 'lodash/head'; -import last from 'lodash/last'; // Each action has different meaning and configuration const actions = { @@ -84,10 +82,10 @@ export default class CommentList extends React.Component { const {active} = this.state; // check boundaries if (active === null || !commentIds.length) { - this.setState({active: head(commentIds)}); - } else if (direction === 'up' && active !== head(commentIds)) { + 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 !== last(commentIds)) { + } else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) { this.setState({active: commentIds[commentIds.indexOf(active) + 1]}); } @@ -107,7 +105,7 @@ export default class CommentList extends React.Component { // activate the next comment if (id === this.state.active) { const {commentIds} = this.props; - if (last(commentIds) === this.state.active) { + if (commentIds[commentIds.length - 1] === this.state.active) { this.setState({active: commentIds[commentIds.length - 2]}); } else { this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]}); From 81071eacd6361f5f8f080db7918454a9ec7861be Mon Sep 17 00:00:00 2001 From: gaba Date: Sat, 10 Dec 2016 14:00:46 -1000 Subject: [PATCH 12/53] It adds a status field and change the name of the closedAt and closedMessage to record changes from the status from closed to open too. --- models/asset.js | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/models/asset.js b/models/asset.js index 3c68b7c13..c1fc59dc3 100644 --- a/models/asset.js +++ b/models/asset.js @@ -5,6 +5,12 @@ const Setting = require('./setting'); const uuid = require('uuid'); +// ASSET_STATUSES is the list of statuses that are permitted for the asset status. +const ASSET_STATUS = [ + 'open', + 'closed' +]; + const AssetSchema = new Schema({ id: { type: String, @@ -29,14 +35,15 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, - closedAt: { + status: { + type: String, + default: 'open' + }, + statusChangedAt: { type: Date, default: null }, - closedMessage: { - type: String, - default: null - }, + statusClosedMessage: String, title: String, description: String, image: String, @@ -68,9 +75,27 @@ AssetSchema.index({ * Returns true if the asset is closed, false else. */ AssetSchema.virtual('isClosed').get(function() { - return this.closedAt && this.closedAt.getTime() <= new Date().getTime(); + return (this.status === 'closed') && this.statusChangedAt && this.statusChangedAt.getTime() <= new Date().getTime(); }); +/** + * Close or Open the asset. + */ +AssetSchema.statics.changeOpenStatus = (id, status, closedMessage = '') => { + // Check to see if the user role is in the allowable set of roles. + if (ASSET_STATUS.indexOf(status) === -1) { + // Asset status is not supported! Error out here. + return Promise.reject(new Error(`status ${status} is not supported`)); + } + return Asset.update({id}, { + $set: { + status: status, + statusChangedAt: new Date().getTime(), + statusClosedMessage: closedMessage + } + }); +}; + /** * Finds an asset by its id. * @param {String} id identifier of the asset (uuid). From 5e21102c2fb9573ec9bb650555da1cd6ea02a971 Mon Sep 17 00:00:00 2001 From: gaba Date: Sat, 10 Dec 2016 14:01:24 -1000 Subject: [PATCH 13/53] Fix routes and tests for asset status change. --- routes/api/asset/index.js | 4 ++-- routes/api/comments/index.js | 2 +- tests/routes/api/comments/index.js | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 2ddc3ea23..ebdcf038d 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -90,9 +90,9 @@ router.put('/:asset_id/settings', (req, res, next) => { }); router.put('/:asset_id/status', (req, res, next) => { - // Update the asset status + Asset - .update({id: req.params.asset_id}, {status: req.query.status}) + .changeOpenStatus(req.params.asset_id, req.query.status, req.query.closedMessage) .then(() => res.status(204).end()) .catch((err) => next(err)); }); diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 168b89fc3..fad9083cc 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -89,7 +89,7 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { if (asset.isClosed) { // They have, ensure that we send back an error. - return Promise.reject(new Error(`asset has commenting closed because: ${asset.closedMessage}`)); + return Promise.reject(new Error(`asset has commenting closed because: ${asset.statusClosedMessage}`)); } return asset; diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index b3e4e2675..4787e4d58 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -199,8 +199,9 @@ describe('/api/v1/comments', () => { it('shouldn\'t create a comment when the asset has expired commenting', () => { return Asset.create({ - closedAt: new Date().setDate(0), - closedMessage: 'tests said expired!' + status: 'closed', + statusChangedAt: new Date().setDate(0), + statusClosedMessage: 'tests said expired!' }) .then((asset) => { return chai.request(app) From c9a8d448bab80a79d70714527d042fc4fe569d7e Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 12 Dec 2016 12:20:38 -0700 Subject: [PATCH 14/53] use plugin CommentHistory component --- client/coral-framework/actions/items.js | 2 -- .../CommentHistory.css | 0 client/coral-plugin-history/CommentHistory.js | 20 +++++++++++-------- .../containers/SettingsContainer.js | 6 ++++-- 4 files changed, 16 insertions(+), 12 deletions(-) rename client/{coral-settings/components => coral-plugin-history}/CommentHistory.css (100%) diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index 6cdc1adeb..01327a324 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -13,8 +13,6 @@ export const ADD_ITEM = 'ADD_ITEM'; export const UPDATE_ITEM = 'UPDATE_ITEM'; export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY'; -/* Item Actions */ - /** * Action creators */ diff --git a/client/coral-settings/components/CommentHistory.css b/client/coral-plugin-history/CommentHistory.css similarity index 100% rename from client/coral-settings/components/CommentHistory.css rename to client/coral-plugin-history/CommentHistory.css diff --git a/client/coral-plugin-history/CommentHistory.js b/client/coral-plugin-history/CommentHistory.js index 492c1545b..ea8846373 100644 --- a/client/coral-plugin-history/CommentHistory.js +++ b/client/coral-plugin-history/CommentHistory.js @@ -1,6 +1,18 @@ import React from 'react'; import {connect} from 'react-redux'; +import styles from './CommentHistory.css'; + +class CommentHistory extends React.Component { + render () { + return ( +
          +

          Comment History

          +
          + ); + } +} + const mapStateToProps = state => { return { config: state.config.toJS(), @@ -9,12 +21,4 @@ const mapStateToProps = state => { }; }; -class CommentHistory extends React.Component { - render () { - return ( -
          Comment History
          - ); - } -} - export default connect(mapStateToProps)(CommentHistory); diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js index 96020a10c..916a85baf 100644 --- a/client/coral-settings/containers/SettingsContainer.js +++ b/client/coral-settings/containers/SettingsContainer.js @@ -6,10 +6,12 @@ import {saveBio} from 'coral-framework/actions/user'; import BioContainer from './BioContainer'; import NotLoggedIn from '../components/NotLoggedIn'; import {TabBar, Tab, TabContent} from '../../coral-ui'; -import CommentHistory from '../components/CommentHistory'; +import CommentHistory from 'coral-plugin-history/CommentHistory'; import SettingsHeader from '../components/SettingsHeader'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; +import {fetchCommentsByUserId} from 'coral-framework/actions/items'; + class SignInContainer extends Component { constructor (props) { super(props); @@ -56,7 +58,7 @@ const mapStateToProps = () => ({ const mapDispatchToProps = dispatch => ({ saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData)), - getHistory: () => dispatch(), + fetchCommentsByUserId: userId => dispatch(fetchCommentsByUserId(userId)) }); export default connect( From c999e1c1a9df8969c726014d614efba1dcc2d783 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 12 Dec 2016 12:28:10 -0700 Subject: [PATCH 15/53] use new coralApi fetch --- client/coral-framework/actions/items.js | 3 +-- client/coral-settings/containers/SettingsContainer.js | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index 01327a324..e2dfc1ee9 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -92,8 +92,7 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => export const fetchCommentsByUserId = userId => { return (dispatch) => { dispatch({type: REQUEST_COMMENTS_BY_USER}); - return fetch(`/api/v1/comments?user_id=${userId}`, getInit('GET')) - .then(responseHandler) + return coralApi(`/comments?user_id=${userId}`) .then(comments => { dispatch({type: RECEIVE_COMMENTS_BY_USER, comments}); diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js index 916a85baf..913f5f62e 100644 --- a/client/coral-settings/containers/SettingsContainer.js +++ b/client/coral-settings/containers/SettingsContainer.js @@ -24,6 +24,8 @@ class SignInContainer extends Component { componentWillMount () { // Fetch commentHistory + console.log('userData', this.props.userData); + this.props.fetchCommentsByUserId(this.props.userData.id); } handleTabChange(tab) { From c016e29290835940fbc41e430363975ac3ccbb7c Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 12 Dec 2016 13:00:39 -0700 Subject: [PATCH 16/53] separate route for loading comments per user --- client/coral-framework/actions/items.js | 6 ++++-- routes/api/comments/index.js | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index e2dfc1ee9..f9f0157c7 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -92,16 +92,19 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => export const fetchCommentsByUserId = userId => { return (dispatch) => { dispatch({type: REQUEST_COMMENTS_BY_USER}); - return coralApi(`/comments?user_id=${userId}`) + return coralApi(`/comments/user/${userId}`) .then(comments => { dispatch({type: RECEIVE_COMMENTS_BY_USER, comments}); + console.log('comments?', comments); + comments.forEach(comment => { dispatch(addItem(comment, 'comments')); }); }) .catch(error => { + console.error('FAILURE_COMMENTS_BY_USER', error); dispatch({type: FAILURE_COMMENTS_BY_USER, error}); }); }; @@ -145,7 +148,6 @@ export function getStream (assetUrl) { /* Sort comments by date*/ json.comments.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); - const rels = json.comments.reduce((h, item) => { /* Check for root and child comments. */ if ( diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 168b89fc3..ec01c2984 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -116,6 +116,18 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { }); }); +router.get('/user/:user_id', (req, res, next) => { + // how to only get YOUR comments? + Comment.findByUserId(req.params.user_id) + .then(comments => { + res.json(comments); + }) + .catch(error => { + error.status = 500; + next(error); + }); +}); + router.get('/:comment_id', authorization.needed('admin'), (req, res, next) => { Comment .findById(req.params.comment_id) From 3b131ce315b3d74f81508a4161c7c1166e4995eb Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 12 Dec 2016 13:02:39 -0800 Subject: [PATCH 17/53] Removes status field for Asset. Now closedAt and closedMessage are in the settings field. --- client/coral-framework/actions/config.js | 21 ++++++----- models/asset.js | 44 +++--------------------- routes/api/asset/index.js | 18 ++++++---- routes/api/comments/index.js | 3 +- tests/routes/api/comments/index.js | 10 ++++-- 5 files changed, 36 insertions(+), 60 deletions(-) diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index f85e609eb..eb03d63ed 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}); @@ -31,3 +23,16 @@ export const updateConfiguration = newConfig => (dispatch, getState) => { }) .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(updateConfiguration({closedAt: null})); + } else { + dispatch(closeStream()); + dispatch(updateConfiguration({closedAt: Date.getTime()})); + } +}; diff --git a/models/asset.js b/models/asset.js index c1fc59dc3..09a9f49e8 100644 --- a/models/asset.js +++ b/models/asset.js @@ -5,12 +5,6 @@ const Setting = require('./setting'); const uuid = require('uuid'); -// ASSET_STATUSES is the list of statuses that are permitted for the asset status. -const ASSET_STATUS = [ - 'open', - 'closed' -]; - const AssetSchema = new Schema({ id: { type: String, @@ -35,15 +29,6 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, - status: { - type: String, - default: 'open' - }, - statusChangedAt: { - type: Date, - default: null - }, - statusClosedMessage: String, title: String, description: String, image: String, @@ -71,31 +56,6 @@ AssetSchema.index({ background: true }); -/** - * Returns true if the asset is closed, false else. - */ -AssetSchema.virtual('isClosed').get(function() { - return (this.status === 'closed') && this.statusChangedAt && this.statusChangedAt.getTime() <= new Date().getTime(); -}); - -/** - * Close or Open the asset. - */ -AssetSchema.statics.changeOpenStatus = (id, status, closedMessage = '') => { - // Check to see if the user role is in the allowable set of roles. - if (ASSET_STATUS.indexOf(status) === -1) { - // Asset status is not supported! Error out here. - return Promise.reject(new Error(`status ${status} is not supported`)); - } - return Asset.update({id}, { - $set: { - status: status, - statusChangedAt: new Date().getTime(), - statusClosedMessage: closedMessage - } - }); -}; - /** * Finds an asset by its id. * @param {String} id identifier of the asset (uuid). @@ -108,6 +68,10 @@ AssetSchema.statics.findById = (id) => Asset.findOne({id}); */ AssetSchema.statics.findByUrl = (url) => Asset.findOne({url}); +AssetSchema.virtual('isClosed').get(function() { + return this.settings && this.settings.closedAt && this.settings.closedAt <= new Date().getTime(); +}); + /** * Retrieves the settings given an asset query and rectifies it against the * global settings. diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index ebdcf038d..f61851f0c 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -89,12 +89,16 @@ router.put('/:asset_id/settings', (req, res, next) => { .catch((err) => next(err)); }); -router.put('/:asset_id/status', (req, res, next) => { - - Asset - .changeOpenStatus(req.params.asset_id, req.query.status, req.query.closedMessage) - .then(() => res.status(204).end()) - .catch((err) => next(err)); -}); +// router.put('/:asset_id/status', (req, res, next) => { +// Asset +// .findOneAndUpdate(req.params.asset_id, { +// $set: { +// closedAt: req.query.closedAt, +// closedMessage: req.query.closedMessage +// } +// }) +// .then(() => res.status(204).end()) +// .catch((err) => next(err)); +// }); module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index fad9083cc..bf741ed2d 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -87,9 +87,8 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { // Check to see if the asset has closed commenting... if (asset.isClosed) { - // They have, ensure that we send back an error. - return Promise.reject(new Error(`asset has commenting closed because: ${asset.statusClosedMessage}`)); + return Promise.reject(new Error(`asset has commenting closed because: ${asset.settings.closedMessage}`)); } return asset; diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 4787e4d58..0f6e8442e 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -160,6 +160,9 @@ describe('/api/v1/comments', () => { .then((res) => { expect(res).to.have.status(201); expect(res.body).to.have.property('id'); + }) + .catch((err) => { + expect(err).to.be.null; }); }); @@ -199,9 +202,10 @@ describe('/api/v1/comments', () => { it('shouldn\'t create a comment when the asset has expired commenting', () => { return Asset.create({ - status: 'closed', - statusChangedAt: new Date().setDate(0), - statusClosedMessage: 'tests said expired!' + settings: { + closedAt: new Date().setDate(0), + closedMessage: 'tests said expired!' + } }) .then((asset) => { return chai.request(app) From 39479fd9ad3c68766b649c0e5c9ebc219421863c Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 17:07:43 -0500 Subject: [PATCH 18/53] Add character count field. --- .../containers/Configure/CommentSettings.js | 32 +++++++++++++++++++ .../src/containers/Configure/Configure.css | 16 ++++++++++ 2 files changed, 48 insertions(+) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 194af720c..429893b34 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -11,6 +11,19 @@ import { Checkbox } from 'react-mdl'; +const updateCharCountEnable = (updateSettings, charCountChecked) => () => { + const charCountEnable = !charCountChecked; + updateSettings({charCountEnable}); +}; + +const updateCharCount = (updateSettings) => (event) => { + const charCount = event.target.value; + if (charCount.match(/[^0-9]/)) { + //Show error + } + updateSettings({charCount: charCount}); +}; + const updateModeration = (updateSettings, mod) => () => { const moderation = mod === 'pre' ? 'post' : 'pre'; updateSettings({moderation}); @@ -40,6 +53,25 @@ const CommentSettings = (props) => {lang.t('configure.enable-pre-moderation')} + + + + + + Limit Content Length +

          + Comments will be limited to + + characters. +

          +
          +
          Date: Mon, 12 Dec 2016 17:35:33 -0500 Subject: [PATCH 19/53] Updating settings box style. --- .../src/containers/Configure/CommentSettings.js | 15 ++++++++++----- .../src/containers/Configure/Configure.css | 15 ++++++++++++++- client/coral-admin/src/translations.json | 2 ++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 429893b34..53f769da4 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -45,22 +45,27 @@ const updateClosedMessage = (updateSettings) => (event) => { }; const CommentSettings = (props) => - + - {lang.t('configure.enable-pre-moderation')} + +
          {lang.t('configure.enable-pre-moderation')}
          +

          + {lang.t('configure.enable-pre-moderation-text')} +

          +
          - + - Limit Content Length +
          Limit Content Length

          Comments will be limited to

          - + Date: Mon, 12 Dec 2016 17:45:20 -0500 Subject: [PATCH 20/53] Moving text to translations file. --- .../src/containers/Configure/CommentSettings.js | 6 +++--- .../coral-admin/src/containers/Configure/Configure.css | 2 +- client/coral-admin/src/translations.json | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 53f769da4..f33b7204f 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -65,15 +65,15 @@ const CommentSettings = (props) => checked={props.settings.charCountEnable} /> -
          Limit Content Length
          +
          {lang.t('configure.comment-count-header')}

          - Comments will be limited to + {lang.t('configure.comment-count-text-pre')} - characters. + {lang.t('configure.comment-count-text-post')}

          diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css index f1519df56..ad811a351 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -112,7 +112,7 @@ } .disabledSetting { - padding-left: 23px; + padding-left: 22px; } .hidden { diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 7672cdfce..743380621 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -56,7 +56,10 @@ "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 Content Length", + "comment-count-text-pre": "Comments will be limited to ", + "comment-count-text-post": " characters." }, "bandialog": { "ban_user": "Ban User?", @@ -112,7 +115,10 @@ "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": "tracundeme", + "comment-count-text-pre": "tracundeme", + "comment-count-text-post": "tracundeme" }, "bandialog": { "ban_user": "Quieres suspender el Usuario?", From 4a4b92299cd9cba8ab0fa111790d0346a5a6c5f9 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 18:10:47 -0500 Subject: [PATCH 21/53] Adding error message. --- .../containers/Configure/CommentSettings.js | 216 ++++++++++-------- .../src/containers/Configure/Configure.css | 11 +- client/coral-admin/src/translations.json | 3 +- 3 files changed, 134 insertions(+), 96 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index f33b7204f..9e162fc30 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Component, PropTypes} from 'react'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../../translations.json'; import styles from './Configure.css'; @@ -8,108 +8,136 @@ import { ListItemContent, ListItemAction, Textfield, - Checkbox + Checkbox, + Icon } from 'react-mdl'; -const updateCharCountEnable = (updateSettings, charCountChecked) => () => { - const charCountEnable = !charCountChecked; - updateSettings({charCountEnable}); -}; +class CommentSettings extends Component { -const updateCharCount = (updateSettings) => (event) => { - const charCount = event.target.value; - if (charCount.match(/[^0-9]/)) { - //Show error + state = { + charCountError: false } - updateSettings({charCount: charCount}); -}; -const updateModeration = (updateSettings, mod) => () => { - const moderation = mod === 'pre' ? 'post' : 'pre'; - updateSettings({moderation}); -}; + static propTypes = { + updateSettings: PropTypes.func.isRequired, + settings: PropTypes.object.isRequired + } -const updateInfoBoxEnable = (updateSettings, infoBox) => () => { - const infoBoxEnable = !infoBox; - updateSettings({infoBoxEnable}); -}; + updateCharCountEnable = (updateSettings, charCountChecked) => () => { + const charCountEnable = !charCountChecked; + updateSettings({charCountEnable}); + }; -const updateInfoBoxContent = (updateSettings) => (event) => { - const infoBoxContent = event.target.value; - updateSettings({infoBoxContent}); -}; + updateCharCount = (updateSettings) => (event) => { + const charCount = event.target.value; + if (charCount.match(/[^0-9]/)) { + this.setState({charCountError: true}); + } else { + this.setState({charCountError: false}); + } + updateSettings({charCount: charCount}); + }; -const updateClosedMessage = (updateSettings) => (event) => { - const closedMessage = event.target.value; - updateSettings({closedMessage}); -}; + updateModeration = (updateSettings, mod) => () => { + const moderation = mod === 'pre' ? 'post' : 'pre'; + updateSettings({moderation}); + }; -const CommentSettings = (props) => - - - - - -
          {lang.t('configure.enable-pre-moderation')}
          -

          - {lang.t('configure.enable-pre-moderation-text')} -

          -
          -
          - - - - - -
          {lang.t('configure.comment-count-header')}
          -

          - {lang.t('configure.comment-count-text-pre')} - - {lang.t('configure.comment-count-text-post')} -

          -
          -
          - - - - - - {lang.t('configure.include-comment-stream')} -

          - {lang.t('configure.include-comment-stream-desc')} -

          -
          -
          - - - - - - - - {lang.t('configure.closed-comments-desc')} - - - -
          ; + updateInfoBoxEnable = (updateSettings, infoBox) => () => { + const infoBoxEnable = !infoBox; + updateSettings({infoBoxEnable}); + }; + + updateInfoBoxContent = (updateSettings) => (event) => { + const infoBoxContent = event.target.value; + updateSettings({infoBoxContent}); + }; + + updateClosedMessage = (updateSettings) => (event) => { + const closedMessage = event.target.value; + updateSettings({closedMessage}); + }; + + render() { + + const {updateSettings, settings} = this.props; + + return + + + + + +
          {lang.t('configure.enable-pre-moderation')}
          +

          + {lang.t('configure.enable-pre-moderation-text')} +

          +
          +
          + + + + + +
          {lang.t('configure.comment-count-header')}
          +

          + {lang.t('configure.comment-count-text-pre')} + + {lang.t('configure.comment-count-text-post')} + { + this.state.charCountError && + +
          + + {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 ad811a351..4788110de 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -18,11 +18,20 @@ .configSetting { border: 1px solid #ccc; border-radius: 4px; - height: 90px; + height: 95px; margin-bottom: 10px; align-items: flex-start; } +.settingsError { + color: #d50000; +} + +.settingsError i { + font-size: 14px; + margin-right: 3px; +} + .settingsHeader { margin-top: 3px; margin-bottom: 10px; diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 743380621..1780bc133 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -59,7 +59,8 @@ "closed-comments-label": "Write a message...", "comment-count-header": "Limit Content Length", "comment-count-text-pre": "Comments will be limited to ", - "comment-count-text-post": " characters." + "comment-count-text-post": " characters.", + "comment-count-error": "Please enter a valid number." }, "bandialog": { "ban_user": "Ban User?", From ba8a845741090ec7ed6efcfa61799fe7acac74e0 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 18:14:03 -0500 Subject: [PATCH 22/53] Making character input field green to match designs. --- .../coral-admin/src/containers/Configure/CommentSettings.js | 2 +- client/coral-admin/src/containers/Configure/Configure.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 9e162fc30..b3ab8d62a 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -87,7 +87,7 @@ class CommentSettings extends Component {

          {lang.t('configure.comment-count-text-pre')} diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css index 4788110de..667bb4744 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -71,6 +71,10 @@ border-width: 0px 0px 1px 0px; } +.charCountTexfieldEnabled { + border-color: #4caf50; +} + .charCountTexfield:focus { outline: none; } From d43cd9caf9df3010ea262dff6363bca6b2d895e3 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 12 Dec 2016 16:26:02 -0700 Subject: [PATCH 23/53] list comments out --- client/coral-framework/actions/items.js | 31 ---------------- client/coral-framework/actions/user.js | 27 ++++++++++++++ client/coral-framework/constants/user.js | 3 ++ client/coral-framework/reducers/items.js | 1 - client/coral-framework/reducers/user.js | 7 ++-- client/coral-plugin-history/CommentHistory.js | 35 +++++++++---------- .../components/CommentHistory.js | 15 -------- .../containers/SettingsContainer.js | 12 +++---- 8 files changed, 57 insertions(+), 74 deletions(-) delete mode 100644 client/coral-settings/components/CommentHistory.js diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index f9f0157c7..cd798c6b9 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -6,9 +6,6 @@ import {UPDATE_CONFIG} from '../constants/config'; * Action name constants */ -export const REQUEST_COMMENTS_BY_USER = 'REQUEST_COMMENTS_BY_USER'; -export const RECEIVE_COMMENTS_BY_USER = 'RECEIVE_COMMENTS_BY_USER'; -export const FAILURE_COMMENTS_BY_USER = 'FAILURE_COMMENTS_BY_USER'; export const ADD_ITEM = 'ADD_ITEM'; export const UPDATE_ITEM = 'UPDATE_ITEM'; export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY'; @@ -82,34 +79,6 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => }; }; -/** - * - * Get a list of comments by a single user - * - * @param {string} user_id - * @returns Promise - */ -export const fetchCommentsByUserId = userId => { - return (dispatch) => { - dispatch({type: REQUEST_COMMENTS_BY_USER}); - return coralApi(`/comments/user/${userId}`) - .then(comments => { - dispatch({type: RECEIVE_COMMENTS_BY_USER, comments}); - - console.log('comments?', comments); - - comments.forEach(comment => { - dispatch(addItem(comment, 'comments')); - }); - - }) - .catch(error => { - console.error('FAILURE_COMMENTS_BY_USER', error); - dispatch({type: FAILURE_COMMENTS_BY_USER, error}); - }); - }; -}; - /* * Get Items from Query * Gets a set of items from a predefined query diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index cda2d765d..5cbd04792 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -1,5 +1,6 @@ import * as actions from '../constants/user'; 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 +20,29 @@ 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.REQUEST_COMMENTS_BY_USER}); + return coralApi(`/comments/user/${userId}`) + .then(comments => { + comments.forEach(comment => { + dispatch(addItem(comment, 'comments')); + }); + + dispatch({type: actions.RECEIVE_COMMENTS_BY_USER, comments: comments.map(comment => comment.id)}); + }) + .catch(error => { + console.error(error.stack); + console.error('FAILURE_COMMENTS_BY_USER', error); + dispatch({type: actions.FAILURE_COMMENTS_BY_USER, error}); + }); + }; +}; diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js index 0c316d48a..ce9af61a2 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 REQUEST_COMMENTS_BY_USER = 'REQUEST_COMMENTS_BY_USER'; +export const RECEIVE_COMMENTS_BY_USER = 'RECEIVE_COMMENTS_BY_USER'; +export const FAILURE_COMMENTS_BY_USER = 'FAILURE_COMMENTS_BY_USER'; diff --git a/client/coral-framework/reducers/items.js b/client/coral-framework/reducers/items.js index fa14085f3..93388c1ab 100644 --- a/client/coral-framework/reducers/items.js +++ b/client/coral-framework/reducers/items.js @@ -17,7 +17,6 @@ export default (state = initialState, action) => { return state.setIn([action.item_type, action.id, action.property], fromJS(action.value)); case actions.APPEND_ITEM_ARRAY: return state.updateIn([action.item_type, action.id, action.property], (prop) => { - console.log(action, prop); if (action.add_to_front) { return prop ? prop.unshift(fromJS(action.value)) : fromJS([action.value]); } else { diff --git a/client/coral-framework/reducers/user.js b/client/coral-framework/reducers/user.js index 11b57fc15..c7badf458 100644 --- a/client/coral-framework/reducers/user.js +++ b/client/coral-framework/reducers/user.js @@ -1,11 +1,12 @@ -import {Map} from 'immutable'; +import {Map, fromJS} from 'immutable'; import * as authActions from '../constants/auth'; import * as actions from '../constants/user'; const initialState = Map({ displayName: '', profiles: [], - settings: {} + settings: {}, + myComments: [] }); const purge = user => { @@ -30,6 +31,8 @@ export default function user (state = initialState, action) { case actions.SAVE_BIO_SUCCESS: return state .set('settings', action.settings); + case actions.RECEIVE_COMMENTS_BY_USER: + return state.set('myComments', fromJS(action.comments)); default : return state; } diff --git a/client/coral-plugin-history/CommentHistory.js b/client/coral-plugin-history/CommentHistory.js index ea8846373..de95ff2b2 100644 --- a/client/coral-plugin-history/CommentHistory.js +++ b/client/coral-plugin-history/CommentHistory.js @@ -1,24 +1,21 @@ -import React from 'react'; -import {connect} from 'react-redux'; +import React, {PropTypes} from 'react'; import styles from './CommentHistory.css'; -class CommentHistory extends React.Component { - render () { - return ( -

          -

          Comment History

          -
          - ); - } -} - -const mapStateToProps = state => { - return { - config: state.config.toJS(), - items: state.items.toJS(), - auth: state.auth.toJS() - }; +const CommentHistory = props => { + return ( +
          +

          Comment History

          + {props.comments.map((comment, i) => { + console.log('a comment', comment); + return

          {comment.body}

          ; + })} +
          + ); }; -export default connect(mapStateToProps)(CommentHistory); +CommentHistory.propTypes = { + comments: PropTypes.arrayOf(PropTypes.object).isRequired +}; + +export default CommentHistory; diff --git a/client/coral-settings/components/CommentHistory.js b/client/coral-settings/components/CommentHistory.js deleted file mode 100644 index 3160c8a88..000000000 --- a/client/coral-settings/components/CommentHistory.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import styles from './CommentHistory.css'; - -export default ({comments = []}) => ( -
          -

          Comments

          -
            - {comments.map(() => ( -
          • - {/* Comment Data*/} -
          • - ))} -
          -
          -); diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js index 913f5f62e..9ad489816 100644 --- a/client/coral-settings/containers/SettingsContainer.js +++ b/client/coral-settings/containers/SettingsContainer.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; -import {saveBio} from 'coral-framework/actions/user'; +import {saveBio, fetchCommentsByUserId} from 'coral-framework/actions/user'; import BioContainer from './BioContainer'; import NotLoggedIn from '../components/NotLoggedIn'; @@ -10,8 +10,6 @@ import CommentHistory from 'coral-plugin-history/CommentHistory'; import SettingsHeader from '../components/SettingsHeader'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; -import {fetchCommentsByUserId} from 'coral-framework/actions/items'; - class SignInContainer extends Component { constructor (props) { super(props); @@ -35,7 +33,7 @@ class SignInContainer extends Component { } render() { - const {loggedIn, userData, showSignInDialog} = this.props; + const {loggedIn, userData, showSignInDialog, items, user} = this.props; const {activeTab} = this.state; return ( }> @@ -45,7 +43,7 @@ class SignInContainer extends Component { Profile Settings - + items.comments[id])} /> @@ -55,7 +53,9 @@ class SignInContainer extends Component { } } -const mapStateToProps = () => ({ +const mapStateToProps = state => ({ + items: state.items.toJS(), + user: state.user.toJS() }); const mapDispatchToProps = dispatch => ({ From 0648b2b1c81ff0a434ec89e6c8c2fa22af570554 Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 12 Dec 2016 15:38:28 -0800 Subject: [PATCH 24/53] More tests. --- client/coral-framework/actions/config.js | 18 ++++++++++++-- models/asset.js | 10 +++++++- routes/api/asset/index.js | 27 ++++++++++++--------- routes/api/comments/index.js | 2 +- tests/routes/api/assets/index.js | 31 +++++++++++++++++++++++- tests/routes/api/comments/index.js | 6 ++--- 6 files changed, 74 insertions(+), 20 deletions(-) diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index eb03d63ed..7850577dc 100644 --- a/client/coral-framework/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -24,15 +24,29 @@ export const updateConfiguration = newConfig => (dispatch, getState) => { .catch(error => dispatch(updateConfigFailure(error))); }; +export const updateOpenStream = newConfig => (dispatch, getState) => { + const assetId = getState().items.get('assets') + .keySeq() + .toArray()[0]; + + dispatch(updateConfigRequest()); + coralApi(`/asset/${assetId}/status`, {method: 'PUT', body: newConfig}) + .then(() => { + dispatch(addNotification('success', lang.t('successUpdateSettings'))); + dispatch(updateConfigSuccess(newConfig)); + }) + .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(updateConfiguration({closedAt: null})); + dispatch(updateOpenStream({closedAt: null})); } else { dispatch(closeStream()); - dispatch(updateConfiguration({closedAt: Date.getTime()})); + dispatch(updateOpenStream({closedAt: Date.now(), closedMessage: ''})); } }; diff --git a/models/asset.js b/models/asset.js index 09a9f49e8..ed1bf98d0 100644 --- a/models/asset.js +++ b/models/asset.js @@ -29,6 +29,14 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, + closedAt: { + type: Date, + default: null + }, + closedMessage: { + type: String, + default: null + }, title: String, description: String, image: String, @@ -69,7 +77,7 @@ AssetSchema.statics.findById = (id) => Asset.findOne({id}); AssetSchema.statics.findByUrl = (url) => Asset.findOne({url}); AssetSchema.virtual('isClosed').get(function() { - return this.settings && this.settings.closedAt && this.settings.closedAt <= new Date().getTime(); + return this.closedAt && this.closedAt.getTime() <= new Date().getTime(); }); /** diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index f61851f0c..6723d3671 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -89,16 +89,21 @@ router.put('/:asset_id/settings', (req, res, next) => { .catch((err) => next(err)); }); -// router.put('/:asset_id/status', (req, res, next) => { -// Asset -// .findOneAndUpdate(req.params.asset_id, { -// $set: { -// closedAt: req.query.closedAt, -// closedMessage: req.query.closedMessage -// } -// }) -// .then(() => res.status(204).end()) -// .catch((err) => next(err)); -// }); +router.put('/:asset_id/status', (req, res, next) => { + const id = req.params.asset_id; + const { + closedAt = null, + closedMessage = null + } = req.query; + Asset + .update(id, { + closedAt: closedAt, + closedMessage: closedMessage + }) + .then((asset) => { + res.status(201).json(asset); + }) + .catch((err) => next(err)); +}); module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index bf741ed2d..b54717c78 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -88,7 +88,7 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { // Check to see if the asset has closed commenting... if (asset.isClosed) { // They have, ensure that we send back an error. - return Promise.reject(new Error(`asset has commenting closed because: ${asset.settings.closedMessage}`)); + return Promise.reject(new Error(`asset has commenting closed because: ${asset.closedMessage}`)); } return asset; diff --git a/tests/routes/api/assets/index.js b/tests/routes/api/assets/index.js index c56e5b1ba..817f5b181 100644 --- a/tests/routes/api/assets/index.js +++ b/tests/routes/api/assets/index.js @@ -17,7 +17,8 @@ describe('/api/v1/assets', () => { { url: 'https://coralproject.net/news/asset1', title: 'Asset 1', - description: 'term1' + description: 'term1', + id: '1' }, { url: 'https://coralproject.net/news/asset2', @@ -82,4 +83,32 @@ describe('/api/v1/assets', () => { }); + describe('#put', () => { + it('should close the asset', function() { + + const asset = Asset.findByUrl('http://test.com') + .then((asset) => { + expect(asset).to.have.property('closedAt') + .and.to.equal(null); + }); + + const today = Date.now(); + return chai.request(app) + .put(`/api/v1/asset/${asset.id}/status`) + .set(passport.inject({roles: ['admin']})) + .send({closedAt: today}) + .then((res) => { + expect(res).to.have.status(201); + + Asset.findByUrl('http://test.com') + .then((asset) => { + expect(asset).to.have.property('isClosed') + .and.to.equal(true); + expect(asset).to.have.property('closedAt') + .and.to.not.equal(null); + }); + }); + }); + }); + }); diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 0f6e8442e..59d840e1b 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -202,10 +202,8 @@ describe('/api/v1/comments', () => { it('shouldn\'t create a comment when the asset has expired commenting', () => { return Asset.create({ - settings: { - closedAt: new Date().setDate(0), - closedMessage: 'tests said expired!' - } + closedAt: new Date().setDate(0), + closedMessage: 'tests said expired!' }) .then((asset) => { return chai.request(app) From cd63f49a5e56706068d6c84bccb8585c4ffb8bb6 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 19:08:35 -0500 Subject: [PATCH 25/53] Disabling save button on error and updating translations. --- client/coral-admin/src/actions/settings.js | 5 +- .../containers/Configure/CommentSettings.js | 217 ++++++++---------- .../src/containers/Configure/Configure.js | 21 +- client/coral-admin/src/translations.json | 9 +- models/setting.js | 10 +- 5 files changed, 136 insertions(+), 126 deletions(-) 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/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index b3ab8d62a..6589e115e 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -12,132 +12,115 @@ import { Icon } from 'react-mdl'; -class CommentSettings extends Component { +const updateCharCountEnable = (updateSettings, charCountChecked) => () => { + const charCountEnable = !charCountChecked; + updateSettings({charCountEnable}); +}; - state = { - charCountError: false +const updateCharCount = (updateSettings, settingsError) => (event) => { + const charCount = event.target.value; + if (charCount.match(/[^0-9]/)) { + settingsError('charCount', true); + } else { + settingsError('charCount', false); } + updateSettings({charCount: charCount}); +}; - static propTypes = { - updateSettings: PropTypes.func.isRequired, - settings: PropTypes.object.isRequired - } +const updateModeration = (updateSettings, mod) => () => { + const moderation = mod === 'pre' ? 'post' : 'pre'; + updateSettings({moderation}); +}; - updateCharCountEnable = (updateSettings, charCountChecked) => () => { - const charCountEnable = !charCountChecked; - updateSettings({charCountEnable}); - }; +const updateInfoBoxEnable = (updateSettings, infoBox) => () => { + const infoBoxEnable = !infoBox; + updateSettings({infoBoxEnable}); +}; - updateCharCount = (updateSettings) => (event) => { - const charCount = event.target.value; - if (charCount.match(/[^0-9]/)) { - this.setState({charCountError: true}); - } else { - this.setState({charCountError: false}); - } - updateSettings({charCount: charCount}); - }; +const updateInfoBoxContent = (updateSettings) => (event) => { + const infoBoxContent = event.target.value; + updateSettings({infoBoxContent}); +}; - updateModeration = (updateSettings, mod) => () => { - const moderation = mod === 'pre' ? 'post' : 'pre'; - updateSettings({moderation}); - }; +const updateClosedMessage = (updateSettings) => (event) => { + const closedMessage = event.target.value; + updateSettings({closedMessage}); +}; - updateInfoBoxEnable = (updateSettings, infoBox) => () => { - const infoBoxEnable = !infoBox; - updateSettings({infoBoxEnable}); - }; - - updateInfoBoxContent = (updateSettings) => (event) => { - const infoBoxContent = event.target.value; - updateSettings({infoBoxContent}); - }; - - updateClosedMessage = (updateSettings) => (event) => { - const closedMessage = event.target.value; - updateSettings({closedMessage}); - }; - - render() { - - const {updateSettings, settings} = this.props; - - return - - - - - -
          {lang.t('configure.enable-pre-moderation')}
          -

          - {lang.t('configure.enable-pre-moderation-text')} +const CommentSettings = ({updateSettings, settingsError, settings, errors}) => + + + + + +

          {lang.t('configure.enable-pre-moderation')}
          +

          + {lang.t('configure.enable-pre-moderation-text')} +

          +
          +
          + + + + + +
          {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.comment-count-header')}
          -

          - {lang.t('configure.comment-count-text-pre')} - - {lang.t('configure.comment-count-text-post')} - { - this.state.charCountError && - -
          - - {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')} - - - -
          ; - } -} +
          + + + + + + {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.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 ?
        :

        {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-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 0167d517f..078aba201 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, @@ -59,8 +70,16 @@ class CommentBox extends Component { this.setState({body: ''}); } + onUpdateComment = (e) => { + const body = e.target.value; + if (!this.props.charCount || body.length > this.props.charCount) { + this.setState({body}); + } + } + render () { - const {styles, reply, author} = this.props; + const {styles, reply, author, charCount} = this.props; + const length = this.state.body.length; // How to handle language in plugins? Should we have a dependency on our central translation file? return
    this.setState({body: e.target.value})} rows={3}/>
    +
    charCount && `${name}-char-max`}`}> + { + charCount && + `${length}/${charCount}` + } +
    { author && (