diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index 9f4dd4715..1457601ed 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -34,7 +34,7 @@ export default store => next => action => { // Get comments to fill each of the three lists on the mod queue const fetchModerationQueueComments = store => -Promise.all([fetch('/api/v1/comments/status/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')]) +Promise.all([fetch('/api/v1/queue/comments/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')]) .then(res => Promise.all(res.map(r => r.json()))) .then(res => { res[2] = res[2].map(comment => { comment.flagged = true; return comment; }); diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index d6edfddda..bfccf7cfc 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 PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; const {addItem, updateItem, postItem, getStream, postAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; @@ -131,6 +132,9 @@ class CommentStream extends Component { + + ; }) diff --git a/client/coral-plugin-permalinks/PermalinkButton.js b/client/coral-plugin-permalinks/PermalinkButton.js new file mode 100644 index 000000000..978a6dd03 --- /dev/null +++ b/client/coral-plugin-permalinks/PermalinkButton.js @@ -0,0 +1,94 @@ +import React, {PropTypes} from 'react'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from './translations'; +import onClickOutside from 'react-onclickoutside'; +const name = 'coral-plugin-permalinks'; + +const lang = new I18n(translations); + +class PermalinkButton extends React.Component { + + static propTypes = { + asset_id: PropTypes.string.isRequired, + comment_id: PropTypes.string.isRequired + } + + constructor (props) { + super(props); + this.state = {popoverOpen: false, copySuccessful: null, copyFailure: null}; + this.toggle = this.toggle.bind(this); + this.copyPermalink = this.copyPermalink.bind(this); + } + + toggle () { + this.setState({popoverOpen: !this.state.popoverOpen}); + } + + handleClickOutside () { + this.setState({popoverOpen: false}); + } + + copyPermalink () { + this.permalinkInput.select(); + try { + document.execCommand('copy'); + this.setState({copySuccessful: true}); + } catch (err) { + this.setState({copyFailure: true}); + } + + setTimeout(() => { + this.setState({copyFailure: null, copySuccessful: null}); + }, 4500); + } + + render () { + const publisherUrl = `${location.protocol}//${location.host}/`; + + return ( +
+ +
+ this.permalinkInput = input} + value={`${publisherUrl}${this.props.asset_id}#${this.props.comment_id}`} + onChange={() => {}} /> + + { + this.state.copySuccessful ?

copied to clipboard

: null + } + { + this.state.copyFailure + ?

copying to clipboard not supported in this browser. Use Cmd + C.

+ : null + } +
+
+ ); + } +} + +export default onClickOutside(PermalinkButton); + +const styles = { + position: 'relative', + + popover: active => { + return { + display: active ? 'block' : 'none', + backgroundColor: 'white', + border: '1px solid black', + minWidth: 400, + position: 'absolute', + top: 30, + right: 0, + padding: 5 + }; + } +}; diff --git a/client/coral-plugin-permalinks/translations.json b/client/coral-plugin-permalinks/translations.json new file mode 100644 index 000000000..af5c19b0a --- /dev/null +++ b/client/coral-plugin-permalinks/translations.json @@ -0,0 +1,12 @@ +{ + "en": { + "permalink": { + "permalink": "Permalink" + } + }, + "es": { + "permalink": { + "permalink": "Enlace permanente" + } + } +} diff --git a/package.json b/package.json index 0de0b8607..6312e51ff 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "react": "15.3.2", "react-dom": "15.3.2", "react-mdl": "^1.7.2", + "react-onclickoutside": "^5.7.1", "react-redux": "^4.4.5", "react-router": "^3.0.0", "redux": "^3.6.0", diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 6492f5968..ea142c35d 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -1,8 +1,6 @@ const express = require('express'); const Comment = require('../../../models/comment'); -const Setting = require('../../../models/setting'); - const router = express.Router(); //============================================================================== @@ -25,11 +23,7 @@ router.get('/:comment_id', (req, res, next) => { .catch(next); }); -//============================================================================== -// Moderation Queues Routes -//============================================================================== - -// Get all the comments that have that action_type over them. +// Get all the comments that have an action_type over them. router.get('/action/:action_type', (req, res, next) => { Comment .findByActionType(req.params.action_type) @@ -41,34 +35,30 @@ router.get('/action/:action_type', (req, res, next) => { // Get all the comments that were rejected. router.get('/status/rejected', (req, res, next) => { - Comment - .findByStatus('rejected') - .then(comments => { - res.status(200).json(comments); - }) - .catch(next); + Comment.findByStatus('rejected').then(comments => { + res.status(200).json(comments); + }) + .catch(next); }); -// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated, -// depending on the settings. The :moderation overwrites this settings. -// 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}) => { - let moderationValue = req.query.moderation; - if (typeof moderationValue === 'undefined' || moderationValue === undefined) { - moderationValue = moderation; - } - Comment - .moderationQueue(moderationValue) - .then((comments) => { - res.status(200).json(comments); - }); - }) - .catch(next); +// Get all the comments that were accepted. +router.get('/status/accepted', (req, res, next) => { + Comment.findByStatus('accepted').then((comments) => { + res.status(200).json(comments); + }) + .catch(error => { + next(error); + }); +}); + +// Get all the not moderated comments. +router.get('/status/new', (req, res, next) => { + Comment.findByStatus('').then((comments) => { + res.status(200).json(comments); + }) + .catch(error => { + next(error); + }); }); //============================================================================== @@ -76,9 +66,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) => { diff --git a/routes/api/index.js b/routes/api/index.js index 3e149f9e8..c49e52c9d 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -5,6 +5,7 @@ const router = express.Router(); router.use('/asset', require('./asset')); router.use('/auth', require('./auth')); router.use('/comments', require('./comments')); +router.use('/queue', require('./queue')); router.use('/settings', require('./settings')); router.use('/stream', require('./stream')); router.use('/user', require('./user')); diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js new file mode 100644 index 000000000..f8ab95d6b --- /dev/null +++ b/routes/api/queue/index.js @@ -0,0 +1,27 @@ +const express = require('express'); +const Comment = require('../../../models/comment'); + +const Setting = require('../../../models/setting'); + +const router = express.Router(); + +//============================================================================== +// Get Routes +//============================================================================== + +// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated, +// depending on the settings. The :moderation overwrites this settings. +// 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('/comments/pending', (req, res, next) => { + Setting.getModerationSetting().then(function({moderation}){ + Comment.moderationQueue(moderation).then((comments) => { + res.status(200).json(comments); + }); + }) + .catch(error => { + next(error); + }); +}); + +module.exports = router; diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 900cdac37..cde4b9efb 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -75,7 +75,7 @@ describe('Get /comments', () => { }); }); -describe('Get moderation queues rejected, pending, flags', () => { +describe('Get comments by status and action', () => { const comments = [{ id: 'abc', body: 'comment 10', @@ -133,9 +133,20 @@ describe('Get moderation queues rejected, pending, flags', () => { }); }); - it('should return all the pending comments', function(done){ + it('should return all the approved comments', function(done){ chai.request(app) - .get('/api/v1/comments/status/pending') + .get('/api/v1/comments/status/accepted') + .end(function(err, res){ + expect(err).to.be.null; + expect(res).to.have.status(200); + expect(res.body[0]).to.have.property('id', 'hij'); + done(); + }); + }); + + it('should return all the new comments', function(done){ + chai.request(app) + .get('/api/v1/comments/status/new') .end(function(err, res){ expect(err).to.be.null; expect(res).to.have.status(200); @@ -144,37 +155,15 @@ describe('Get moderation queues rejected, pending, flags', () => { }); }); - it('should return all the pending comments as pre moderated', function(done){ + it('should return all the flagged comments', function(done){ chai.request(app) - .get('/api/v1/comments/status/pending') - .query({'moderation': 'pre'}) - .end(function(err, res){ - expect(err).to.be.null; - expect(res).to.have.status(200); - expect(res.body[0]).to.have.property('id', 'def'); - done(); - }); - }); - - it('should return all the pending comments as post moderated', function(done){ - chai.request(app) - .get('/api/v1/comments/status/pending') - .query({'moderation': 'post'}) - .end(function(err, res){ - expect(err).to.be.null; - expect(res).to.have.status(200); - expect(res.body).to.have.lengthOf(0); - done(); - }); - }); - - it('should return all the flagged comments', () => { - return chai.request(app) .get('/api/v1/comments/action/flag') - .then((res) => { + .end(function(err, res){ expect(res).to.have.status(200); + expect(err).to.be.null; expect(res.body.length).to.equal(1); expect(res.body[0]).to.have.property('id', 'abc'); + done(); }); }); }); @@ -390,6 +379,11 @@ describe('Remove /:comment_id', () => { }); }); +process.on('unhandledRejection', (reason) => { + console.error('Reason: '); + console.error(reason); +}); + describe('Post /:comment_id/status', () => { const comments = [{ diff --git a/tests/routes/api/queue/index.js b/tests/routes/api/queue/index.js new file mode 100644 index 000000000..f21fe1331 --- /dev/null +++ b/tests/routes/api/queue/index.js @@ -0,0 +1,81 @@ +process.env.NODE_ENV = 'test'; + +require('../../../utils/mongoose'); + +const app = require('../../../../app'); +const chai = require('chai'); +const expect = chai.expect; + +// Setup chai. +chai.should(); +chai.use(require('chai-http')); + +const Comment = require('../../../../models/comment'); +const Action = require('../../../../models/action'); +const User = require('../../../../models/user'); + +const Setting = require('../../../../models/setting'); +const settings = {id: '1', moderation: 'pre'}; + +beforeEach(() => { + return Setting.create(settings); +}); + +describe('Get moderation queues rejected, pending, flags', () => { + const comments = [{ + id: 'abc', + body: 'comment 10', + asset_id: 'asset', + author_id: '123', + status: 'rejected' + }, { + id: 'def', + body: 'comment 20', + asset_id: 'asset', + author_id: '456' + }, { + id: 'hij', + body: 'comment 30', + asset_id: '456', + status: 'accepted' + }]; + + const users = [{ + displayName: 'Ana', + email: 'ana@gmail.com', + password: '123' + }, { + displayName: 'Maria', + email: 'maria@gmail.com', + password: '123' + }]; + + const actions = [{ + action_type: 'flag', + item_id: 'abc', + item_type: 'comment' + }, { + action_type: 'like', + item_id: 'hij', + item_type: 'comment' + }]; + + beforeEach(() => { + return Promise.all([ + Comment.create(comments), + User.createLocalUsers(users), + Action.create(actions) + ]); + }); + + it('should return all the pending comments', function(done){ + chai.request(app) + .get('/api/v1/queue/comments/pending') + .end(function(err, res){ + expect(err).to.be.null; + expect(res).to.have.status(200); + expect(res.body[0]).to.have.property('id', 'def'); + done(); + }); + }); +});