From 1adac5cde1186282975f93db26a3db75f4592c5f Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 11 Nov 2016 16:25:47 -0500 Subject: [PATCH 01/33] Adding like button. --- .../coral-embed-stream/src/CommentStream.js | 55 +++++++++++++------ client/coral-embed-stream/style/default.css | 21 ++++++- client/coral-plugin-flags/FlagButton.js | 24 ++++---- client/coral-plugin-likes/LikeButton.js | 35 ++++++++++++ client/coral-plugin-likes/translations.json | 10 ++++ client/coral-plugin-replies/ReplyButton.js | 2 +- 6 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 client/coral-plugin-likes/LikeButton.js create mode 100644 client/coral-plugin-likes/translations.json diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index d6edfddda..e3832a0dc 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -14,6 +14,7 @@ import AuthorName from '../../coral-plugin-author-name/AuthorName'; import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; import Pym from 'pym.js'; import FlagButton from '../../coral-plugin-flags/FlagButton'; +import LikeButton from '../../coral-plugin-likes/LikeButton'; const {addItem, updateItem, postItem, getStream, postAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; @@ -119,7 +120,20 @@ class CommentStream extends Component { -
+
+ + +
+
-
-
- - -
+
+ + +
+
+ +
; }) } diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index e1c730bfc..f68e2a0e3 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -105,16 +105,33 @@ hr { /* Comment Action Styles */ -.commentActions, .replyActions { +.commentActionsRight, .replyActionsRight { display: flex; justify-content: flex-end; + width: 50%; +} +.commentActionsLeft, .replyActionsLeft { + display: flex; + justify-content: flex-start; + float: left; + width: 50%; } -.commentActions .material-icons, .replyActions .material-icons { +.commentActionsLeft .material-icons,.commentActionsRight .material-icons, +.replyActionsLeft .material-icons, .replyActionsRight .material-icons { font-size: 12px; + margin-left: 3px; vertical-align: middle; } +.likedButton { + color: rgb(0,134,227); +} + +.flaggedIcon { + color: #F00; +} + /* Comment count styles */ .coral-plugin-comment-count-text { margin-bottom: 15px; diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index 4f90dfb7c..7374def5f 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -7,24 +7,26 @@ const name = 'coral-plugin-flags'; const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification}) => { const flagged = flag && flag.current_user; const onFlagClick = () => { - postAction(id, 'flag', '123', 'comments') - .then((action) => { - addItem({...action, current_user:true}, 'actions'); - updateItem(action.item_id, action.action_type, action.id, 'comments'); - }); - addNotification('success', lang.t('flag-notif')); + if (!flagged) { + postAction(id, 'flag', '123', 'comments') + .then((action) => { + addItem({...action, current_user:true}, 'actions'); + updateItem(action.item_id, action.action_type, action.id, 'comments'); + }); + addNotification('success', lang.t('flag-notif')); + } }; return
; }; diff --git a/client/coral-plugin-likes/LikeButton.js b/client/coral-plugin-likes/LikeButton.js new file mode 100644 index 000000000..ec8528793 --- /dev/null +++ b/client/coral-plugin-likes/LikeButton.js @@ -0,0 +1,35 @@ +import React from 'react'; +import {I18n} from '../coral-framework'; +import translations from './translations.json'; + +const name = 'coral-plugin-flags'; + +const LikeButton = ({like, id, postAction, addItem, updateItem}) => { + const liked = like && like.current_user; + const onLikeClick = () => { + if (!liked) { + postAction(id, 'like', '123', 'comments') + .then((action) => { + addItem({id: action.id, current_user:true, count: like ? like.count + 1 : 1}, 'actions'); + updateItem(action.item_id, action.action_type, action.id, 'comments'); + }); + } + }; + + return
+ +
; +}; + +export default LikeButton; + +const lang = new I18n(translations); diff --git a/client/coral-plugin-likes/translations.json b/client/coral-plugin-likes/translations.json new file mode 100644 index 000000000..93d73d3a2 --- /dev/null +++ b/client/coral-plugin-likes/translations.json @@ -0,0 +1,10 @@ +{ + "en": { + "like": "Like", + "liked": "Liked" + }, + "es": { + "like": "Me Gusta", + "liked": "Me Gustó" + } +} diff --git a/client/coral-plugin-replies/ReplyButton.js b/client/coral-plugin-replies/ReplyButton.js index 44213c2f7..4fbfd5f60 100644 --- a/client/coral-plugin-replies/ReplyButton.js +++ b/client/coral-plugin-replies/ReplyButton.js @@ -7,9 +7,9 @@ const name = 'coral-plugin-replies'; const ReplyButton = (props) => ; export default ReplyButton; From 92869e529abdd41dbba8b2b332938b2953543f78 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 11 Nov 2016 16:27:56 -0500 Subject: [PATCH 02/33] Fixing comment count bug. --- client/coral-plugin-comment-count/CommentCount.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index 47f00ffb5..6786d5c74 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -5,12 +5,12 @@ const name = 'coral-plugin-comment-count'; const CommentCount = ({items, id}) => { let count = 0; - if (items[id]) { - count += items[id].comments.length; + if (items.assets[id]) { + count += items.assets[id].comments.length; } - const itemKeys = Object.keys(items); + const itemKeys = Object.keys(items.comments); for (let i = 0; i < itemKeys.length; i++) { - const item = items[itemKeys[i]]; + const item = items.comments[itemKeys[i]]; if (item.children) { count += item.children.length; } From 4bea5e5914f3a978bf04594a6e294b2890bc1fab Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 11 Nov 2016 17:06:29 -0500 Subject: [PATCH 03/33] Adding route to delete actions. --- models/comment.js | 21 ++++++++++++++++++--- routes/api/comments/index.js | 19 +++++++++++++++---- tests/models/comment.js | 11 +++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/models/comment.js b/models/comment.js index 8eab6aff4..492c06e9c 100644 --- a/models/comment.js +++ b/models/comment.js @@ -104,14 +104,14 @@ CommentSchema.statics.findByStatusByActionType = function(status, action_type) { return Action .findCommentsIdByActionType(action_type, 'comment') .then((actions) => { - + return Comment.find({ - 'status': status, + 'status': status, 'id': { '$in': actions.map(a => { return a.item_id; }) - } + } }); }); @@ -188,6 +188,21 @@ CommentSchema.statics.removeById = function(id) { return Comment.remove({'id': id}); }; +/** + * Remove an action from the comment. + * @param {String} id identifier of the comment (uuid) + * @param {String} action_type the type of the action to be removed + * @param {String} user_id the id of the user performing the action +*/ +CommentSchema.statics.removeAction = function(id, user_id, action_type) { + return Action.remove({ + action_type: action_type, + item_type: 'comment', + item_id: id, + user_id: user_id + }); +}; + const Comment = mongoose.model('Comment', CommentSchema); module.exports = Comment; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 5126bf93c..525dc87e6 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -54,7 +54,7 @@ router.get('/status/rejected', (req, res, next) => { // Pre-moderation: New comments are shown in the moderator queues immediately. // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. router.get('/status/pending', (req, res, next) => { - + Setting .getModerationSetting() .then(({moderation}) => { @@ -76,9 +76,9 @@ router.get('/status/pending', (req, res, next) => { //============================================================================== router.post('/', (req, res, next) => { - + const {body, author_id, asset_id, parent_id, status, username} = req.body; - + Comment .new(body, author_id, asset_id, parent_id, status, username) .then((comment) => { @@ -109,7 +109,7 @@ router.post('/:comment_id', (req, res, next) => { }); router.post('/:comment_id/status', (req, res, next) => { - + Comment .changeStatus(req.params.comment_id, req.body.status) .then(comment => res.status(200).send(comment)) @@ -143,4 +143,15 @@ router.delete('/:comment_id', (req, res, next) => { }); }); +router.delete('/:comment_id/actions', (req, res, next) => { + Comment + .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) + .then(() => { + res.status(201).send('OK. Removed'); + }) + .catch(error => { + next(error); + }); +}); + module.exports = router; diff --git a/tests/models/comment.js b/tests/models/comment.js index 94e45625a..f8dd7f9f5 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -134,4 +134,15 @@ describe('Comment: models', () => { // }); // }); }); + + describe('#removeAction', () => { + it('should remove an action', () => { + return Comment.removeAction('3', '123', 'flag').then(() => { + return Action.findByItemIdArray(['123']); + }) + .then((actions) => { + expect(actions.length).to.equal(0); + }); + }); + }); }); From b629cf359f69ca3ac742734f296ed174ad628a80 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 11 Nov 2016 19:07:15 -0500 Subject: [PATCH 04/33] Removing likes and flags on second click. --- .../coral-embed-stream/src/CommentStream.js | 9 +++- client/coral-framework/store/actions/items.js | 44 +++++++++++++++++-- client/coral-plugin-flags/FlagButton.js | 14 ++++-- client/coral-plugin-flags/translations.json | 6 ++- client/coral-plugin-likes/LikeButton.js | 10 ++++- .../coral-framework/store/itemActions.spec.js | 18 ++++++++ 6 files changed, 88 insertions(+), 13 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index e3832a0dc..9270b7a01 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -16,7 +16,7 @@ import Pym from 'pym.js'; import FlagButton from '../../coral-plugin-flags/FlagButton'; import LikeButton from '../../coral-plugin-likes/LikeButton'; -const {addItem, updateItem, postItem, getStream, postAction, appendItemArray} = itemActions; +const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; const {setLoggedInUser} = authActions; @@ -55,6 +55,9 @@ const mapDispatchToProps = (dispatch) => { postAction: (item, action, user, itemType) => { return dispatch(postAction(item, action, user, itemType)); }, + deleteAction: (item, action, user, itemType) => { + return dispatch(deleteAction(item, action, user, itemType)); + }, appendItemArray: (item, property, value, addToFront, itemType) => { return dispatch(appendItemArray(item, property, value, addToFront, itemType)); } @@ -129,6 +132,7 @@ class CommentStream extends Component { id={commentId} like={this.props.items.actions[comment.like]} postAction={this.props.postAction} + deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> @@ -139,6 +143,7 @@ class CommentStream extends Component { id={commentId} flag={this.props.items.actions[comment.flag]} postAction={this.props.postAction} + deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> @@ -170,6 +175,7 @@ class CommentStream extends Component { id={replyId} like={this.props.items.actions[reply.like]} postAction={this.props.postAction} + deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> @@ -180,6 +186,7 @@ class CommentStream extends Component { id={replyId} flag={this.props.items.actions[reply.flag]} postAction={this.props.postAction} + deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 07f8e54af..648a1d95e 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -241,9 +241,45 @@ export function postAction (item_id, action_type, user_id, item_type) { return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`); } - ) - .then((json)=>{ - return json; - }); + ); + }; +} + +/* +* DeleteAction +* Deletes an action to an item +* +* @params +* id - the id of the item on which the action is taking place +* action - the name of the action +* user - the user performing the action +* host - the coral host +* +* @returns +* A promise resolving to null or an error +* +*/ + +export function deleteAction (item_id, action_type, user_id, item_type) { + return () => { + const action = { + action_type, + user_id + }; + const options = { + method: 'DELETE', + headers: { + 'Content-Type':'application/json' + }, + body: JSON.stringify(action) + }; + + return fetch(`/api/v1/${item_type}/${item_id}/actions`, options) + .then( + response => { + return response.ok ? response.text() + : Promise.reject(`${response.status} ${response.statusText}`); + } + ); }; } diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index 7374def5f..a4869c486 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -4,7 +4,7 @@ import translations from './translations.json'; const name = 'coral-plugin-flags'; -const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification}) => { +const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, addNotification}) => { const flagged = flag && flag.current_user; const onFlagClick = () => { if (!flagged) { @@ -14,17 +14,23 @@ const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification} updateItem(action.item_id, action.action_type, action.id, 'comments'); }); addNotification('success', lang.t('flag-notif')); + } else { + deleteAction(id, 'flag', '123', 'comments') + .then(() => { + updateItem(id, 'flag', '', 'comments'); + }); + addNotification('success', lang.t('flag-notif-remove')); } }; - return
- diff --git a/client/coral-plugin-flags/translations.json b/client/coral-plugin-flags/translations.json index 28eb9f8cb..fb2daa883 100644 --- a/client/coral-plugin-flags/translations.json +++ b/client/coral-plugin-flags/translations.json @@ -2,11 +2,13 @@ "en": { "flag": "Flag", "flagged": "Flagged", - "flag-notif": "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly." + "flag-notif": "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly.", + "flag-notif-remove": "Your flag has been removed." }, "es": { "flag": "Marcar", "flagged": "Marcado", - "flag-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar." + "flag-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar.", + "flag-notif-remove": "¡traduceme!" } } diff --git a/client/coral-plugin-likes/LikeButton.js b/client/coral-plugin-likes/LikeButton.js index ec8528793..9bdf7b84d 100644 --- a/client/coral-plugin-likes/LikeButton.js +++ b/client/coral-plugin-likes/LikeButton.js @@ -4,7 +4,7 @@ import translations from './translations.json'; const name = 'coral-plugin-flags'; -const LikeButton = ({like, id, postAction, addItem, updateItem}) => { +const LikeButton = ({like, id, postAction, deleteAction, addItem, updateItem}) => { const liked = like && like.current_user; const onLikeClick = () => { if (!liked) { @@ -13,6 +13,12 @@ const LikeButton = ({like, id, postAction, addItem, updateItem}) => { addItem({id: action.id, current_user:true, count: like ? like.count + 1 : 1}, 'actions'); updateItem(action.item_id, action.action_type, action.id, 'comments'); }); + } else { + deleteAction(id, 'like', '123', 'comments') + .then(() => { + updateItem(like.id, 'count', like.count - 1, 'actions'); + updateItem(like.id, 'current_user', false, 'actions'); + }); } }; @@ -25,7 +31,7 @@ const LikeButton = ({like, id, postAction, addItem, updateItem}) => { } thumb_up - {like && like.count} + {like && like.count > 0 && like.count}
; }; diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index d45212794..33fe15cab 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -162,6 +162,24 @@ describe('itemActions', () => { expect(err).to.be.truthy; }); }); + }); + describe('deleteAction', () => { + it ('should remove an action', () => { + fetchMock.delete('*', 'Action removed.'); + return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch) + .then(response => { + expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions'); + expect(response).to.equal('Action removed.'); + }); + }); + + it('should handle an error', () => { + fetchMock.post('*', 404); + return actions.postAction('abc', 'flag', '123')(store.dispatch) + .catch((err) => { + expect(err).to.be.truthy; + }); + }); }); }); From 494b7d808bbcd94a3f24d5fb802a53c0d8b6fdf5 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 13:02:08 -0500 Subject: [PATCH 05/33] Addressing lint error. --- client/coral-embed-stream/src/CommentStream.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index dbe720325..9a1910105 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -14,11 +14,8 @@ import AuthorName from '../../coral-plugin-author-name/AuthorName'; import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; import Pym from 'pym.js'; import FlagButton from '../../coral-plugin-flags/FlagButton'; -<<<<<<< HEAD import LikeButton from '../../coral-plugin-likes/LikeButton'; -======= import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; ->>>>>>> 9897135035a3cdf14e22fe995b6a44e8b599f0b7 const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; From 6fa40c3f9b42d796859888785995ae06c4022d5f Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 15:11:25 -0500 Subject: [PATCH 06/33] Adding model tests for assets. --- models/asset.js | 12 +- tests/models/asset.js | 181 +++++++++++++++++-------------- tests/routes/api/assets/index.js | 95 ++++++++++++++++ 3 files changed, 203 insertions(+), 85 deletions(-) create mode 100644 tests/routes/api/assets/index.js diff --git a/models/asset.js b/models/asset.js index de8155131..1092cd3e0 100644 --- a/models/asset.js +++ b/models/asset.js @@ -62,11 +62,21 @@ AssetSchema.statics.findByUrl = function(url) { }; +/** + * Finds a asset by its url. + * @param {String} url identifier of the asset (uuid). +*/ +AssetSchema.statics.findOrCreateByUrl = function(url) { + + return Asset.findOne({url}) + .then((asset) => asset ? asset + : Asset.upsert({url})); +}; + /** * Upserts an asset. */ AssetSchema.statics.upsert = function(data) { - // If an id is not sent, create one. if (typeof data.id === 'undefined') { data.id = uuid.v4(); diff --git a/tests/models/asset.js b/tests/models/asset.js index b4d950a93..d0c3d3429 100644 --- a/tests/models/asset.js +++ b/tests/models/asset.js @@ -1,95 +1,108 @@ +/* eslint-env node, mocha */ + require('../utils/mongoose'); -const chai = require('chai'); -const expect = chai.expect; -const server = require('../../app'); +const Asset = require('../../models/asset'); +const expect = require('chai').expect; -// Setup chai. -chai.should(); -chai.use(require('chai-http')); +describe('Asset: model', () => { -let fixture = { - 'url': 'http://hhgg.com/total-perspective-vortex', - 'type': 'article', - 'headline': 'The Total Perspective Vortex', - 'summary': 'You are an insignificant dot on an insignificant dot.', - 'section': 'Everything', - 'authors': ['Ford Prefect'] -}; + beforeEach(() => { + const defaults = {url:'http://test.com'}; + return Asset.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); + }); -describe('Asset: models', () => { - - describe('/GET Asset', () => { - describe('#get', () => { - it('It should get an empty array when there are no assets.', (done) => { - - chai.request(server) - .get('/api/v1/asset') - .end((err, res) => { - - if (err) { - throw new Error(err); - } - - res.should.have.status(200); - res.body.should.be.a('array'); - res.body.length.should.be.eql(0); - done(); - }); - - }); + describe('#findById', ()=> { + it('should find an asset by the id', () => { + return Asset.findById(1) + .then((asset) => { + expect(asset).to.have.property('url') + .and.to.equal('http://test.com'); + }); }); }); - // This test checks PUT and read - describe('/PUT Asset', () => { - describe('#put', () => { - it('It should save an asset and load it again.', (done) => { - - chai.request(server) - .put('/api/v1/asset') - .send(fixture) - .end((err, res) => { - - if (err) { - throw new Error(err); - } - - res.should.have.status(200); - res.body.should.be.a('object'); - - // Id should be generated by the model if absent. - res.body.should.have.property('id'); - - // Save the asset id to compare with GET result. - let assetId = res.body.id; - - // Load the asset to make sure it's really there. - chai.request(server) - .get(`/api/v1/asset?url=${encodeURIComponent(fixture.url)}`) - .end((err, res) => { - - if (err) { - throw new Error(err); - } - - res.should.have.status(200); - res.body.should.be.an('array'); - - let asset = res.body[0]; - - expect(asset).to.have.property('id'); - - // Ensure the asset has the same id as above. - // This tests the single url per Id concept. - expect(assetId).to.equal(asset.id); - - done(); - - }); - }); - }); + describe('#findByUrl', ()=> { + it('should find an asset by a url', () => { + return Asset.findByUrl('http://test.com') + .then((asset) => { + expect(asset).to.have.property('url') + .and.to.equal('http://test.com'); + }); }); - }); // End describe /PUT Asset + it('should return null when a url does not exist', () => { + return Asset.findByUrl('http://new.test.com') + .then((asset) => { + expect(asset).to.be.null; + }); + }); + }); + + describe('#findOrCreateByUrl', ()=> { + it('should find an asset by a url', () => { + return Asset.findOrCreateByUrl('http://test.com') + .then((asset) => { + expect(asset).to.have.property('url') + .and.to.equal('http://test.com'); + }); + }); + + it('should return a new asset when the url does not exist', () => { + return Asset.findOrCreateByUrl('http://new.test.com') + .then((asset) => { + expect(asset).to.have.property('id') + .and.to.not.equal(1); + }); + }); + }); + + describe('#findOrCreateByUrl', ()=> { + it('should find an asset by a url', () => { + return Asset.findOrCreateByUrl('http://test.com') + .then((asset) => { + expect(asset).to.have.property('url') + .and.to.equal('http://test.com'); + }); + }); + + it('should return a new asset when the url does not exist', () => { + return Asset.findOrCreateByUrl('http://new.test.com') + .then((asset) => { + expect(asset).to.have.property('id') + .and.to.not.equal(1); + }); + }); + }); + + describe('#upsert', ()=> { + it('should insert an asset with no id', () => { + return Asset.upsert({url: 'http://newasset.test.com'}) + .then((asset) => { + expect(asset).to.have.property('id'); + }); + }); + + it('should update an asset when the id exists', () => { + return Asset.upsert({id: 1, url: 'http://new.test.com'}) + .then((asset) => { + expect(asset).to.have.property('id') + .and.to.equal('1'); + expect(asset).to.have.property('url') + .and.to.equal('http://new.test.com'); + }); + }); + }); + + describe('#removeAll', ()=> { + it('should insert an asset with no id', () => { + return Asset.removeAll({id:1}) + .then(() => { + return Asset.findById(1); + }) + .then((result) => { + expect(result).to.be.null; + }); + }); + }); }); diff --git a/tests/routes/api/assets/index.js b/tests/routes/api/assets/index.js new file mode 100644 index 000000000..aa764e214 --- /dev/null +++ b/tests/routes/api/assets/index.js @@ -0,0 +1,95 @@ +require('../../../utils/mongoose'); + +const chai = require('chai'); +const expect = chai.expect; +const server = require('../../../../app'); + +// Setup chai. +chai.should(); +chai.use(require('chai-http')); + +let fixture = { + 'url': 'http://hhgg.com/total-perspective-vortex', + 'type': 'article', + 'headline': 'The Total Perspective Vortex', + 'summary': 'You are an insignificant dot on an insignificant dot.', + 'section': 'Everything', + 'authors': ['Ford Prefect'] +}; + +describe('Asset: routes', () => { + + describe('/GET Asset', () => { + describe('#get', () => { + it('It should get an empty array when there are no assets.', (done) => { + + chai.request(server) + .get('/api/v1/asset') + .end((err, res) => { + + if (err) { + throw new Error(err); + } + + res.should.have.status(200); + res.body.should.be.a('array'); + res.body.length.should.be.eql(0); + done(); + }); + + }); + }); + }); + + // This test checks PUT and read + describe('/PUT Asset', () => { + describe('#put', () => { + it('It should save an asset and load it again.', (done) => { + + chai.request(server) + .put('/api/v1/asset') + .send(fixture) + .end((err, res) => { + + if (err) { + throw new Error(err); + } + + res.should.have.status(200); + res.body.should.be.a('object'); + + // Id should be generated by the model if absent. + res.body.should.have.property('id'); + + // Save the asset id to compare with GET result. + let assetId = res.body.id; + + // Load the asset to make sure it's really there. + chai.request(server) + .get(`/api/v1/asset?url=${encodeURIComponent(fixture.url)}`) + .end((err, res) => { + + if (err) { + throw new Error(err); + } + + res.should.have.status(200); + res.body.should.be.an('array'); + + let asset = res.body[0]; + + expect(asset).to.have.property('id'); + + // Ensure the asset has the same id as above. + // This tests the single url per Id concept. + expect(assetId).to.equal(asset.id); + + done(); + + }); + }); + }); + }); + }); // End describe /PUT Asset + +}); From 1a7b716820b8c103e2366f69611b9a901a67123c Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 15:51:17 -0500 Subject: [PATCH 07/33] Updating stream endpoint to expect asset_url and to return asset. --- routes/api/stream/index.js | 26 ++++++++++++++++-------- tests/routes/api/stream/index.js | 35 +++++++++++++++++--------------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index 3f49d48fc..db1d2bc36 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -3,35 +3,45 @@ const express = require('express'); const Comment = require('../../../models/comment'); const User = require('../../../models/user'); const Action = require('../../../models/action'); +const Asset = require('../../../models/asset'); const Setting = require('../../../models/setting'); const router = express.Router(); -// Find all the comments by a specific asset_id. +// Find all the comments by a specific asset_url. // . if pre: get the comments that are accepted. // . if post: get the comments that are new and accepted. router.get('/', (req, res, next) => { - const commentsPromise = Setting.getModerationSetting().then(({moderation}) => { + + // Get the asset_id for this url (or create it if it doesn't exist) + Promise.all([ + Asset.findOrCreateByUrl(req.query.asset_url), + Setting.getModerationSetting() + ]) + .then(([asset, {moderation}]) => { + // Get the sitewide moderation setting and return the appropriate comments switch(moderation){ case 'pre': - return Comment.findAcceptedByAssetId(req.query.asset_id); + return Promise.all([Comment.findAcceptedByAssetId(asset.id), asset]); case 'post': - return Comment.findAcceptedAndNewByAssetId(req.query.asset_id); + return Promise.all([Comment.findAcceptedAndNewByAssetId(asset.id), asset]); default: throw new Error('Moderation setting not found.'); } - }); - + }) // Get all the users and actions for those comments. - commentsPromise.then(comments => { + .then(([comments, asset]) => { return Promise.all([ + [asset], comments, User.findByIdArray(comments.map((comment) => comment.author_id)), Action.getActionSummaries(comments.map((comment) => comment.id)) ]); - }).then(([comments, users, actions]) => { + }) + .then(([assets, comments, users, actions]) => { res.json({ + assets, comments, users, actions diff --git a/tests/routes/api/stream/index.js b/tests/routes/api/stream/index.js index c967cb932..009184b6b 100644 --- a/tests/routes/api/stream/index.js +++ b/tests/routes/api/stream/index.js @@ -11,6 +11,7 @@ chai.use(require('chai-http')); const Action = require('../../../../models/action'); const User = require('../../../../models/user'); const Comment = require('../../../../models/comment'); +const Asset = require('../../../../models/asset'); const Setting = require('../../../../models/setting'); @@ -21,14 +22,12 @@ describe('api/stream: routes', () => { const comments = [{ id: 'abc', body: 'comment 10', - asset_id: 'asset', author_id: '', parent_id: '', status: 'accepted' }, { id: 'def', body: 'comment 20', - asset_id: 'asset', author_id: '', parent_id: '', status: '' @@ -66,29 +65,33 @@ describe('api/stream: routes', () => { beforeEach(() => { - return User - .createLocalUsers(users) - .then(users => { + return Promise.all([ + User.createLocalUsers(users), + Asset.findOrCreateByUrl('http://test.com') + ]) + .then(([users, asset]) => { - comments[0].author_id = users[0].id; - comments[1].author_id = users[1].id; - - return Promise.all([ - Comment.create(comments), - Action.create(actions), - Setting.create(settings) - ]); + comments[0].author_id = users[0].id; + comments[1].author_id = users[1].id; - }); + comments[0].asset_id = asset.id; + comments[1].asset_id = asset.id; + return Promise.all([ + Comment.create(comments), + Action.create(actions), + Setting.create(settings) + ]); + }); }); - it('should return a stream with comments, users and actions', () => { + it('should return a stream with comments, users and actions for an existing asset', () => { return chai.request(app) .get('/api/v1/stream') - .query({'asset_id': 'asset'}) + .query({'asset_url': 'http://test.com'}) .then(res => { expect(res).to.have.status(200); + expect(res.body.assets.length).to.equal(1); expect(res.body.comments.length).to.equal(1); expect(res.body.users.length).to.equal(1); expect(res.body.actions.length).to.equal(1); From 223887616d4983a31248256938b2f1c3e65c7ba3 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 14 Nov 2016 16:20:56 -0500 Subject: [PATCH 08/33] feature(coral-admin): Add users role change --- client/coral-admin/src/actions/community.js | 10 +- client/coral-admin/src/constants/community.js | 1 + .../src/containers/Community/Community.js | 4 + .../src/containers/Community/Table.js | 94 +++++++++++++------ client/coral-admin/src/reducers/community.js | 9 +- client/coral-admin/src/translations.js | 12 ++- package.json | 1 + routes/api/user/index.js | 14 ++- 8 files changed, 108 insertions(+), 37 deletions(-) diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 06c8ab0f6..d35cf0614 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -5,7 +5,8 @@ import { FETCH_COMMENTERS_SUCCESS, FETCH_COMMENTERS_FAILURE, SORT_UPDATE, - COMMENTERS_NEW_PAGE + COMMENTERS_NEW_PAGE, + SET_ROLE } from '../constants/community'; import {base, getInit, handleResp} from '../helpers/response'; @@ -40,3 +41,10 @@ export const newPage = () => ({ type: COMMENTERS_NEW_PAGE }); +export const setRole = (id, role) => dispatch => { + return fetch(`${base}/user/${id}/role`, getInit('POST', { role })) + .then(() => { + return dispatch({ type: SET_ROLE, id, role }); + }) + +} diff --git a/client/coral-admin/src/constants/community.js b/client/coral-admin/src/constants/community.js index e628a14d6..2ea77ea77 100644 --- a/client/coral-admin/src/constants/community.js +++ b/client/coral-admin/src/constants/community.js @@ -3,3 +3,4 @@ export const FETCH_COMMENTERS_SUCCESS = 'FETCH_COMMENTERS_SUCCESS'; export const FETCH_COMMENTERS_FAILURE = 'FETCH_COMMENTERS_FAILURE'; export const SORT_UPDATE = 'SORT_UPDATE'; export const COMMENTERS_NEW_PAGE = 'COMMENTERS_NEW_PAGE'; +export const SET_ROLE = 'SET_ROLE'; diff --git a/client/coral-admin/src/containers/Community/Community.js b/client/coral-admin/src/containers/Community/Community.js index 8e0b955a4..fb6f5df8c 100644 --- a/client/coral-admin/src/containers/Community/Community.js +++ b/client/coral-admin/src/containers/Community/Community.js @@ -19,6 +19,10 @@ const tableHeaders = [ { title: lang.t('community.account_creation_date'), field: 'created_at' + }, + { + title: lang.t('community.newsroom_role'), + field: 'role' } ]; diff --git a/client/coral-admin/src/containers/Community/Table.js b/client/coral-admin/src/containers/Community/Table.js index a15a88723..66dbc894b 100644 --- a/client/coral-admin/src/containers/Community/Table.js +++ b/client/coral-admin/src/containers/Community/Table.js @@ -1,34 +1,66 @@ -import React from 'react'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { SelectField, Option } from 'react-mdl-selectfield'; import styles from './Community.css'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from '../../translations'; +import { setRole } from '../../actions/community'; -const Table = ({headers, data, onHeaderClickHandler}) => ( - - - - {headers.map((header, i) =>( - - ))} - - - - {data.map((row, i)=> ( - - - - - ))} - -
onHeaderClickHandler({field: header.field})}> - {header.title} -
- {row.displayName} - {row.profiles.map(({id}) => id)} - - {row.created_at} -
-); +const lang = new I18n(translations); -export default Table; +class Table extends Component { + + constructor (props) { + super(props); + this.onRoleChange = this.onRoleChange.bind(this); + } + + onRoleChange (id, role) { + this.props.dispatch(setRole(id, role)); + } + + render () { + const { headers, commenters, onHeaderClickHandler } = this.props; + + return ( + + + + {headers.map((header, i) =>( + + ))} + + + + {commenters.map((row, i)=> ( + + + + + + ))} + +
onHeaderClickHandler({field: header.field})}> + {header.title} +
+ {row.displayName} + {row.profiles.map(({id}) => id)} + + {row.created_at} + + this.onRoleChange(row.id, role)}> + + + + +
+ ); + } +} + +export default connect(state => ({ commenters: state.community.get('commenters') }))(Table); diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index 8d5dfd2c3..3421a079c 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -4,7 +4,8 @@ import { FETCH_COMMENTERS_REQUEST, FETCH_COMMENTERS_FAILURE, FETCH_COMMENTERS_SUCCESS, - SORT_UPDATE + SORT_UPDATE, + SET_ROLE } from '../constants/community'; const initialState = Map({ @@ -37,6 +38,12 @@ export default function community (state = initialState, action) { }) .set('commenters', commenters); // Sets to normal array } + case SET_ROLE: + const commenters = state.get('commenters'); + const idx = commenters.findIndex(el => el.id === action.id); + + commenters[idx].roles[0] = action.role + return state.set('commenters', commenters); case SORT_UPDATE : return state .set('field', action.sort.field) diff --git a/client/coral-admin/src/translations.js b/client/coral-admin/src/translations.js index 67a8142fd..fa655294a 100644 --- a/client/coral-admin/src/translations.js +++ b/client/coral-admin/src/translations.js @@ -2,7 +2,11 @@ export default { en: { 'community': { username_and_email: 'Username and Email', - account_creation_date: 'Account Creation Date' + account_creation_date: 'Account Creation Date', + newsroom_role: 'Newsroom Role', + admin: 'Administrator', + moderator: 'Moderator', + role: 'Select role...' }, 'modqueue': { 'pending': 'pending', @@ -30,7 +34,11 @@ export default { es: { 'community': { username_and_email: 'Usuario y E-mail', - account_creation_date: 'Fecha de creación de la cuenta' + account_creation_date: 'Fecha de creación de la cuenta', + newsroom_role: 'Rol en la redacción', + admin: 'Administrador', + moderator: 'Moderador', + role: 'Select role...' }, 'modqueue': { 'pending': 'pendiente', diff --git a/package.json b/package.json index cbdaad174..a36fcbd3a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "mongoose": "^4.6.5", "morgan": "^1.7.0", "prompt": "^1.0.0", + "react-mdl-selectfield": "^0.2.0", "uuid": "^2.0.3" }, "devDependencies": { diff --git a/routes/api/user/index.js b/routes/api/user/index.js index 18872eab7..4665f4e52 100644 --- a/routes/api/user/index.js +++ b/routes/api/user/index.js @@ -40,11 +40,13 @@ router.get('/', (req, res, next) => { ]) .then(([data, count]) => { const users = data.map((user) => { - const {displayName, created_at} = user; + const {id, displayName, created_at} = user; return { + id, displayName, created_at, - profiles: user.toObject().profiles + profiles: user.toObject().profiles, + roles: user.toObject().roles }; }); @@ -60,4 +62,12 @@ router.get('/', (req, res, next) => { .catch(next); }); +router.post('/:user_id/role', (req, res, next) => { + User.addRoleToUser(req.params.user_id, req.body.role) + .then(role => { + res.send(role); + }) + .catch(next); +}); + module.exports = router; From 688fbd0bf68aa2e0c0290e4bf415e5ca4b93c330 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 16:40:04 -0500 Subject: [PATCH 09/33] Sending asset_url to backend and retreiving stream with asset. --- .../coral-embed-stream/src/CommentStream.js | 22 +++++-------------- client/coral-framework/store/actions/items.js | 11 +++++----- .../CommentCount.js | 2 +- routes/api/stream/index.js | 2 +- .../coral-framework/store/itemActions.spec.js | 15 ++++++++----- 5 files changed, 21 insertions(+), 31 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 9a1910105..5fb48d0a0 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -76,28 +76,16 @@ class CommentStream extends Component { componentDidMount () { // Set up messaging between embedded Iframe an parent component // Using recommended Pym init code which violates .eslint standards - new Pym.Child({polling: 500}); - this.props.getStream('assetTest'); + const pym = new Pym.Child({polling: 100}); + console.log(pym); + this.props.getStream('http://www.test.com'); } render () { - if (Object.keys(this.props.items).length === 0) { - // Loading mock asset - this.props.postItem({ - comments: [], - url: 'http://coralproject.net' - }, 'asset', 'assetTest'); - - // Loading mock user - this.props.postItem({name: 'Ban Ki-Moon'}, 'user', 'user_8989') - .then((id) => { - this.props.setLoggedInUser(id); - }); - } // TODO: Replace teststream id with id from params - const rootItemId = 'assetTest'; + const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0]; const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; return
{ @@ -117,7 +105,7 @@ class CommentStream extends Component { reply={false}/>
{ - rootItem.comments.map((commentId) => { + rootItem.comments && rootItem.comments.map((commentId) => { const comment = this.props.items.comments[commentId]; return

diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 648a1d95e..eea3315b0 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -77,9 +77,9 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => * @dispatches * A set of items to the item store */ -export function getStream (assetId) { +export function getStream (assetUrl) { return (dispatch) => { - return fetch(`/api/v1/stream?asset_id=${assetId}`) + return fetch(`/api/v1/stream?asset_url=${encodeURIComponent(assetUrl)}`) .then( response => { return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`); @@ -95,6 +95,8 @@ export function getStream (assetId) { } } + 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) => { @@ -112,10 +114,7 @@ export function getStream (assetId) { return h; }, {rootComments: [], childComments: {}}); - dispatch(addItem({ - id: assetId, - comments: rels.rootComments, - }, 'assets')); + dispatch(updateItem(assetId, 'comments', rels.rootComments, 'assets')); const childKeys = Object.keys(rels.childComments); for (let i = 0; i < childKeys.length; i++ ) { diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index 6786d5c74..7a27c1982 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -5,7 +5,7 @@ const name = 'coral-plugin-comment-count'; const CommentCount = ({items, id}) => { let count = 0; - if (items.assets[id]) { + if (items.assets[id] && items.assets[id].comments) { count += items.assets[id].comments.length; } const itemKeys = Object.keys(items.comments); diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index db1d2bc36..b093f0d31 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -16,7 +16,7 @@ router.get('/', (req, res, next) => { // Get the asset_id for this url (or create it if it doesn't exist) Promise.all([ - Asset.findOrCreateByUrl(req.query.asset_url), + Asset.findOrCreateByUrl(decodeURIComponent(req.query.asset_url)), Setting.getModerationSetting() ]) .then(([asset, {moderation}]) => { diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index 33fe15cab..d6ce0c970 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -18,8 +18,11 @@ describe('itemActions', () => { }); describe('getStream', () => { - const rootId = '1234'; + const assetUrl = 'http://www.test.com'; const response = { + assets: [{ + id: '1234', url: assetUrl + }], comments: [ {body: 'stuff', id: '123'}, {body: 'morestuff', id: '456'} @@ -42,17 +45,17 @@ describe('itemActions', () => { it('should get an stream from an asset_id and send the appropriate dispatches', () => { fetchMock.get('*', JSON.stringify(response)); - return actions.getStream(rootId)(store.dispatch) + return actions.getStream(assetUrl)(store.dispatch) .then((res) => { - expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_id=1234'); + expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_url=http%3A%2F%2Fwww.test.com'); expect(res).to.deep.equal(response); - expect(store.getActions()[0]).to.deep.equal({ + expect(store.getActions()[1]).to.deep.equal({ type: actions.ADD_ITEM, item: response.comments[0], item_type: 'comments', id: '123' }); - expect(store.getActions()[1]).to.deep.equal({ + expect(store.getActions()[2]).to.deep.equal({ type: actions.ADD_ITEM, item: response.comments[1], item_type: 'comments', @@ -62,7 +65,7 @@ describe('itemActions', () => { }); it('should handle an error', () => { fetchMock.get('*', 404); - return actions.getStream(rootId)(store.dispatch) + return actions.getStream(assetUrl)(store.dispatch) .catch((err) => { expect(err).to.be.truthy; }); From 9c41adf224f59cc3db1a175127758c3a82609442 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 14 Nov 2016 16:44:21 -0500 Subject: [PATCH 10/33] Added enum check for the user model --- models/user.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/user.js b/models/user.js index c0a846f1a..12f939e72 100644 --- a/models/user.js +++ b/models/user.js @@ -24,7 +24,10 @@ const UserSchema = new mongoose.Schema({ required: true } }], - roles: [String] + roles: { + type: [{ type: String, enum: ['admin', 'moderator'] }], + + } }, { timestamps: { createdAt: 'created_at', From 16fa3e46f56af90ebf12f5e8fa1508070d94649b Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 16:51:54 -0500 Subject: [PATCH 11/33] Loading comment stream based on parent URL. --- client/coral-embed-stream/src/CommentStream.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 5fb48d0a0..aa58b5103 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -77,8 +77,7 @@ class CommentStream extends Component { // Set up messaging between embedded Iframe an parent component // Using recommended Pym init code which violates .eslint standards const pym = new Pym.Child({polling: 100}); - console.log(pym); - this.props.getStream('http://www.test.com'); + this.props.getStream(pym.parentUrl); } render () { From e53ac55e6b242272e39350ba76c74dc6bad346d5 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 14 Nov 2016 17:03:31 -0500 Subject: [PATCH 12/33] Linting --- client/coral-admin/src/actions/community.js | 9 ++++----- client/coral-admin/src/containers/Community/Table.js | 12 ++++++------ client/coral-admin/src/reducers/community.js | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index d35cf0614..7a4112f8b 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -42,9 +42,8 @@ export const newPage = () => ({ }); export const setRole = (id, role) => dispatch => { - return fetch(`${base}/user/${id}/role`, getInit('POST', { role })) + return fetch(`${base}/user/${id}/role`, getInit('POST', {role})) .then(() => { - return dispatch({ type: SET_ROLE, id, role }); - }) - -} + return dispatch({type: SET_ROLE, id, role}); + }); +}; diff --git a/client/coral-admin/src/containers/Community/Table.js b/client/coral-admin/src/containers/Community/Table.js index 66dbc894b..97848bc9a 100644 --- a/client/coral-admin/src/containers/Community/Table.js +++ b/client/coral-admin/src/containers/Community/Table.js @@ -1,10 +1,10 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { SelectField, Option } from 'react-mdl-selectfield'; +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {SelectField, Option} from 'react-mdl-selectfield'; import styles from './Community.css'; import I18n from 'coral-framework/i18n/i18n'; import translations from '../../translations'; -import { setRole } from '../../actions/community'; +import {setRole} from '../../actions/community'; const lang = new I18n(translations); @@ -20,7 +20,7 @@ class Table extends Component { } render () { - const { headers, commenters, onHeaderClickHandler } = this.props; + const {headers, commenters, onHeaderClickHandler} = this.props; return ( @@ -63,4 +63,4 @@ class Table extends Component { } } -export default connect(state => ({ commenters: state.community.get('commenters') }))(Table); +export default connect(state => ({commenters: state.community.get('commenters')}))(Table); diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index 3421a079c..d8fe88dbc 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -38,11 +38,11 @@ export default function community (state = initialState, action) { }) .set('commenters', commenters); // Sets to normal array } - case SET_ROLE: + case SET_ROLE : const commenters = state.get('commenters'); const idx = commenters.findIndex(el => el.id === action.id); - commenters[idx].roles[0] = action.role + commenters[idx].roles[0] = action.role; return state.set('commenters', commenters); case SORT_UPDATE : return state From 44292a81ad86844c22bab6a028ec90c52fdd33b5 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 17:09:20 -0500 Subject: [PATCH 13/33] Moving init and responsehandler to shared functions, updating all routes to return valid JSON. --- client/coral-framework/store/actions/items.js | 82 ++++++------------- routes/api/comments/index.js | 4 +- .../coral-framework/store/itemActions.spec.js | 5 +- 3 files changed, 32 insertions(+), 59 deletions(-) diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 648a1d95e..2e7dca70f 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -8,6 +8,23 @@ export const ADD_ITEM = 'ADD_ITEM'; export const UPDATE_ITEM = 'UPDATE_ITEM'; export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY'; +const getInit = (method, body) => { + const headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + + const init = {method, headers}; + if (method.toLowerCase() !== 'get') { + init.body = JSON.stringify(body); + } + + return init; +}; + +const responseHandler = response => { + return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`); +}; /** * Action creators */ @@ -79,12 +96,8 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => */ export function getStream (assetId) { return (dispatch) => { - return fetch(`/api/v1/stream?asset_id=${assetId}`) - .then( - response => { - return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`); - } - ) + return fetch(`/api/v1/stream?asset_id=${assetId}`, getInit('GET')) + .then(responseHandler) .then((json) => { /* Add items to the store */ @@ -148,13 +161,8 @@ export function getStream (assetId) { export function getItemsArray (ids) { return (dispatch) => { - return fetch(`/v1/item/${ids}`) - .then( - response => { - return response.ok ? response.json() - : Promise.reject(`${response.status } ${ response.statusText}`); - } - ) + return fetch(`/v1/item/${ids}`, getInit('GET')) + .then(responseHandler) .then((json) => { for (let i = 0; i < json.items.length; i++) { dispatch(addItem(json.items[i])); @@ -183,20 +191,8 @@ export function postItem (item, type, id) { if (id) { item.id = id; } - let options = { - method: 'POST', - body: JSON.stringify(item), - headers: { - 'Content-Type':'application/json' - } - }; - return fetch(`/api/v1/${type}`, options) - .then( - response => { - return response.ok ? response.json() - : Promise.reject(`${response.status} ${response.statusText}`); - } - ) + return fetch(`/api/v1/${type}`, getInit('POST', item)) + .then(responseHandler) .then((json) => { dispatch(addItem({...item, id:json.id}, type)); return json.id; @@ -227,21 +223,9 @@ export function postAction (item_id, action_type, user_id, item_type) { action_type, user_id }; - const options = { - method: 'POST', - headers: { - 'Content-Type':'application/json' - }, - body: JSON.stringify(action) - }; - return fetch(`/api/v1/${item_type}/${item_id}/actions`, options) - .then( - response => { - return response.ok ? response.json() - : Promise.reject(`${response.status} ${response.statusText}`); - } - ); + return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('POST', action)) + .then(responseHandler); }; } @@ -266,20 +250,8 @@ export function deleteAction (item_id, action_type, user_id, item_type) { action_type, user_id }; - const options = { - method: 'DELETE', - headers: { - 'Content-Type':'application/json' - }, - body: JSON.stringify(action) - }; - return fetch(`/api/v1/${item_type}/${item_id}/actions`, options) - .then( - response => { - return response.ok ? response.text() - : Promise.reject(`${response.status} ${response.statusText}`); - } - ); + return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action)) + .then(responseHandler); }; } diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index eabb1be7b..4954563cb 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -126,7 +126,7 @@ router.delete('/:comment_id', (req, res, next) => { Comment .removeById(req.params.comment_id) .then(() => { - res.status(201).send('OK. Removed'); + res.status(201).send({}); }) .catch(error => { next(error); @@ -137,7 +137,7 @@ router.delete('/:comment_id/actions', (req, res, next) => { Comment .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) .then(() => { - res.status(201).send('OK. Removed'); + res.status(201).sent({}); }) .catch(error => { next(error); diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index 33fe15cab..4dbba5e82 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -119,6 +119,7 @@ describe('itemActions', () => { { method: 'POST', headers: { + 'Accept': 'application/json', 'Content-Type':'application/json' }, body: JSON.stringify(item.data) @@ -166,11 +167,11 @@ describe('itemActions', () => { describe('deleteAction', () => { it ('should remove an action', () => { - fetchMock.delete('*', 'Action removed.'); + fetchMock.delete('*', {}); return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch) .then(response => { expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions'); - expect(response).to.equal('Action removed.'); + expect(response).to.deep.equal({}); }); }); From 14405b22aaa44b32e404fb93eaa92c29d8f94c36 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 14 Nov 2016 20:02:01 -0500 Subject: [PATCH 14/33] Stripping out protocol and query from asset url. --- client/coral-embed-stream/src/CommentStream.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index aa58b5103..06253b72f 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -77,7 +77,8 @@ class CommentStream extends Component { // Set up messaging between embedded Iframe an parent component // Using recommended Pym init code which violates .eslint standards const pym = new Pym.Child({polling: 100}); - this.props.getStream(pym.parentUrl); + const path = /https?\:\/\/([^?]+)/.exec(pym.parentUrl)[1]; + this.props.getStream(path); } render () { From 366e072280cdf89a1bd7fc103f500e485f0b6f5e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 14 Nov 2016 23:35:33 -0300 Subject: [PATCH 15/33] Linting Issues --- client/coral-admin/src/reducers/community.js | 3 ++- models/user.js | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index d8fe88dbc..a42252805 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -38,12 +38,13 @@ export default function community (state = initialState, action) { }) .set('commenters', commenters); // Sets to normal array } - case SET_ROLE : + case SET_ROLE : { const commenters = state.get('commenters'); const idx = commenters.findIndex(el => el.id === action.id); commenters[idx].roles[0] = action.role; return state.set('commenters', commenters); + } case SORT_UPDATE : return state .set('field', action.sort.field) diff --git a/models/user.js b/models/user.js index 12f939e72..c6197a220 100644 --- a/models/user.js +++ b/models/user.js @@ -24,10 +24,9 @@ const UserSchema = new mongoose.Schema({ required: true } }], - roles: { - type: [{ type: String, enum: ['admin', 'moderator'] }], - - } + roles: { + type: [{type: String, enum: ['admin', 'moderator']}] + } }, { timestamps: { createdAt: 'created_at', From 4fd41704c090d57f4bf903489db464de0f608891 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 15 Nov 2016 12:31:30 -0500 Subject: [PATCH 16/33] Moving preview to embed code. --- views/embed-stream.ejs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/views/embed-stream.ejs b/views/embed-stream.ejs index 47bda6bee..36f7ce43b 100644 --- a/views/embed-stream.ejs +++ b/views/embed-stream.ejs @@ -17,7 +17,12 @@ -
- +
+ + + + From 27261cc515942d8cacafcbcfc73ef0a6f970a700 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Tue, 15 Nov 2016 12:37:05 -0500 Subject: [PATCH 17/33] Added fix for returning a new array --- client/coral-admin/src/reducers/community.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index d8fe88dbc..215063f0f 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -43,7 +43,7 @@ export default function community (state = initialState, action) { const idx = commenters.findIndex(el => el.id === action.id); commenters[idx].roles[0] = action.role; - return state.set('commenters', commenters); + return state.set('commenters', commenters.map(id => id)); case SORT_UPDATE : return state .set('field', action.sort.field) From 2438161ad957a728d81d0b5c6ec8926203905845 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Tue, 15 Nov 2016 12:54:25 -0500 Subject: [PATCH 18/33] Removed extra return --- client/coral-admin/src/reducers/community.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index 7ff8514fd..81a09a1cc 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -44,7 +44,6 @@ export default function community (state = initialState, action) { commenters[idx].roles[0] = action.role; return state.set('commenters', commenters.map(id => id)); - return state.set('commenters', commenters); } case SORT_UPDATE : return state From 08d672ed4655cda257ec0c44a78c6ce2648ea533 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 10:11:37 -0800 Subject: [PATCH 19/33] Changes model and add admin. --- .../coral-admin/src/containers/Configure.css | 6 +++++ .../coral-admin/src/containers/Configure.js | 24 ++++++++++++++++- .../coral-embed-stream/src/CommentStream.js | 4 +++ client/coral-embed-stream/style/default.css | 11 ++++++++ client/coral-plugin-infobox/InfoBox.js | 11 ++++++++ .../__tests__/infoBox.spec.js | 26 +++++++++++++++++++ models/setting.js | 12 ++++++++- tests/models/setting.js | 12 +++++++-- 8 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 client/coral-plugin-infobox/InfoBox.js create mode 100644 client/coral-plugin-infobox/__tests__/infoBox.spec.js diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index 98cb0e254..c832e96c1 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -23,6 +23,12 @@ cursor: pointer; } +.configSettingInfoBox { + border: 1px solid #ccc; + border-radius: 4px; + margin-bottom: 10px; +} + .configSettingEmbed { border: 1px solid #ccc; border-radius: 4px; diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 229c4e948..0dff4cf9a 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -7,7 +7,7 @@ import { ListItem, ListItemContent, ListItemAction, - //Textfield, + Textfield, Checkbox, Button, Icon @@ -36,6 +36,16 @@ class Configure extends React.Component { this.props.dispatch(updateSettings({moderation})); } + updateInfoBoxEnable () { + const infoboxEnable = this.props.settings.infoBoxEnable; + this.props.dispatch(updateSettings({infoboxEnable})); + } + + updateInfoBoxContent () { + const infoboxContent = this.props.settings.infoBoxContent; + this.props.dispatch(updateSettings({infoboxContent})); + } + saveSettings () { this.props.dispatch(saveSettingsToServer()); } @@ -50,6 +60,18 @@ class Configure extends React.Component { Enable pre-moderation + + + + + + {/* diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index bfccf7cfc..004f5b642 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -7,6 +7,7 @@ import { } from '../../coral-framework'; import {connect} from 'react-redux'; import CommentBox from '../../coral-plugin-commentbox/CommentBox'; +import InfoBox from '../../coral-plugin-infobox/InfoBox'; import Content from '../../coral-plugin-commentcontent/CommentContent'; import PubDate from '../../coral-plugin-pubdate/PubDate'; import Count from '../../coral-plugin-comment-count/CommentCount'; @@ -100,6 +101,9 @@ class CommentStream extends Component { rootItem ?
+ diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index e1c730bfc..08b1d8041 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -49,6 +49,17 @@ hr { font-weight: bold; } +/* Info Box Styles */ +.coral-plugin-infobox-info { + position: fixed; + bottom: 0; + border: 0; + background: rgb(105,105,105); + color: white; + border-radius: 2px; + font-weight: bold; +} + /* Comment Box Styles */ .coral-plugin-commentbox-container { display: flex; diff --git a/client/coral-plugin-infobox/InfoBox.js b/client/coral-plugin-infobox/InfoBox.js new file mode 100644 index 000000000..cae80df88 --- /dev/null +++ b/client/coral-plugin-infobox/InfoBox.js @@ -0,0 +1,11 @@ +import React from 'react'; +const packagename = 'coral-plugin-infobox'; + +const InfoBox = ({enable, content}) => +; + +export default InfoBox; diff --git a/client/coral-plugin-infobox/__tests__/infoBox.spec.js b/client/coral-plugin-infobox/__tests__/infoBox.spec.js new file mode 100644 index 000000000..b8b761489 --- /dev/null +++ b/client/coral-plugin-infobox/__tests__/infoBox.spec.js @@ -0,0 +1,26 @@ +import React from 'react'; +import {shallow} from 'enzyme'; +import {expect} from 'chai'; +import InfoBox from '../InfoBox'; + +describe('InfoBox', () => { + let comment; + let render; + beforeEach(() => { + comment = {}; + const postItem = (item) => { + comment.posted = item; + return Promise.resolve(4); + }; + render = shallow( comment.text = e.target.value} + item_id={'1'} + comments={['1', '2', '3']}/>); + }); + + it('should render the InfoBox appropriately', () => { + expect(render.contains('
{ beforeEach(() => { - const defaults = {id: 1, moderation: 'pre'}; + const defaults = {id: 1}; return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); }); @@ -18,13 +18,21 @@ describe('Setting: model', () => { expect(settings).to.have.property('moderation').and.to.equal('pre'); }); }); + it('should have two infoBox fields defined', () => { + return Setting.getSettings().then(settings => { + expect(settings).to.have.property('infoBoxEnable').and.to.equal(false); + expect(settings).to.have.property('infoBoxContent').and.to.equal(''); + }); + }); }); describe('#updateSettings()', () => { it('should update the settings with a passed object', () => { - const mockSettings = {moderation: 'post'}; + const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'}; return Setting.updateSettings(mockSettings).then(updatedSettings => { expect(updatedSettings).to.have.property('moderation').and.to.equal('post'); + expect(updatedSettings).to.have.property('infoBoxEnable', true); + expect(updatedSettings).to.have.property('infoBoxContent', 'yeah'); }); }); }); From a0accfd993e3fd65e6cbe431a3c90d943bff0066 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Tue, 15 Nov 2016 13:26:39 -0500 Subject: [PATCH 20/33] Fixed infoBoxEnabled name --- client/coral-admin/src/containers/Configure.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 0dff4cf9a..416ef8f7b 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -37,13 +37,13 @@ class Configure extends React.Component { } updateInfoBoxEnable () { - const infoboxEnable = this.props.settings.infoBoxEnable; - this.props.dispatch(updateSettings({infoboxEnable})); + const infoBoxEnable = !this.props.settings.infoBoxEnable; + this.props.dispatch(updateSettings({infoBoxEnable})); } updateInfoBoxContent () { - const infoboxContent = this.props.settings.infoBoxContent; - this.props.dispatch(updateSettings({infoboxContent})); + const infoBoxContent = this.props.settings.infoBoxContent; + this.props.dispatch(updateSettings({infoBoxContent})); } saveSettings () { From fbf27a9d75f849f74c20e4ff080715b255cb5b8a Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 15 Nov 2016 14:24:38 -0500 Subject: [PATCH 21/33] Resolving 500 error when deleting an action. --- client/coral-framework/store/actions/items.js | 3 ++- models/comment.js | 8 ++++---- routes/api/comments/index.js | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 2e7dca70f..25aa5d54e 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -248,7 +248,8 @@ export function deleteAction (item_id, action_type, user_id, item_type) { return () => { const action = { action_type, - user_id + user_id, + item_id }; return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action)) diff --git a/models/comment.js b/models/comment.js index 492c06e9c..d17e98860 100644 --- a/models/comment.js +++ b/models/comment.js @@ -194,12 +194,12 @@ CommentSchema.statics.removeById = function(id) { * @param {String} action_type the type of the action to be removed * @param {String} user_id the id of the user performing the action */ -CommentSchema.statics.removeAction = function(id, user_id, action_type) { +CommentSchema.statics.removeAction = function(item_id, user_id, action_type) { return Action.remove({ - action_type: action_type, + action_type, item_type: 'comment', - item_id: id, - user_id: user_id + item_id, + user_id }); }; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 4954563cb..05ad75b72 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -135,9 +135,9 @@ router.delete('/:comment_id', (req, res, next) => { router.delete('/:comment_id/actions', (req, res, next) => { Comment - .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) + .removeAction(req.params.item_id, req.body.user_id, req.body.action_type) .then(() => { - res.status(201).sent({}); + res.status(201).send({}); }) .catch(error => { next(error); From d1ea85468e1bb5128d31ecfc3366fd23512aa139 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 15 Nov 2016 14:46:08 -0500 Subject: [PATCH 22/33] Addressing issue when deleting actions. --- client/coral-framework/store/actions/items.js | 3 +-- routes/api/comments/index.js | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 25aa5d54e..2e7dca70f 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -248,8 +248,7 @@ export function deleteAction (item_id, action_type, user_id, item_type) { return () => { const action = { action_type, - user_id, - item_id + user_id }; return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action)) diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 05ad75b72..99957e987 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -134,8 +134,9 @@ router.delete('/:comment_id', (req, res, next) => { }); router.delete('/:comment_id/actions', (req, res, next) => { + console.log(req.params); Comment - .removeAction(req.params.item_id, req.body.user_id, req.body.action_type) + .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) .then(() => { res.status(201).send({}); }) From 721c0413321a44554f87304a4b57499a52cc6ebe Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 15 Nov 2016 13:33:02 -0700 Subject: [PATCH 23/33] Added fix for webpack building --- package.json | 24 +++++++----------------- webpack.config.dev.js | 2 +- webpack.config.js | 6 ------ 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index a36fcbd3a..354340ae1 100644 --- a/package.json +++ b/package.json @@ -5,25 +5,20 @@ "main": "app.js", "scripts": { "start": "./bin/www", - "build": "webpack --config webpack.config.js --bail", - "build-watch": "webpack --config webpack.config.dev.js --watch", + "build": "NODE_ENV=production webpack --config webpack.config.js --bail", + "build-watch": "NODE_ENV=development webpack --config webpack.config.dev.js --watch", "lint": "eslint bin/* .", "lint-fix": "eslint . --fix", "pretest": "npm install", "test": "mocha --compilers js:babel-core/register --recursive tests", "test-watch": "mocha --compilers js:babel-core/register --recursive -w tests", - "embed-start": "npm run build && ./bin/www" + "embed-start": "NODE_ENV=development npm run build && ./bin/www" }, "config": { "pre-git": { "commit-msg": [], - "pre-commit": [ - "npm run lint", - "npm test" - ], - "pre-push": [ - "npm test" - ], + "pre-commit": ["npm run lint", "npm test"], + "pre-push": ["npm test"], "post-commit": [], "post-merge": [] } @@ -32,12 +27,7 @@ "type": "git", "url": "git+https://github.com/coralproject/talk.git" }, - "keywords": [ - "talk", - "coral", - "coralproject", - "ask" - ], + "keywords": ["talk", "coral", "coralproject", "ask"], "author": "", "license": "Apache-2.0", "bugs": { @@ -54,7 +44,6 @@ "mongoose": "^4.6.5", "morgan": "^1.7.0", "prompt": "^1.0.0", - "react-mdl-selectfield": "^0.2.0", "uuid": "^2.0.3" }, "devDependencies": { @@ -104,6 +93,7 @@ "react": "15.3.2", "react-dom": "15.3.2", "react-mdl": "^1.7.2", + "react-mdl-selectfield": "^0.2.0", "react-onclickoutside": "^5.7.1", "react-redux": "^4.4.5", "react-router": "^3.0.0", diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 8e2f253e5..571c31625 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -85,7 +85,7 @@ module.exports = { }), new webpack.DefinePlugin({ 'process.env': { - 'NODE_ENV': `"${'development'}"`, + 'NODE_ENV': `"${process.env.NODE_ENV}"`, 'VERSION': `"${require('./package.json').version}"` } }) diff --git a/webpack.config.js b/webpack.config.js index bf602733f..46cd2961b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,12 +5,6 @@ const devConfig = require('./webpack.config.dev'); devConfig.devtool = null; devConfig.plugins = devConfig.plugins.concat([ - new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': `"${'production'}"`, - 'VERSION': `"${require('./package.json').version}"` - } - }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false From 399ba72e0e24ee4a353897290406db16f3abf2a9 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 15:15:41 -0800 Subject: [PATCH 24/33] It changes translations to json file. It adds infobox setting. --- client/coral-admin/src/components/Comment.js | 2 +- .../src/components/ModerationKeysModal.js | 2 +- .../src/containers/Community/Community.js | 2 +- .../coral-admin/src/containers/Configure.css | 2 + .../coral-admin/src/containers/Configure.js | 21 ++++++--- .../src/containers/ModerationQueue.js | 4 +- client/coral-admin/src/translations.js | 47 ------------------- client/coral-admin/src/translations.json | 47 +++++++++++++++++++ 8 files changed, 69 insertions(+), 58 deletions(-) delete mode 100644 client/coral-admin/src/translations.js create mode 100644 client/coral-admin/src/translations.json diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 2b0b7561b..c115bcf61 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -4,7 +4,7 @@ import {Button, Icon} from 'react-mdl'; import timeago from 'timeago.js'; import styles from './CommentList.css'; import I18n from 'coral-framework/i18n/i18n'; -import translations from '../translations'; +import translations from '../translations.json'; // Render a single comment for the list export default props => ( diff --git a/client/coral-admin/src/components/ModerationKeysModal.js b/client/coral-admin/src/components/ModerationKeysModal.js index 5cf9fe17e..d350c74e0 100644 --- a/client/coral-admin/src/components/ModerationKeysModal.js +++ b/client/coral-admin/src/components/ModerationKeysModal.js @@ -1,5 +1,5 @@ import I18n from 'coral-framework/i18n/i18n'; -import translations from '../translations'; +import translations from '../translations.json'; import React from 'react'; import Modal from 'components/Modal'; import styles from './ModerationKeysModal.css'; diff --git a/client/coral-admin/src/containers/Community/Community.js b/client/coral-admin/src/containers/Community/Community.js index 8e0b955a4..1f009429b 100644 --- a/client/coral-admin/src/containers/Community/Community.js +++ b/client/coral-admin/src/containers/Community/Community.js @@ -1,6 +1,6 @@ import React from 'react'; import I18n from 'coral-framework/i18n/i18n'; -import translations from '../../translations'; +import translations from '../../translations.json'; import {Grid, Cell} from 'react-mdl'; import styles from './Community.css'; diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index c832e96c1..a70e68746 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -27,6 +27,8 @@ border: 1px solid #ccc; border-radius: 4px; margin-bottom: 10px; + cursor: pointer; + width: auto; } .configSettingEmbed { diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 416ef8f7b..bafe335ab 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -14,7 +14,7 @@ import { } from 'react-mdl'; import styles from './Configure.css'; import I18n from 'coral-framework/i18n/i18n'; -import translations from '../translations'; +import translations from '../translations.json'; class Configure extends React.Component { constructor (props) { @@ -24,6 +24,8 @@ class Configure extends React.Component { this.copyToClipBoard = this.copyToClipBoard.bind(this); this.updateModeration = this.updateModeration.bind(this); + this.updateInfoBoxEnable = this.updateInfoBoxEnable.bind(this); + this.updateInfoBoxContent = this.updateInfoBoxContent.bind(this); this.saveSettings = this.saveSettings.bind(this); } @@ -41,8 +43,8 @@ class Configure extends React.Component { this.props.dispatch(updateSettings({infoBoxEnable})); } - updateInfoBoxContent () { - const infoBoxContent = this.props.settings.infoBoxContent; + updateInfoBoxContent (event) { + const infoBoxContent = event.target.value; this.props.dispatch(updateSettings({infoBoxContent})); } @@ -60,17 +62,24 @@ class Configure extends React.Component { Enable pre-moderation - + + Include Comment Stream Description for Readers. + Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc. + + + + rows={3} + style={{width: '100%'}}/> {/* diff --git a/client/coral-admin/src/containers/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue.js index 9178bda57..cfc48ae6b 100644 --- a/client/coral-admin/src/containers/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue.js @@ -6,7 +6,7 @@ import {updateStatus} from 'actions/comments'; import styles from './ModerationQueue.css'; import key from 'keymaster'; import I18n from 'coral-framework/i18n/i18n'; -import translations from '../translations'; +import translations from '../translations.json'; /* * Renders the moderation queue as a tabbed layout with 3 moderation @@ -91,7 +91,7 @@ class ModerationQueue extends React.Component { commentIds={ comments .get('ids') - .filter(id => + .filter(id => comments .get('byId') .get(id) diff --git a/client/coral-admin/src/translations.js b/client/coral-admin/src/translations.js deleted file mode 100644 index 67a8142fd..000000000 --- a/client/coral-admin/src/translations.js +++ /dev/null @@ -1,47 +0,0 @@ -export default { - en: { - 'community': { - username_and_email: 'Username and Email', - account_creation_date: 'Account Creation Date' - }, - 'modqueue': { - 'pending': 'pending', - 'rejected': 'rejected', - 'flagged': 'flagged', - 'shortcuts': 'Shortcuts', - 'close': 'Close', - 'actions': 'Actions', - 'navigation': 'Navigation', - 'approve': 'Approve comment', - 'reject': 'Reject comment', - 'nextcomment': 'Go to the next comment', - 'prevcomment': 'Go to the previous comment', - 'singleview': 'Toggle single comment edit view', - 'thismenu': 'Open this menu' - }, - 'comment': { - 'flagged': 'flagged', - 'anon': 'Anonymous' - }, - 'embedlink': { - 'copy': 'Copy to Clipboard' - } - }, - es: { - 'community': { - username_and_email: 'Usuario y E-mail', - account_creation_date: 'Fecha de creación de la cuenta' - }, - 'modqueue': { - 'pending': 'pendiente', - 'rejected': 'rechazado', - 'flagged': 'marcado', - 'shortcuts': 'Atajos de teclado', - 'close': 'Cerrar' - }, - 'comment': { - 'flagged': 'marcado', - 'anon': 'Anónimo' - } - } -}; diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json new file mode 100644 index 000000000..43d0939e0 --- /dev/null +++ b/client/coral-admin/src/translations.json @@ -0,0 +1,47 @@ +{ + "en": { + "community": { + "username_and_email": "Username and Email", + "account_creation_date": "Account Creation Date" + }, + "modqueue": { + "pending": "pending", + "rejected": "rejected", + "flagged": "flagged", + "shortcuts": "Shortcuts", + "close": "Close", + "actions": "Actions", + "navigation": "Navigation", + "approve": "Approve comment", + "reject": "Reject comment", + "nextcomment": "Go to the next comment", + "prevcomment": "Go to the previous comment", + "singleview": "Toggle single comment edit view", + "thismenu": "Open this menu" + }, + "comment": { + "flagged": "flagged", + "anon": "Anonymous" + }, + "embedlink": { + "copy": "Copy to Clipboard" + } + }, + "es": { + "community": { + "username_and_email": "Usuario y E-mail", + "account_creation_date": "Fecha de creación de la cuenta" + }, + "modqueue": { + "pending": "pendiente", + "rejected": "rechazado", + "flagged": "marcado", + "shortcuts": "Atajos de teclado", + "close": "Cerrar" + }, + "comment": { + "flagged": "marcado", + "anon": "Anónimo" + } + } +} From d24c7c0bc28dca666f46a21cba16fe6874bc3ae4 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 15:18:17 -0800 Subject: [PATCH 25/33] Struggling with hiding elements. --- client/coral-embed-stream/src/CommentStream.js | 4 ++-- client/coral-embed-stream/style/default.css | 6 ++++++ client/coral-plugin-infobox/InfoBox.js | 3 +-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 004f5b642..d8f5718a7 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -102,8 +102,8 @@ class CommentStream extends Component { ?
+ content={this.props.config.infoBoxContent} + enable={this.props.config.infoBoxEnable}/> diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 08b1d8041..9875198cb 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -58,6 +58,12 @@ hr { color: white; border-radius: 2px; font-weight: bold; + display: block; +} + +.hidden { + visibility: hidden; + display: none; } /* Comment Box Styles */ diff --git a/client/coral-plugin-infobox/InfoBox.js b/client/coral-plugin-infobox/InfoBox.js index cae80df88..2aaa2f486 100644 --- a/client/coral-plugin-infobox/InfoBox.js +++ b/client/coral-plugin-infobox/InfoBox.js @@ -3,8 +3,7 @@ const packagename = 'coral-plugin-infobox'; const InfoBox = ({enable, content}) => ; From 3abc0692bbd85995e71aa5ec314e23f80e67de44 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 15:31:12 -0800 Subject: [PATCH 26/33] At the top, not the bottom. --- client/coral-embed-stream/style/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 793ab0f8c..2f7394d4d 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -52,7 +52,7 @@ hr { /* Info Box Styles */ .coral-plugin-infobox-info { position: fixed; - bottom: 0; + top: 0; border: 0; background: rgb(105,105,105); color: white; From 621550e261e8676f278088b9562e11897abd326e Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 15:31:54 -0800 Subject: [PATCH 27/33] Null, not empty string. --- client/coral-plugin-infobox/InfoBox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-plugin-infobox/InfoBox.js b/client/coral-plugin-infobox/InfoBox.js index 2aaa2f486..db2ea22c4 100644 --- a/client/coral-plugin-infobox/InfoBox.js +++ b/client/coral-plugin-infobox/InfoBox.js @@ -3,7 +3,7 @@ const packagename = 'coral-plugin-infobox'; const InfoBox = ({enable, content}) =>
+ className={`${packagename}-info ${enable ? null : ', hidden'}` }> {content}
; From ff2c3cf30b6fa2cc822194e582912f8596ee7bcb Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 15 Nov 2016 20:31:24 -0800 Subject: [PATCH 28/33] Hide the textfield when not enable. --- client/coral-admin/src/containers/Configure.css | 4 ++++ client/coral-admin/src/containers/Configure.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index a70e68746..7141059b1 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -61,3 +61,7 @@ font-size: 14px; letter-spacing: 0.03em; } + +.hidden { + display: none; +} diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index bafe335ab..7d33e8e5a 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -72,7 +72,7 @@ class Configure extends React.Component { Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc. - + Date: Wed, 16 Nov 2016 03:07:27 -0800 Subject: [PATCH 29/33] It adds translations for the admin and fix CSS. --- client/coral-admin/src/components/Header.js | 12 +++-- .../coral-admin/src/components/ui/Drawer.js | 10 ++-- .../coral-admin/src/components/ui/Header.js | 10 ++-- .../coral-admin/src/containers/Configure.css | 7 +++ .../coral-admin/src/containers/Configure.js | 49 +++++++------------ client/coral-admin/src/translations.json | 26 ++++++++++ 6 files changed, 74 insertions(+), 40 deletions(-) diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js index 1a89da513..b2bee0a44 100644 --- a/client/coral-admin/src/components/Header.js +++ b/client/coral-admin/src/components/Header.js @@ -2,22 +2,26 @@ import React from 'react'; import {Layout, Navigation, Drawer, Header} from 'react-mdl'; import {Link} from 'react-router'; import styles from './Header.css'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from '../translations.json'; // App header. If we add a navbar it should be here export default (props) => (
- Moderate - Configure + {lang.t('configure.moderate')} + {lang.t('Configure')}
- Moderate - Configure + {lang.t('configure.moderate')} + {lang.t('configure.Configure')} {props.children}
); + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/components/ui/Drawer.js b/client/coral-admin/src/components/ui/Drawer.js index 9ea727911..12bf7c1d2 100644 --- a/client/coral-admin/src/components/ui/Drawer.js +++ b/client/coral-admin/src/components/ui/Drawer.js @@ -2,13 +2,17 @@ import React from 'react'; import {Navigation, Drawer} from 'react-mdl'; import {Link} from 'react-router'; import styles from './Header.css'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from '../../translations.json'; export default () => ( - Moderate - Community - Configure + {lang.t('configure.moderate')} + {lang.t('configure.community')} + {lang.t('configure.configure')} ); + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 86e622672..d85a91744 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -2,16 +2,20 @@ import React from 'react'; import {Navigation, Header} from 'react-mdl'; import {Link} from 'react-router'; import styles from './Header.css'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from '../../translations.json'; export default () => (
- Moderate - Community - Configure + {lang.t('configure.moderate')} + {lang.t('configure.community')} + {lang.t('configure.configure')} {`v${process.env.VERSION}`}
); + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index 7141059b1..8d1fc49a2 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -29,6 +29,13 @@ margin-bottom: 10px; cursor: pointer; width: auto; + height: auto; + text-align: left; +} + +.configSettingInfoBox p { + font-size: 12px; + bottom: 0; } .configSettingEmbed { diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 7d33e8e5a..7d057eacc 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -60,7 +60,7 @@ class Configure extends React.Component { onClick={this.updateModeration} checked={this.props.settings.moderation === 'pre'} /> - Enable pre-moderation + {lang.t('configure.enable-pre-moderation')}
@@ -68,33 +68,22 @@ class Configure extends React.Component { onClick={this.updateInfoBoxEnable} checked={this.props.settings.infoBoxEnable} /> - Include Comment Stream Description for Readers. - Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc. + + {lang.t('configure.include-comment-stream')} +

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

- + + + - {/* - - - Include Comment Stream Description for Readers - - - - Limit Comment Length - - - */} ; } @@ -115,7 +104,7 @@ class Configure extends React.Component { return -

Copy and paste code below into your CMS to embed your comment box in your articles

+

{lang.t('configure.copy-and-paste')}