From b5a0483603333e3785ad2823c9ee22fa369b2c7a Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 12 Oct 2017 16:58:12 +0100 Subject: [PATCH 001/344] Update nav --- docs/_data/nav.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/_data/nav.yaml b/docs/_data/nav.yaml index eeb7c2ebc..9a205e8fe 100644 --- a/docs/_data/nav.yaml +++ b/docs/_data/nav.yaml @@ -17,6 +17,10 @@ items: children: - title: How Talk Works url: /how-talk-works/ + - title: Commenter Features + url: /commenter-features/ + - title: Moderator Features + url: /moderator-features/ - title: Trust url: /trust/ - title: Toxic Comments From e0e481373bddee6ac0d1fe84dc5f78239d5a8293 Mon Sep 17 00:00:00 2001 From: Facu Date: Thu, 12 Oct 2017 17:59:04 -0300 Subject: [PATCH 002/344] bulk approve/reject actions for moderators --- client/coral-admin/src/actions/userDetail.js | 6 + .../coral-admin/src/components/UserDetail.css | 192 ++++++++++-------- .../coral-admin/src/components/UserDetail.js | 101 +++++---- .../coral-admin/src/constants/userDetail.js | 2 +- .../coral-admin/src/containers/UserDetail.js | 3 + client/coral-admin/src/reducers/userDetail.js | 5 + 6 files changed, 189 insertions(+), 120 deletions(-) diff --git a/client/coral-admin/src/actions/userDetail.js b/client/coral-admin/src/actions/userDetail.js index f8b22008c..3a1de5745 100644 --- a/client/coral-admin/src/actions/userDetail.js +++ b/client/coral-admin/src/actions/userDetail.js @@ -20,3 +20,9 @@ export const toggleSelectCommentInUserDetail = (id, active) => { }; }; +export const toggleSelectAllCommentInUserDetail = (ids, active) => { + return { + type: active ? actions.SELECT_ALL_USER_DETAIL_COMMENT : actions.CLEAR_USER_DETAIL_SELECTIONS, + ids + }; +}; diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index 5cbe0fe32..fbde4a626 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -1,144 +1,168 @@ .copyButton { - background-color: white; - border: solid 1px; - padding: 2px 6px; - height: auto; - line-height: initial; - min-width: auto; - letter-spacing: normal; - font-size: 0.9em; - margin-left: 10px; + background-color: white; + border: solid 1px; + padding: 2px 6px; + height: auto; + line-height: initial; + min-width: auto; + letter-spacing: normal; + font-size: 0.9em; + margin-left: 10px; } .userDetailList { - list-style: none; - padding: 0; - margin: 0; + list-style: none; + padding: 0; + margin: 0; } .userDetailItem { - margin: 0 5px; - font-weight: 500; + margin: 0 5px; + font-weight: 500; } .stats { - display: flex; - list-style: none; - padding: 0; - margin: 0; - text-align: center; - margin: 15px 0 5px; - color: #595959; + display: flex; + list-style: none; + padding: 0; + margin: 0; + text-align: center; + margin: 15px 0 5px; + color: #595959; } .stat { - margin-right: 20px; + margin-right: 20px; } .stat:last-child { - margin-right: 0px; + margin-right: 0px; } .statItem, .statReportResult { - padding: 3px 5px; - background-color: #D8D8D8; - border-radius: 3px; - font-weight: 500; - display: block; - font-size: 0.9em; - line-height: normal; - letter-spacing: 0.4px; - min-width: 60px; + padding: 3px 5px; + background-color: #D8D8D8; + border-radius: 3px; + font-weight: 500; + display: block; + font-size: 0.9em; + line-height: normal; + letter-spacing: 0.4px; + min-width: 60px; } .statResult { - font-size: 1.5em; - padding: 5px 0; - display: inline-block; + font-size: 1.5em; + padding: 5px 0; + display: inline-block; } .statReportResult { - color: white; - margin: 5px 0; - font-weight: 400; + color: white; + margin: 5px 0; + font-weight: 400; } .statReportResult.reliable { - background-color: #749C48; + background-color: #749C48; } .statReportResult.neutral { - background-color: #616161; + background-color: #616161; } .statReportResult.unreliable { - background-color: #F44336; + background-color: #F44336; } .memberSince { - clear: both; + clear: both; } .small { - color: #888888; - font-size: 0.9em; - letter-spacing: 0.4px; + color: #888888; + font-size: 0.9em; + letter-spacing: 0.4px; } .profileEmail { - border: none; - background-color: transparent; - font-size: 16px; - outline: none; - width: calc(100% - 90px); + border: none; + background-color: transparent; + font-size: 16px; + outline: none; + width: calc(100% - 90px); } .commentStatuses { - padding: 10px 0 0 0; - margin: 0; - height: 52px; - list-style: none; - box-sizing: border-box; - - li { - display: inline-block; - margin-right: 10px; - cursor: pointer; - padding: 0 10px; - } + padding: 0 0 0 10px; + margin: 0; + align-self: center; + list-style: none; + box-sizing: border-box; + li { + display: inline-block; + margin-right: 10px; + cursor: pointer; + padding: 0 10px; + } } .active { - font-weight: bold; - border-bottom: 3px solid #F36451; + font-weight: bold; + border-bottom: 3px solid #F36451; } .bulkActionGroup { - height: 52px; - background-color: #efefef; + height: 52px; + background-color: #efefef; + padding: 0 0 0 10px; + display: flex; + i { + margin-right: 0; + } - i { - margin-right: 0; - } + .bulkAction { + display: inline-block; + width: 48px; + height: 48px; + transform: scale(.7); + min-width: 0; + } - .bulkAction { - display: inline-block; - width: 48px; - height: 48px; - transform: scale(.7); - min-width: 0; - } + .bulkAction:last-child { + margin-left: -10px; + } - .bulkAction:last-child { - margin-left: -10px; - } + span{ + align-self: center; + font-weight: 500; + margin-left: 15px; + } } .loadMore > button { - background-color: #696969; + background-color: #696969; - &:hover { - background-color: #404040; - color: white; - } + &:hover { + background-color: #404040; + color: white; + } } + +.toggleAll { + padding: 0 10px 0 0; + align-self: center; +} + +.commentList { + clear: both; +} + +.bulkActionHeader{ + display: flex; + justify-content: space-between; + height: 52px; + &.selected{ + background-color: #efefef; + } +} \ No newline at end of file diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 275503085..ab609fbfb 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Comment from '../containers/UserDetailComment'; import styles from './UserDetail.css'; -import {Icon, Button, Drawer, Spinner} from 'coral-ui'; +import {Icon, Drawer, Spinner} from 'coral-ui'; import {Slot} from 'coral-framework/components'; import ButtonCopyToClipboard from './ButtonCopyToClipboard'; import ClickOutside from 'coral-framework/components/ClickOutside'; @@ -10,6 +10,8 @@ import LoadMore from '../components/LoadMore'; import cn from 'classnames'; import capitalize from 'lodash/capitalize'; import {getReliability} from 'coral-framework/utils/user'; +import ApproveButton from './ApproveButton'; +import RejectButton from './RejectButton'; export default class UserDetail extends React.Component { @@ -23,6 +25,7 @@ export default class UserDetail extends React.Component { toggleSelect: PropTypes.func.isRequired, bulkAccept: PropTypes.func.isRequired, bulkReject: PropTypes.func.isRequired, + toggleSelectAll: PropTypes.func.isRequired } rejectThenReload = async (info) => { @@ -47,6 +50,28 @@ export default class UserDetail extends React.Component { } } + bulkAcceptThenReload = async () => { + try { + await this.props.bulkAccept(); + this.props.data.refetch(); + } catch (err) { + + // TODO: handle error. + console.error(err); + } + } + + bulkRejectThenReload = async () => { + try { + await this.props.bulkReject(); + this.props.data.refetch(); + } catch (err) { + + // TODO: handle error. + console.error(err); + } + } + showAll = () => { this.props.changeStatus('all'); } @@ -78,11 +103,10 @@ export default class UserDetail extends React.Component { activeTab, selectedCommentIds, toggleSelect, - bulkAccept, - bulkReject, hideUserDetail, viewUserDetail, loadMore, + toggleSelectAll } = this.props; let rejectedPercent = (rejectedComments / totalComments) * 100; @@ -100,14 +124,14 @@ export default class UserDetail extends React.Component {
  • - + Member Since: {new Date(user.created_at).toLocaleString()}
  • {user.profiles.map(({id}) =>
  • - + Email: {id}
  • @@ -142,35 +166,42 @@ export default class UserDetail extends React.Component { queryData={{root, user}} /> -
    - { - selectedCommentIds.length === 0 - ? ( -
      -
    • All
    • -
    • Rejected
    • -
    - ) - : ( -
    - - - {`${selectedCommentIds.length} comments selected`} -
    - ) - } - -
    +
    +
    0) ? cn(styles.bulkActionHeader, styles.selected) : styles.bulkActionHeader}> + { + selectedCommentIds.length === 0 + ? ( +
      +
    • All
    • +
    • Rejected
    • +
    + ) + : ( +
    + + + {`${selectedCommentIds.length} comments selected`} +
    + ) + } +
    + 0 && selectedCommentIds.length === nodes.length} + onChange={(e) => { + toggleSelectAll(nodes.map((comment) => comment.id), e.target.checked); + }} /> + +
    +
    +
    { nodes.map((comment) => { const selected = selectedCommentIds.indexOf(comment.id) !== -1; @@ -199,7 +230,7 @@ export default class UserDetail extends React.Component { ); } - render () { + render() { if (this.props.loading) { return this.renderLoading(); } diff --git a/client/coral-admin/src/constants/userDetail.js b/client/coral-admin/src/constants/userDetail.js index b68efb5ea..f1617f5a3 100644 --- a/client/coral-admin/src/constants/userDetail.js +++ b/client/coral-admin/src/constants/userDetail.js @@ -4,4 +4,4 @@ export const CHANGE_USER_DETAIL_STATUSES = 'CHANGE_USER_DETAIL_STATUSES'; export const SELECT_USER_DETAIL_COMMENT = 'SELECT_USER_DETAIL_COMMENT'; export const UNSELECT_USER_DETAIL_COMMENT = 'UNSELECT_USER_DETAIL_COMMENT'; export const CLEAR_USER_DETAIL_SELECTIONS = 'CLEAR_USER_DETAIL_SELECTIONS'; - +export const SELECT_ALL_USER_DETAIL_COMMENT = 'SELECT_ALL_USER_DETAIL_COMMENT'; diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index ffc661150..3feee87a9 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -11,6 +11,7 @@ import { changeUserDetailStatuses, clearUserDetailSelections, toggleSelectCommentInUserDetail, + toggleSelectAllCommentInUserDetail } from 'coral-admin/src/actions/userDetail'; import {withSetCommentStatus} from 'coral-framework/graphql/mutations'; import UserDetailComment from './UserDetailComment'; @@ -120,6 +121,7 @@ class UserDetailContainer extends React.Component { bulkAccept={this.bulkAccept} changeStatus={this.props.changeUserDetailStatuses} toggleSelect={this.props.toggleSelectCommentInUserDetail} + toggleSelectAll={this.props.toggleSelectAllCommentInUserDetail} acceptComment={this.acceptComment} rejectComment={this.rejectComment} loading={loading} @@ -188,6 +190,7 @@ const mapDispatchToProps = (dispatch) => ({ toggleSelectCommentInUserDetail, viewUserDetail, hideUserDetail, + toggleSelectAllCommentInUserDetail }, dispatch) }); diff --git a/client/coral-admin/src/reducers/userDetail.js b/client/coral-admin/src/reducers/userDetail.js index e3c49a0d1..3ad435973 100644 --- a/client/coral-admin/src/reducers/userDetail.js +++ b/client/coral-admin/src/reducers/userDetail.js @@ -41,6 +41,11 @@ export default function banUserDialog(state = initialState, action) { ...state, selectedCommentIds: state.selectedCommentIds.filter((id) => id !== action.id), }; + case actions.SELECT_ALL_USER_DETAIL_COMMENT: + return { + ...state, + selectedCommentIds: action.ids + }; default: return state; } From 9d5739b3960026a8149fc36628eafa1b53bc42f2 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 12 Oct 2017 15:27:57 -0600 Subject: [PATCH 003/344] better support for email verification --- errors.js | 49 +++++++++++++++++---------- routes/api/users/index.js | 48 ++++++++++++++++++-------- services/limit.js | 47 ++++++++++++++++++++++++++ services/passport.js | 2 +- services/users.js | 50 +++++++--------------------- test/server/routes/api/auth/index.js | 4 +-- test/server/services/users.js | 12 ++++--- 7 files changed, 134 insertions(+), 78 deletions(-) create mode 100644 services/limit.js diff --git a/errors.js b/errors.js index ab1667e07..93ebf34b9 100644 --- a/errors.js +++ b/errors.js @@ -97,7 +97,8 @@ class ErrAssetCommentingClosed extends APIError { class ErrAuthentication extends APIError { constructor(message = null) { super('authentication error occured', { - status: 401 + status: 401, + translation_key: 'AUTHENTICATION' }, { message }); @@ -195,31 +196,45 @@ const ErrAssetURLAlreadyExists = new APIError('Asset URL already exists, cannot status: 409 }); +// ErrNotVerified is returned when a user tries to login with valid credentials +// but their email address is not yet verified. +const ErrNotVerified = new APIError('User does not have a verified email address', { + translation_key: 'EMAIL_NOT_VERIFIED', + status: 401, +}); + +const ErrMaxRateLimit = new APIError('Rate limit exeeded', { + translation_key: 'RATE_LIMIT_EXCEEDED', + status: 429, +}); + module.exports = { - ExtendableError, APIError, ErrAlreadyExists, - ErrPasswordTooShort, - ErrSettingsNotInit, + ErrAssetCommentingClosed, + ErrAssetURLAlreadyExists, + ErrAuthentication, + ErrCommentTooShort, + ErrContainsProfanity, + ErrEditWindowHasEnded, + ErrEmailTaken, + ErrInstallLock, + ErrInvalidAssetURL, + ErrLoginAttemptMaximumExceeded, + ErrMaxRateLimit, ErrMissingEmail, ErrMissingPassword, ErrMissingToken, - ErrEmailTaken, - ErrSpecialChars, ErrMissingUsername, - ErrContainsProfanity, - ErrUsernameTaken, - ErrAssetCommentingClosed, - ErrNotFound, - ErrInvalidAssetURL, - ErrAuthentication, ErrNotAuthorized, + ErrNotFound, + ErrNotVerified, + ErrPasswordTooShort, ErrPermissionUpdateUsername, ErrSameUsernameProvided, ErrSettingsInit, - ErrInstallLock, - ErrLoginAttemptMaximumExceeded, - ErrEditWindowHasEnded, - ErrCommentTooShort, - ErrAssetURLAlreadyExists + ErrSettingsNotInit, + ErrSpecialChars, + ErrUsernameTaken, + ExtendableError, }; diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 189d0ea3e..d076288e6 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -5,6 +5,7 @@ const mailer = require('../../../services/mailer'); const errors = require('../../../errors'); const authorization = require('../../../middleware/authorization'); const i18n = require('../../../services/i18n'); +const Limit = require('../../../services/limit'); const { ROOT_URL } = require('../../../config'); @@ -109,16 +110,15 @@ router.post('/:user_id/email', authorization.needed('ADMIN', 'MODERATOR'), async /** * SendEmailConfirmation sends a confirmation email to the user. - * @param {ExpressApp} app the instance of the express app * @param {String} userID the id for the user to send the email to * @param {String} email the email for the user to send the email to */ -const SendEmailConfirmation = async (app, userID, email, referer) => { - let token = await UsersService.createEmailConfirmToken(userID, email, referer); +const SendEmailConfirmation = async (user, email, referer) => { + let token = await UsersService.createEmailConfirmToken(user, email, referer); return mailer.sendSimple({ - template: 'email-confirm', // needed to know which template to render! - locals: { // specifies the template locals. + template: 'email-confirm', + locals: { token, rootURL: ROOT_URL, email @@ -138,7 +138,7 @@ router.post('/', async (req, res, next) => { // Send an email confirmation. The Front end will know about the // requireEmailConfirmation as it's included in the settings get endpoint. - await SendEmailConfirmation(req.app, user.id, email, redirectUri); + await SendEmailConfirmation(user, email, redirectUri); res.status(201).json(user); } catch (e) { @@ -166,25 +166,45 @@ router.post('/:user_id/actions', authorization.needed(), async (req, res, next) } }); +// This will allow 1 try every minute. +const resendRateLimiter = new Limit('/api/v1/users/resend-verify', 1, '1m'); + // trigger an email confirmation re-send by a new user router.post('/resend-verify', async (req, res, next) => { - const {email} = req.body; const redirectUri = req.header('X-Pym-Url') || req.header('Referer'); + let {email = ''} = req.body; - if (!email) { + // Clean up and validate the email. + email = email.toLowerCase().trim(); + if (email.length < 5) { return next(errors.ErrMissingEmail); } + // Check if we're past the rate limit, if we are, stop now. Otherwise, record + // this as an attempt to send a verification email. try { + const tries = await resendRateLimiter.get(email); + if (tries > 0) { + throw errors.ErrMaxRateLimit; + } - // find user by email. - // if the local profile is verified, return an error code? - // send a 204 after the email is re-sent - await SendEmailConfirmation(req.app, null, email, redirectUri); + await resendRateLimiter.test(email); + } catch (err) { + return next(err); + } + + try { + const user = await UsersService.findLocalUser(email); + if (!user) { + throw errors.ErrNotFound; + } + + await SendEmailConfirmation(user, email, redirectUri); res.status(204).end(); } catch (e) { - return next(e); + console.trace(e); + res.status(204).end(); } }); @@ -208,7 +228,7 @@ router.post('/:user_id/email/confirm', authorization.needed('ADMIN', 'MODERATOR' } // Send the email to the first local profile that was found. - await SendEmailConfirmation(req.app, user.id, localProfile.id); + await SendEmailConfirmation(user, localProfile.id); res.status(204).end(); } catch (e) { diff --git a/services/limit.js b/services/limit.js new file mode 100644 index 000000000..7acc78f0c --- /dev/null +++ b/services/limit.js @@ -0,0 +1,47 @@ +const ms = require('ms'); +const errors = require('../errors'); +const {createClientFactory} = require('./redis'); +const client = createClientFactory(); + +class Limit { + constructor(prefix, max, duration) { + this.ttl = ms(duration) / 1000; + this.prefix = prefix; + this.max = max; + } + + key(value) { + return `limit[${this.prefix}][${value}]`; + } + + async get(value) { + const key = this.key(value); + + return client().get(key); + } + + async test(value) { + const key = this.key(value); + + const [[, tries], [, expiry]] = await client() + .multi() + .incr(key) + .expire(key, this.ttl) + .exec(); + + // if this is new or has no expiry + if (tries === 1 || expiry === -1) { + + // then expire it after the timeout + client().expire(key, this.ttl); + } + + if (tries > this.max) { + throw errors.ErrMaxRateLimit; + } + + return tries; + } +} + +module.exports = Limit; diff --git a/services/passport.js b/services/passport.js index 83635fa85..0920c1c39 100644 --- a/services/passport.js +++ b/services/passport.js @@ -145,7 +145,7 @@ async function ValidateUserLogin(loginProfile, user, done) { // If the profile doesn't have a metadata field, or it does not have a // confirmed_at field, or that field is null, then send them back. if (!profile.metadata || !profile.metadata.confirmed_at || profile.metadata.confirmed_at === null) { - return done(new errors.ErrAuthentication(loginProfile.id)); + return done(errors.ErrNotVerified); } } diff --git a/services/users.js b/services/users.js index 78708b40a..4e9d52901 100644 --- a/services/users.js +++ b/services/users.js @@ -17,7 +17,7 @@ const UserModel = require('../models/user'); const USER_STATUS = require('../models/enum/user_status'); const USER_ROLES = require('../models/enum/user_roles'); -const RECAPTCHA_WINDOW_SECONDS = 60 * 10; // 10 minutes. +const RECAPTCHA_WINDOW = '10m'; // 10 minutes. const RECAPTCHA_INCORRECT_TRIGGER = 5; // after 3 incorrect attempts, recaptcha will be required. const ActionsService = require('./actions'); @@ -34,9 +34,8 @@ const PASSWORD_RESET_JWT_SUBJECT = 'password_reset'; const SALT_ROUNDS = 10; // Create a redis client to use for authentication. -const {createClientFactory} = require('./redis'); - -const client = createClientFactory(); +const Limit = require('./limit'); +const loginRateLimiter = new Limit('loginAttempts', RECAPTCHA_INCORRECT_TRIGGER, RECAPTCHA_WINDOW); // UsersService is the interface for the application to interact with the // UserModel through. @@ -70,23 +69,14 @@ module.exports = class UsersService { * where future login attempts must be made with the recaptcha flag. */ static async recordLoginAttempt(email) { - const rdskey = `la[${email.toLowerCase().trim()}]`; + try { + await loginRateLimiter.test(email.toLowerCase().trim()); + } catch (err) { + if (err === errors.ErrMaxRateLimit) { + throw errors.ErrLoginAttemptMaximumExceeded; + } - const replies = await client() - .multi() - .incr(rdskey) - .expire(rdskey, RECAPTCHA_WINDOW_SECONDS) - .exec(); - - // if this is new or has no expiry - if (replies[0] === 1 || replies[1] === -1) { - - // then expire it after the timeout - client().expire(rdskey, RECAPTCHA_WINDOW_SECONDS); - } - - if (replies[0] >= RECAPTCHA_INCORRECT_TRIGGER) { - throw errors.ErrLoginAttemptMaximumExceeded; + throw err; } } @@ -97,9 +87,7 @@ module.exports = class UsersService { * errors.ErrLoginAttemptMaximumExceeded */ static async checkLoginAttempts(email) { - const rdskey = `la[${email.toLowerCase().trim()}]`; - - const attempts = await client().get(rdskey); + const attempts = await loginRateLimiter.get(email.toLowerCase().trim()); if (!attempts) { return; } @@ -717,7 +705,7 @@ module.exports = class UsersService { * @param {String} email The email that we are needing to get confirmed. * @return {Promise} */ - static async createEmailConfirmToken(userID = null, email, referer = ROOT_URL) { + static async createEmailConfirmToken(user, email, referer = ROOT_URL) { if (!email || typeof email !== 'string') { throw new Error('email is required when creating a JWT for resetting passord'); } @@ -731,20 +719,6 @@ module.exports = class UsersService { subject: EMAIL_CONFIRM_JWT_SUBJECT }; - let user; - if (!userID) { - - // If there is no userID, we're coming from the endpoint where a new user - // is re-requesting a confirmation email and we don't know the userID. - user = await UserModel.findOne({profiles: {$elemMatch: {id: email, provider: 'local'}}}); - } else { - user = await UsersService.findById(userID); - } - - if (!user) { - throw errors.ErrNotFound; - } - // Get the profile representing the local account. let profile = user.profiles.find((profile) => profile.id === email && profile.provider === 'local'); diff --git a/test/server/routes/api/auth/index.js b/test/server/routes/api/auth/index.js index 2f3c4116a..463e27711 100644 --- a/test/server/routes/api/auth/index.js +++ b/test/server/routes/api/auth/index.js @@ -74,10 +74,8 @@ describe('/api/v1/auth/local', () => { .catch((err) => { expect(err).to.have.status(401); err.response.body.should.have.property('error'); - err.response.body.error.should.have.property('metadata'); - err.response.body.error.metadata.should.have.property('message', 'maria@gmail.com'); - return UsersService.createEmailConfirmToken(mockUser.id, mockUser.profiles[0].id); + return UsersService.createEmailConfirmToken(mockUser, mockUser.profiles[0].id); }) .then(UsersService.verifyEmailConfirmation) .then(() => { diff --git a/test/server/services/users.js b/test/server/services/users.js index 251618187..09a8396c4 100644 --- a/test/server/services/users.js +++ b/test/server/services/users.js @@ -88,17 +88,19 @@ describe('services.UsersService', () => { describe('#createEmailConfirmToken', () => { it('should create a token for a valid user', async () => { - const token = await UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id); + const token = await UsersService.createEmailConfirmToken(mockUsers[0], mockUsers[0].profiles[0].id); expect(token).to.not.be.null; }); it('should not create a token for a user already verified', async () => { - const token = await UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id); + const token = await UsersService.createEmailConfirmToken(mockUsers[0], mockUsers[0].profiles[0].id); expect(token).to.not.be.null; await UsersService.verifyEmailConfirmation(token); - return expect(UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)).to.eventually.be.rejected; + const user = await UsersService.findById(mockUsers[0].id); + + return expect(UsersService.createEmailConfirmToken(user, mockUsers[0].profiles[0].id)).to.eventually.be.rejected; }); }); @@ -106,7 +108,7 @@ describe('services.UsersService', () => { describe('#verifyEmailConfirmation', () => { it('should correctly validate a valid token', async () => { - const token = await UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id); + const token = await UsersService.createEmailConfirmToken(mockUsers[0], mockUsers[0].profiles[0].id); expect(token).to.not.be.null; return expect(UsersService.verifyEmailConfirmation(token)).to.eventually.not.be.rejected; @@ -122,7 +124,7 @@ describe('services.UsersService', () => { it('should update the user model when verification is complete', () => { return UsersService - .createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id) + .createEmailConfirmToken(mockUsers[0], mockUsers[0].profiles[0].id) .then((token) => { expect(token).to.not.be.null; From 910acdd52bcfa21d9d11aaf418857297e2e5653a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 12 Oct 2017 15:49:21 -0600 Subject: [PATCH 004/344] added cli tools for verifying email address --- bin/cli-users | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/bin/cli-users b/bin/cli-users index c6555ab28..89f53a67d 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -241,13 +241,21 @@ function listUsers() { }); users.forEach((user) => { + let state = user.disabled ? 'Disabled' : 'Enabled'; + const profile = user.profiles.find(({provider}) => provider === 'local'); + if (profile && profile.metadata && profile.metadata.confirmed_at) { + state += ', Verified'; + } else { + state += ', Unverified'; + } + table.push([ user.id, user.username, user.profiles.map((p) => p.provider).join(', '), user.roles.join(', '), user.status, - user.disabled ? 'Disabled' : 'Enabled' + state ]); }); @@ -396,6 +404,23 @@ function enableUser(userID) { }); } +/** + * Verifies an email address for a user. + * + * @param userID the user's id + * @param email the user's email address to be verified + */ +async function verify(userID, email) { + try { + await UsersService.confirmEmail(userID, email); + console.log(`User ${userID} had their email ${email} verified.`); + util.shutdown(); + } catch (err) { + console.error(err); + util.shutdown(1); + } +} + //============================================================================== // Setting up the program command line arguments. //============================================================================== @@ -467,6 +492,11 @@ program .description('enable a given user from logging in') .action(enableUser); +program + .command('verify ') + .description('verifies the given user\'s email address') + .action(verify); + program.parse(process.argv); // If there is no command listed, output help. From 9d36426a1be60b4674406ddef8823b898258be62 Mon Sep 17 00:00:00 2001 From: Facu Date: Fri, 13 Oct 2017 11:41:47 -0300 Subject: [PATCH 005/344] format UserDetail css --- .../coral-admin/src/components/UserDetail.css | 203 +++++++++--------- 1 file changed, 100 insertions(+), 103 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index fbde4a626..4deb35550 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -1,168 +1,165 @@ .copyButton { - background-color: white; - border: solid 1px; - padding: 2px 6px; - height: auto; - line-height: initial; - min-width: auto; - letter-spacing: normal; - font-size: 0.9em; - margin-left: 10px; + background-color: white; + border: solid 1px; + padding: 2px 6px; + height: auto; + line-height: initial; + min-width: auto; + letter-spacing: normal; + font-size: 0.9em; + margin-left: 10px; } .userDetailList { - list-style: none; - padding: 0; - margin: 0; + list-style: none; + padding: 0; + margin: 0; } .userDetailItem { - margin: 0 5px; - font-weight: 500; + margin: 0 5px; + font-weight: 500; } .stats { - display: flex; - list-style: none; - padding: 0; - margin: 0; - text-align: center; - margin: 15px 0 5px; - color: #595959; + display: flex; + list-style: none; + padding: 0; + margin: 0; + text-align: center; + margin: 15px 0 5px; + color: #595959; } .stat { - margin-right: 20px; + margin-right: 20px; } .stat:last-child { - margin-right: 0px; + margin-right: 0px; } -.statItem, .statReportResult { - padding: 3px 5px; - background-color: #D8D8D8; - border-radius: 3px; - font-weight: 500; - display: block; - font-size: 0.9em; - line-height: normal; - letter-spacing: 0.4px; - min-width: 60px; +.statItem, +.statReportResult { + padding: 3px 5px; + background-color: #D8D8D8; + border-radius: 3px; + font-weight: 500; + display: block; + font-size: 0.9em; + line-height: normal; + letter-spacing: 0.4px; + min-width: 60px; } .statResult { - font-size: 1.5em; - padding: 5px 0; - display: inline-block; + font-size: 1.5em; + padding: 5px 0; + display: inline-block; } .statReportResult { - color: white; - margin: 5px 0; - font-weight: 400; + color: white; + margin: 5px 0; + font-weight: 400; } .statReportResult.reliable { - background-color: #749C48; + background-color: #749C48; } .statReportResult.neutral { - background-color: #616161; + background-color: #616161; } .statReportResult.unreliable { - background-color: #F44336; + background-color: #F44336; } .memberSince { - clear: both; + clear: both; } .small { - color: #888888; - font-size: 0.9em; - letter-spacing: 0.4px; + color: #888888; + font-size: 0.9em; + letter-spacing: 0.4px; } .profileEmail { - border: none; - background-color: transparent; - font-size: 16px; - outline: none; - width: calc(100% - 90px); + border: none; + background-color: transparent; + font-size: 16px; + outline: none; + width: calc(100% - 90px); } .commentStatuses { - padding: 0 0 0 10px; - margin: 0; - align-self: center; - list-style: none; - box-sizing: border-box; - li { - display: inline-block; - margin-right: 10px; - cursor: pointer; - padding: 0 10px; - } + padding: 0 0 0 10px; + margin: 0; + align-self: center; + list-style: none; + box-sizing: border-box; + li { + display: inline-block; + margin-right: 10px; + cursor: pointer; + padding: 0 10px; + } } .active { - font-weight: bold; - border-bottom: 3px solid #F36451; + font-weight: bold; + border-bottom: 3px solid #F36451; } .bulkActionGroup { - height: 52px; - background-color: #efefef; - padding: 0 0 0 10px; - display: flex; - i { - margin-right: 0; - } - - .bulkAction { - display: inline-block; - width: 48px; - height: 48px; - transform: scale(.7); - min-width: 0; - } - - .bulkAction:last-child { - margin-left: -10px; - } - - span{ - align-self: center; - font-weight: 500; - margin-left: 15px; - } + height: 52px; + background-color: #efefef; + padding: 0 0 0 10px; + display: flex; + i { + margin-right: 0; + } + .bulkAction { + display: inline-block; + width: 48px; + height: 48px; + transform: scale(.7); + min-width: 0; + } + .bulkAction:last-child { + margin-left: -10px; + } + span { + align-self: center; + font-weight: 500; + margin-left: 15px; + } } -.loadMore > button { - background-color: #696969; - - &:hover { - background-color: #404040; - color: white; - } +.loadMore>button { + background-color: #696969; + &:hover { + background-color: #404040; + color: white; + } } .toggleAll { - padding: 0 10px 0 0; - align-self: center; + padding: 0 10px 0 0; + align-self: center; } .commentList { - clear: both; + clear: both; } -.bulkActionHeader{ +.bulkActionHeader { display: flex; justify-content: space-between; height: 52px; - &.selected{ - background-color: #efefef; - } + &.selected { + background-color: #efefef; + } } \ No newline at end of file From 06d0da9286ec05a060653742e525757a2a30f7fe Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Fri, 13 Oct 2017 15:50:13 +0100 Subject: [PATCH 006/344] Reorganize file order and reinstate h2s --- docs/_docs/02-01-required-configuration.md | 12 +-- docs/_docs/02-02-advanced-configuration.md | 82 +++++++++---------- ... => 03-01-product-guide-how-talk-works.md} | 0 .../03-02-product-guide-commenter-features.md | 4 + .../03-03-product-guide-moderator-features.md | 4 + ...-trust.md => 03-04-product-guide-trust.md} | 0 ... => 03-05-product-guide-toxic-comments.md} | 0 ...-overview.md => 04-01-plugins-overview.md} | 0 ...lt-plugins.md => 04-02-default-plugins.md} | 36 ++++---- ...plugins.md => 04-03-additional-plugins.md} | 20 ++--- ...gin-recipes.md => 04-04-plugin-recipes.md} | 8 +- docs/_sass/talk.scss | 3 - 12 files changed, 85 insertions(+), 84 deletions(-) rename docs/_docs/{04-01-product-guide-how-talk-works.md => 03-01-product-guide-how-talk-works.md} (100%) create mode 100644 docs/_docs/03-02-product-guide-commenter-features.md create mode 100644 docs/_docs/03-03-product-guide-moderator-features.md rename docs/_docs/{04-02-product-guide-trust.md => 03-04-product-guide-trust.md} (100%) rename docs/_docs/{04-03-product-guide-toxic-comments.md => 03-05-product-guide-toxic-comments.md} (100%) rename docs/_docs/{03-01-plugins-overview.md => 04-01-plugins-overview.md} (100%) rename docs/_docs/{03-02-default-plugins.md => 04-02-default-plugins.md} (92%) rename docs/_docs/{03-03-additional-plugins.md => 04-03-additional-plugins.md} (93%) rename docs/_docs/{03-04-plugin-recipes.md => 04-04-plugin-recipes.md} (95%) diff --git a/docs/_docs/02-01-required-configuration.md b/docs/_docs/02-01-required-configuration.md index 84a7236db..dc09a1707 100644 --- a/docs/_docs/02-01-required-configuration.md +++ b/docs/_docs/02-01-required-configuration.md @@ -19,7 +19,7 @@ If you've already configured your application with the required configuration, you can further customize it's behavior by applying [Advanced Configuration]({{ "/advanced-configuration/" | relative_url }}). -### TALK_MONGO_URL +## TALK_MONGO_URL The database connection string for the MongoDB database. This usually takes the form of: @@ -31,7 +31,7 @@ TALK_MONGO_URL=mongodb://:@:` component of the [TALK_ROOT_URL]({{ "/configuration/#talk_root_url" | relative_url }}){: .param}. @@ -361,7 +361,7 @@ Then all the routes for the API will be expecting to be hit on `/talk/`, such as can perform the path stripping when serving an upstream proxy, but some CDN's cannot. You would use this option in the latter situation. -### TALK_SMTP_EMAIL +## TALK_SMTP_EMAIL The email address to send emails from using the SMTP provider in the format: @@ -371,23 +371,23 @@ TALK_SMTP_EMAIL="The Coral Project" Including the name and email address. -### TALK_SMTP_HOST +## TALK_SMTP_HOST The domain for the SMTP provider that you are using. -### TALK_SMTP_PASSWORD +## TALK_SMTP_PASSWORD The password for the SMTP provider you are using. -### TALK_SMTP_PORT +## TALK_SMTP_PORT The port for the SMTP provider that you are using. -### TALK_SMTP_USERNAME +## TALK_SMTP_USERNAME The username of the SMTP provider you are using. -### TALK_STATIC_URI +## TALK_STATIC_URI Used to set the uri where the static assets should be served from. This is used when you want to upload the static assets through your build process to a @@ -395,14 +395,14 @@ service like Google Cloud Storage or Amazon S3 and you would then specify the CDN/Storage url. (Defaults to value of [TALK_ROOT_URL]({{ "/configuration/#talk_root_url" | relative_url }}){: .param}) -### TALK_THREADING_LEVEL +## TALK_THREADING_LEVEL Specify the maximum depth of the comment thread. (Default `3`) **Note that a high value for `TALK_THREADING_LEVEL` will result in large performance impacts.** -### TALK_WEBSOCKET_LIVE_URI +## TALK_WEBSOCKET_LIVE_URI Used to override the location to connect to the websocket endpoint to potentially another host. This should be used when you need to route websocket @@ -419,7 +419,7 @@ is `FALSE`, or the path component of **Warning: if used without managing the auth state manually, auth cannot be persisted due to browser restrictions.** -### TRUST_THRESHOLDS +## TRUST_THRESHOLDS Configure the reliability thresholds for flagging and commenting. (Default `comment:2,-1;flag:2,-1`) diff --git a/docs/_docs/04-01-product-guide-how-talk-works.md b/docs/_docs/03-01-product-guide-how-talk-works.md similarity index 100% rename from docs/_docs/04-01-product-guide-how-talk-works.md rename to docs/_docs/03-01-product-guide-how-talk-works.md diff --git a/docs/_docs/03-02-product-guide-commenter-features.md b/docs/_docs/03-02-product-guide-commenter-features.md new file mode 100644 index 000000000..8fde0f0ac --- /dev/null +++ b/docs/_docs/03-02-product-guide-commenter-features.md @@ -0,0 +1,4 @@ +--- +title: Commenter Features +permalink: /commenter-features/ +--- diff --git a/docs/_docs/03-03-product-guide-moderator-features.md b/docs/_docs/03-03-product-guide-moderator-features.md new file mode 100644 index 000000000..355c40a1c --- /dev/null +++ b/docs/_docs/03-03-product-guide-moderator-features.md @@ -0,0 +1,4 @@ +--- +title: Moderator Features +permalink: /moderator-features/ +--- diff --git a/docs/_docs/04-02-product-guide-trust.md b/docs/_docs/03-04-product-guide-trust.md similarity index 100% rename from docs/_docs/04-02-product-guide-trust.md rename to docs/_docs/03-04-product-guide-trust.md diff --git a/docs/_docs/04-03-product-guide-toxic-comments.md b/docs/_docs/03-05-product-guide-toxic-comments.md similarity index 100% rename from docs/_docs/04-03-product-guide-toxic-comments.md rename to docs/_docs/03-05-product-guide-toxic-comments.md diff --git a/docs/_docs/03-01-plugins-overview.md b/docs/_docs/04-01-plugins-overview.md similarity index 100% rename from docs/_docs/03-01-plugins-overview.md rename to docs/_docs/04-01-plugins-overview.md diff --git a/docs/_docs/03-02-default-plugins.md b/docs/_docs/04-02-default-plugins.md similarity index 92% rename from docs/_docs/03-02-default-plugins.md rename to docs/_docs/04-02-default-plugins.md index ab843f21b..aff17ebcc 100644 --- a/docs/_docs/03-02-default-plugins.md +++ b/docs/_docs/04-02-default-plugins.md @@ -16,7 +16,7 @@ Talk that are not enabled by default. You can enable these or disable these default plugins by consulting the [Plugins Overview]({{ "/plugins/" | relative_url }}) page. -### talk-plugin-auth +## talk-plugin-auth Source: [plugins/talk-plugin-auth](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-auth){:target="_blank"} @@ -24,7 +24,7 @@ Enables generic registration via an email address, a username, a password, and a password confirmation. To sync Talk auth with your own auth systems, you can use this plugin as a template. -### talk-plugin-facebook-auth +## talk-plugin-facebook-auth Source: [plugins/talk-plugin-auth](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-auth){:target="_blank"} @@ -34,12 +34,10 @@ Enables sign-in via Facebook via the server side passport middleware. Configuration: -{:.no_toc} - - [TALK_FACEBOOK_APP_ID]({{ "/configuration/#talk_facebook_app_id" | relative_url }}){:.param} (**required**) - See the existing documentation for the [TALK_FACEBOOK_APP_ID]({{ "/configuration/#talk_facebook_app_id" | relative_url }}){:.param}. - [TALK_FACEBOOK_APP_SECRET]({{ "/configuration/#talk_facebook_app_secret" | relative_url }}){:.param} (**required**) - See the existing documentation for the [TALK_FACEBOOK_APP_SECRET]({{ "/configuration/#talk_facebook_app_secret" | relative_url }}){:.param}. -### talk-plugin-featured-comments +## talk-plugin-featured-comments Source: [plugins/talk-plugin-featured-comments](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-featured-comments){:target="_blank"} @@ -47,14 +45,14 @@ Enables the ability for Moderators to feature and un-feature comments via the Stream and the Admin. Featured comments show in a first-place tab on the Stream if there are any featured comments on that story. -### talk-plugin-respect +## talk-plugin-respect Source: [plugins/talk-plugin-respect](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-respect){:target="_blank"} Enables a `respect` reaction button. Why a "respect" button, you ask? [Read more here](https://mediaengagement.org/research/engagement-buttons/). -### talk-plugin-comment-content +## talk-plugin-comment-content Source: [plugins/talk-plugin-comment-content](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-comment-content){:target="_blank"} @@ -62,7 +60,7 @@ Pluginizes the text of a comment to support custom treatment of this text. This plugin currently parses the given text to see if it contains a link, and makes them clickable. -### talk-plugin-ignore-user +## talk-plugin-ignore-user Source: [plugins/talk-plugin-ignore-user](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-ignore-user){:target="_blank"} @@ -70,20 +68,20 @@ Enables ability for users to ignore (or "mute") other users. If a user is ignored, you will not see any of their comments. You can un-ignore a user via the My Profile tab. -### talk-plugin-permalink +## talk-plugin-permalink Source: [plugins/talk-plugin-permalink](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-permalink){:target="_blank"} Enables a `Link` button that will provide a permalink to the comment that can be shared with others. -### talk-plugin-viewing-options +## talk-plugin-viewing-options Source: [plugins/talk-plugin-viewing-options](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-viewing-options){:target="_blank"} Pluginizes the sorting/viewing options for a comment stream. -### talk-plugin-sort-newest +## talk-plugin-sort-newest Source: [plugins/talk-plugin-sort-newest](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-sort-newest){:target="_blank"} @@ -92,7 +90,7 @@ Requires: [talk-plugin-viewing-options](#talk-plugin-viewing-options){:.param} Provides a sort for the newest comments first. This isn't necessarily required as the default sort without options/plugins is newest first. -### talk-plugin-sort-oldest +## talk-plugin-sort-oldest Source: [plugins/talk-plugin-sort-oldest](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-sort-oldest){:target="_blank"} @@ -100,7 +98,7 @@ Requires: [talk-plugin-viewing-options](#talk-plugin-viewing-options){:.param} Provides a sort for the newest comments first. -### talk-plugin-most-respected +## talk-plugin-most-respected Source: [plugins/talk-plugin-most-respected](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-most-respected){:target="_blank"} @@ -108,7 +106,7 @@ Requires: [talk-plugin-viewing-options](#talk-plugin-viewing-options){:.param}, Provides a sort for the comments with the most `respect` reactions first. -### talk-plugin-most-replied +## talk-plugin-most-replied Source: [plugins/talk-plugin-most-replied](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-most-replied){:target="_blank"} @@ -116,20 +114,20 @@ Requires: [talk-plugin-viewing-options](#talk-plugin-viewing-options){:.param} Provides a sort for the comments with the most replies first. -### talk-plugin-offtopic +## talk-plugin-offtopic Source: [plugins/talk-plugin-offtopic](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-offtopic){:target="_blank"} Allows the comment authors to tag their comment as `Off-Topic` which will add a visible badge on the frontend to other users that their comment is off-topic. -### talk-plugin-author-menu +## talk-plugin-author-menu Source: [plugins/talk-plugin-author-menu](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-author-menu){:target="_blank"} Pluginizes the author's name on hover. -### talk-plugin-member-since +## talk-plugin-member-since Source: [plugins/talk-plugin-member-since](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-member-since){:target="_blank"} @@ -137,14 +135,14 @@ Requires: [talk-plugin-author-menu](#talk-plugin-author-menu){:.param} Displays the date that the user was created as a `Member Since ${created_at}`. -### talk-plugin-moderation-actions +## talk-plugin-moderation-actions Source: [plugins/talk-plugin-moderation-actions](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-moderation-actions){:target="_blank"} Enables in-stream moderation so that Moderators can reject, approve comments, as well as ban users, directly from the comment stream. When [talk-plugin-featured-comments](#talk-plugin-featured-comments){:.param} is enabled -### talk-plugin-flag-details +## talk-plugin-flag-details Source: [plugins/talk-plugin-flag-details](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-flag-details){:target="_blank"} diff --git a/docs/_docs/03-03-additional-plugins.md b/docs/_docs/04-03-additional-plugins.md similarity index 93% rename from docs/_docs/03-03-additional-plugins.md rename to docs/_docs/04-03-additional-plugins.md index 9be7f1cd2..55ab1749b 100644 --- a/docs/_docs/03-03-additional-plugins.md +++ b/docs/_docs/04-03-additional-plugins.md @@ -11,13 +11,13 @@ Talk ships with several plugins that aren't enabled by default: These plugins can be enabled by consulting the [Plugins Overview]({{ "/plugins/" | relative_url }}) page. -### talk-plugin-like +## talk-plugin-like Source: [plugins/talk-plugin-like](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-like){:target="_blank"} Enables a `like` reaction button. -### talk-plugin-sort-most-liked +## talk-plugin-sort-most-liked Source: [plugins/talk-plugin-sort-most-liked](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-sort-most-liked){:target="_blank"} @@ -25,13 +25,13 @@ Requires: [talk-plugin-viewing-options]({{ "/default-plugins/#talk-plugin-viewin Provides a sort for the comments with the most `like` reactions first. -### talk-plugin-love +## talk-plugin-love Source: [plugins/talk-plugin-love](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-love){:target="_blank"} Enables a `love` reaction button. -### talk-plugin-sort-most-loved +## talk-plugin-sort-most-loved Source: [plugins/talk-plugin-sort-most-loved](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-sort-most-loved){:target="_blank"} @@ -39,7 +39,7 @@ Requires: [talk-plugin-viewing-options]({{ "/default-plugins/#talk-plugin-viewin Provides a sort for the comments with the most `love` reactions first. -### talk-plugin-remember-sort +## talk-plugin-remember-sort Source: [plugins/talk-plugin-remember-sort](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-remember-sort){:target="_blank"} @@ -47,7 +47,7 @@ Requires: [talk-plugin-viewing-options]({{ "/default-plugins/#talk-plugin-viewin Enables saving a user’s last sort selection as they browse other articles. -### talk-plugin-deep-reply-count +## talk-plugin-deep-reply-count Source: [plugins/talk-plugin-deep-reply-count](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-deep-reply-count){:target="_blank"} @@ -55,7 +55,7 @@ Enables counting of comments to include replies via a new graph edge. Not recommended for large installations as it will unreasonably reduce the query efficiency to compute this number. -### talk-plugin-slack-notifications +## talk-plugin-slack-notifications Source: [plugins/talk-plugin-slack-notifications](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-slack-notifications){:target="_blank"} @@ -70,12 +70,11 @@ Slack, if this isn't what you want, be sure to use the provided plugin as a recipe to further customize the behavior*. Configuration: -{:.no_toc} - `SLACK_WEBHOOK_URL` (**required**) - The webhook url that will be used to post new comments to. -### talk-plugin-toxic-comments +## talk-plugin-toxic-comments Source: [plugins/talk-plugin-toxic-comments](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-toxic-comments){:target="_blank"} @@ -85,7 +84,6 @@ threshold. For more information on what Toxic Comments are, check out the [Toxic Comments]({{ "/toxic-comments/" | relative_url }}) documentation. Configuration: -{:.no_toc} - `TALK_PERSPECTIVE_API_KEY` (**required**) - The API Key for Perspective. You can register and get your own key at [http://perspectiveapi.com/](http://perspectiveapi.com/){:target="_blank"}. @@ -97,7 +95,7 @@ Configuration: be processed before it will skip the toxicity analysis, parsed by [ms](https://www.npmjs.com/package/ms){:target="_blank"}. (Default `300ms`) -### talk-plugin-subscriber +## talk-plugin-subscriber Source: [plugins/talk-plugin-subscriber](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-subscriber){:target="_blank"} diff --git a/docs/_docs/03-04-plugin-recipes.md b/docs/_docs/04-04-plugin-recipes.md similarity index 95% rename from docs/_docs/03-04-plugin-recipes.md rename to docs/_docs/04-04-plugin-recipes.md index 6b76a54ca..019ff21e4 100644 --- a/docs/_docs/03-04-plugin-recipes.md +++ b/docs/_docs/04-04-plugin-recipes.md @@ -16,7 +16,7 @@ The following are the available recipes for use: {% include toc.html %} -### recipe-avatar +## recipe-avatar Source: [talk-recipes/tree/master/plugins/avatar](https://github.com/coralproject/talk-recipes/tree/master/plugins/avatar){:target="_blank"} @@ -24,7 +24,7 @@ Provides support for avatars hosted via third party, extends the User Model and provides UI on the client-side too. -### recipe-translations +## recipe-translations Source: [talk-recipes/tree/master/plugins/translations](https://github.com/coralproject/talk-recipes/tree/master/plugins/translations){:target="_blank"} @@ -32,7 +32,7 @@ Provides an example for overriding application text through the translation system. -### recipe-subscriber +## recipe-subscriber Source: [talk-recipes/tree/master/plugins/subscriber](https://github.com/coralproject/talk-recipes/tree/master/plugins/subscriber){:target="_blank"} @@ -41,7 +41,7 @@ Provides an example for adding `SUBSCRIBER` badges for users with the the auth system. -### recipe-author-name +## recipe-author-name Source: [talk-recipes/tree/master/plugins/author-name](https://github.com/coralproject/talk-recipes/tree/master/plugins/author-name){:target="_blank"} diff --git a/docs/_sass/talk.scss b/docs/_sass/talk.scss index 85e5f1c93..69e039951 100644 --- a/docs/_sass/talk.scss +++ b/docs/_sass/talk.scss @@ -41,9 +41,6 @@ h2, h3, h4 { } article.configuration { - h2 { - font-weight: bolder; - } h2, .toc { @extend .param; From 5dd08d84e5f5a8694bf6679960e50f96b856c50c Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Fri, 13 Oct 2017 16:48:21 +0100 Subject: [PATCH 007/344] Added Commenter Features section --- .../03-02-product-guide-commenter-features.md | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/docs/_docs/03-02-product-guide-commenter-features.md b/docs/_docs/03-02-product-guide-commenter-features.md index 8fde0f0ac..81ec5128d 100644 --- a/docs/_docs/03-02-product-guide-commenter-features.md +++ b/docs/_docs/03-02-product-guide-commenter-features.md @@ -2,3 +2,141 @@ title: Commenter Features permalink: /commenter-features/ --- + +## Signing up for Talk + +There are 2 ways that newsrooms can support signup/login functionality with Talk: + +Use Talk’s auth plugin out of the box (supports account registration with username and password, as well as features like forgot password) + +Create their own auth plugin (you can use ours as a template) to integrate with your own auth systems + +We also provide a Facebook auth plugin that supports logging in with Facebook (you must provide your own Facebook App ID and Secret, which you can read more about here: https://developers.facebook.com) + +## Comments and Replies + +Talk supports a standard comment hierarchy. There are top-level (or parent) comments, and then replies to that comment (or children comments). + +**Permalinks** + +All levels of comments and replies are able to be linked to via permalink. Permalinks are structured using a `commentId` query param: + +https://domain.com/article-asset-slug/?commentId=0879edd7-1c4f-4918-abd2-a770db446b75 + +**Threading** + +Talk supports by default 3 levels of threading, meaning each top-level comment has a depth of 3 replies; replies beyond that are not nested below the 3rd level. You can adjust this using the `TALK_THREADING_LEVEL` configuration variable. We don’t recommend deep threading because it can cause issues with styling, especially on mobile. + +You can style threaded comments using these CSS classes: + + ``` + talk-stream-comment-wrapper-level-${depth} + talk-stream-comment + talk-stream-comment-level-${depth} + talk-stream-highlighted-comment + talk-stream-pending-comment +``` + +**Automatic Updates** + +Talk supports real-time loading and updating of comments, via subscriptions (specifically GraphQL Subscriptions); this enables us to not have to refresh to see new comments on a given comment stream. + +Talk enables this via “Load More” buttons for both top-level comments (this button appears at the top of the stream), and within conversation threads (this button appears in situ for replies). + +We’ve decided to go this route in order to make the viewing experience as smooth as possible, so that the feed of comments doesn’t change as you’re reading just because new comments are coming in. This could be especially disruptive on breaking news and/or controversial stories with very active discussions. + +**Comment Character Limits** + +You can enable Talk to limit the character length for comments, for example, some newsrooms we’ve worked with prefer a limit between 2000 and 5000 characters. Commenters will be alerted that they have gone over that number and won’t be able to submit their comment until they’ve edited it. This can be a useful tool to ensure commenters are concise with their comments. + +## Comment Reactions + +Talk comes with a `respect` button out of the box. Why a “respect” button, you ask? [Read more here](https://mediaengagement.org/research/engagement-buttons/). + +We also have 2 more plugins, `like` and `love`, that you can turn on and experiment with on your own Talk install. + +And our plugin architecture makes it easy to create your own custom reaction buttons too. + +## Reporting Comments + +Readers can report comments if they feel they’re unsuitable. They can choose one of the following reasons: + +* This comment is offensive +* This looks like an ad/marketing +* I don’t agree with this comment +* Other + +They can also include more information and this shows for moderators in the Flag Detail area on the comments in the moderation queues. + +Comments that are reported go to the Reported queue, with the exception of “I don’t agree with this comment”. This option is a useful way to let other readers vent their frustration, but since just disagreeing with something doesn’t mean it’s not suitable, we leave it be. + + +## Reporting Usernames + +Usernames can also be reported by readers, if the username is inappropriate or offensive. They can choose one of the following reasons: + +* This username is offensive +* I don't like this username +* This user is impersonating +* This looks like an ad/marketing + +Reported usernames go to the Reported Usernames queue which is located in the Community tab. If a username is rejected by a moderator, the commenter is prompted to change their username and they are suspended from commenting, replying or reacting to comments until they do so. They receive an email, and also a message at the top of their comment streams that let’s them know they’re suspended. + +If the commenter changes their username, it goes back to the Reported Usernames queue for approval. If the updated username is accepted by a moderator, the commenter is no longer suspended and continue interacting with the community. If the username is rejected, the commenter remains suspended until they change their username to something appropriate. + +Approved usernames that are reported do not show up in the Reported Usernames queues any longer, since they have been specifically OK’ed by a moderator. + +## Ignoring Users + +Commenters can ignore other commenters and essentially mute them entirely from the comment platform. Commenters can manage their ignored users list in their My Profile tab. + +## Featured Comments + +Moderators can feature comments that they want to highlight and recommend to their community. Featured comments show up on a separate tab, that is the default for the comment stream. Featured comments within the stream show a Featured badge. + +## Sorting the Stream + +Readers can sort the stream in 4 ways based on their viewing preferencet: + +* Oldest first +* Newest first +* Most respect first (or most liked, most loved, etc., depending on what reactions you use) +* Most replied first + +We also make it easy to add more sorts via custom plugins. + +## Badges + +Badges differentiate users and comments on the stream. By default, Talk has two badges. + +The Staff user badge that shows when a commenter has an Admin, Moderator, or Staff role. + +The Featured comment badge shows when a comment has been featured. + +Another optional badge is the Subscriber badge (which is available as a [Recipe](/plugin-recipes/#recipe-subscriber). + +Badges are another easy part of Talk to customize by creating a new `tag`, then setting some rules for when it should show, and how the badge should be styled. + +## My Profile + +The My Profile tab is where commenters can go to see their comment history, as well as reactions and replies to their comments. They can also see their email address associated with Talk, and manage their Ignored Users list here. + +## Notifications & Error Messaging + +Talk leverages notification and messages on the stream to alert users to important information about their comment or their account. + +**Pre-moderation of comments** + +If a stream is set to Pre-mod, or a commenter’s Trust karma score has fallen to negative, or if for any other reason their comment is being pre-moderated, they will get a notification letting them know this when they post a comment. + +**Suspension because of Username** + +When a commenter has been suspended because their username is inappropriate, they will see a message at the top of their streams stating this. + +**Timed Suspension** + +When a commenter has been suspended for a block of time (aka a “time-out”), they will see a message at the top of their streams stating this. + +**Ban** + +When a commenter has been banned, they will see a message at the top of their streams staging this. From aa0c41c342e66ecd254d4024ed4b9997c9c5cc29 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Sat, 14 Oct 2017 18:25:41 +0100 Subject: [PATCH 008/344] Add section Moderator Features --- .../03-03-product-guide-moderator-features.md | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/docs/_docs/03-03-product-guide-moderator-features.md b/docs/_docs/03-03-product-guide-moderator-features.md index 355c40a1c..142fb8652 100644 --- a/docs/_docs/03-03-product-guide-moderator-features.md +++ b/docs/_docs/03-03-product-guide-moderator-features.md @@ -2,3 +2,128 @@ title: Moderator Features permalink: /moderator-features/ --- + +## The Talk Admin + +The Admin is your moderators will moderate your comments, and your Admins will configure and manage the different parts of Talk. + +### Dashboard + +The Dashboard provides real-time information to moderators so they know at a glance where they should direct their attention. It shows what articles are receiving the most reports and where the most active conversations are happening. + +### Moderate + +This is the tab where Moderators will spend the majority of their time. They can choose (via the dropdown) which story they would like to moderate, or moderate site-wide. + +#### Default Mod Queues + +**New** + +The New queue contains all comments that have not been moderated yet. + +**Reported** + +The Reported queue contains all comments that need moderator attention. + +**Approved** + +The Approved queue contains all approved comments. + +**Rejected** + +The Rejected queue contains all comments that have been rejected, either manually by moderators or automatically, e.g. they have used a banned word. + +**All** + +The All queue contains all comments that have been submitted either article or site-wide. + +#### Moderation Badges + +**Pre-mod** + +The Pre-mod badge signifies comments that are being pre-modded. + +**User** + +The User badge signifies comments that have been reported by another user. + +**History** + +The History badge signifies comments that have been flagged because of a user’s history. + +**Toxic** + +The Toxic badge signifies comments that are above the set Toxicity Probability Threshold. Note you must have `talk-plugin-toxic-comments` enabled. [Read more about Toxic Comments here](LINK). + +**Suspect** + +The Suspect badge signifies comments that contain a Suspect Word. [Read more about Suspect Words here](LINK). + +**Contains Link** + +The Contains Link badge signifies a comment that contains a link, which can sometimes mean it is a spam or ad comment. + +**Flag Details View** + +At the bottom of each comment in the moderation queues, you can see more information about a comment’s flags by clicking on More Detail. + +#### Moderator Actions + +**Accept** + +Accepting a comment ensures that the comment is displayed on the stream. + +**Reject** + +Rejecting a comment removes the comment from the stream. + +**Feature** + +Featuring a comment adds that comment to the Featured Comments tab on the stream. + +**Suspend User** + +Suspending a user allows a moderator to give a commenter a “time-out”; during that time they won’t be allowed to post comments or react to comments. + +**Ban User** + +Banning a user allows a moderator to permanently disallow a commenter to interact with their community. The commenter’s previous comments will remain on the site. This action can only be un-done manually by a moderator. + +#### Viewing a User’s Comment History + +In order to get an idea of what sort of a commenter someone is, moderators can click on the commenter’s username in any moderation queue and see details about their history. + +**Username, Email and Member Since Date** + +This shows the basic details about a commenter. + +**Total Comments** + +This shows the number of comments that a commenter has made that currently display on the site. + +**Reject Rate** + +This shows the % of comments a commenter has had rejected by moderators, or automatically. + +**Reports** + +This shows if a commenter is a reliable flagger, an unreliable flagger, or a neutral flagger. [Read more about reliable and unreliable flaggers here](LINK). + +**Moderating from this View** + +Talk also allows you to moderate a commenter’s recent comments from this view. + +#### Keyboard Shortcuts + +Talk also supports a number of keyboard shortcuts that moderators can leverage to moderate quickly: + +| Shortcut | Action | +| -------- | ------------------------------- | +| `j` | Go to the next comment | +| `k` | Go to the previous comment | +| `ctrl+f` | Open search | +| `t` | Switch queues | +| `s` | Toggle single comment edit view | +| `?` | Open this menu | +| `d` | Approve | +| `f` | Reject | From 759338dc02fa18150f0e271780e762edb5c0ffbc Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Sat, 14 Oct 2017 18:36:17 +0100 Subject: [PATCH 009/344] Added more Moderator Features --- .../03-03-product-guide-moderator-features.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/_docs/03-03-product-guide-moderator-features.md b/docs/_docs/03-03-product-guide-moderator-features.md index 142fb8652..98fa5fc4c 100644 --- a/docs/_docs/03-03-product-guide-moderator-features.md +++ b/docs/_docs/03-03-product-guide-moderator-features.md @@ -127,3 +127,26 @@ Talk also supports a number of keyboard shortcuts that moderators can leverage t | `?` | Open this menu | | `d` | Approve | | `f` | Reject | + +### Stories + +In the Stories tab moderators can view all the stories that have Talk comments embedded on them, as well as be able to Open or Close comment streams on stories. + +### Community + +The Community tab houses everything having to do with your team and your commenters. + +#### Moderating Usernames + +Any usernames that have been reported will show in the Reported Usernames sub-tab. Moderators can approve usernames if they’re suitable, or reject a username. If a username is rejected, the commenter will be notified that they need to change their username; until they do, they will be suspended from Talk. The updated username then again appears in this queue for a decision by moderators. + +#### Managing People & Roles + +All your team and commenters show in the People sub-tab. From here, you can manage your team members’ roles (Admins, Moderators, Staff), as well as search for commenters and take action on them (e.g. Ban/Un-ban, Suspend, etc.). +### Configure + +See [Configuring Talk](LINK). + +## Moderating via the Comment Stream + +Moderators can also choose to moderate comments in situ. If you are logged in as a Moderator or Admin, you will see a caret dropdown on each comment that allows you to Approve, Reject, or Feature comments, or Ban a User directly from the comment stream. From c8f432268be247287f23079d938b512697cfcb77 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Sat, 14 Oct 2017 18:41:50 +0100 Subject: [PATCH 010/344] Add Nav for Configuring Talk section --- docs/_data/nav.yaml | 2 ++ docs/_docs/03-06-product-guide-configuring-talk.md | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 docs/_docs/03-06-product-guide-configuring-talk.md diff --git a/docs/_data/nav.yaml b/docs/_data/nav.yaml index 9a205e8fe..41d0f7bda 100644 --- a/docs/_data/nav.yaml +++ b/docs/_data/nav.yaml @@ -25,6 +25,8 @@ items: url: /trust/ - title: Toxic Comments url: /toxic-comments/ + - title: Configuring Talk + url: /configuring-talk/ - title: Plugins children: - title: Plugins Overview diff --git a/docs/_docs/03-06-product-guide-configuring-talk.md b/docs/_docs/03-06-product-guide-configuring-talk.md new file mode 100644 index 000000000..c4089d16c --- /dev/null +++ b/docs/_docs/03-06-product-guide-configuring-talk.md @@ -0,0 +1,4 @@ +--- +title: Configuring Talk +permalink: /configuring-talk/ +--- From a167b6ce2c88e8234f453afb887493588127ba41 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Mon, 16 Oct 2017 18:20:25 +0100 Subject: [PATCH 011/344] Add Stream Configuration docs --- .../03-06-product-guide-configuring-talk.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/_docs/03-06-product-guide-configuring-talk.md b/docs/_docs/03-06-product-guide-configuring-talk.md index c4089d16c..2d50157aa 100644 --- a/docs/_docs/03-06-product-guide-configuring-talk.md +++ b/docs/_docs/03-06-product-guide-configuring-talk.md @@ -2,3 +2,27 @@ title: Configuring Talk permalink: /configuring-talk/ --- + +## Configuring an Individual Stream + +There are two ways Admins can configure Talk - the first is via the Configure tab on the comment stream. + +### Enable Pre-moderation + +Allows toggling pre-moderation for the current comment stream. + +### Pre-moderation Comments Containing Links + +Allows toggling of pre-moderating comments that have links. + +### Ask Readers a Question & Question Icons + +Admins can choose to Ask Readers a Question in order to help guide the discussion. [Read more about why this is important on our blog](https://blog.coralproject.net/the-empty-box/). + +Admins can also use different icons to display different messaging other than a question on a particular stream, like an announcement or general information about the story. + +### Closing a Stream + +Admins can close a comment stream which disallows new comments. Previous comments will remain displayed on the stream for readers to view. + +## Global Configuration From 988cb870bb87a0e5dffb63c0601fe9dc188e6189 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 17 Oct 2017 10:13:40 +0100 Subject: [PATCH 012/344] Global Configuration docs --- .../03-06-product-guide-configuring-talk.md | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/docs/_docs/03-06-product-guide-configuring-talk.md b/docs/_docs/03-06-product-guide-configuring-talk.md index 2d50157aa..845c6ec19 100644 --- a/docs/_docs/03-06-product-guide-configuring-talk.md +++ b/docs/_docs/03-06-product-guide-configuring-talk.md @@ -19,10 +19,70 @@ Allows toggling of pre-moderating comments that have links. Admins can choose to Ask Readers a Question in order to help guide the discussion. [Read more about why this is important on our blog](https://blog.coralproject.net/the-empty-box/). -Admins can also use different icons to display different messaging other than a question on a particular stream, like an announcement or general information about the story. +There are a selection of icons to display different messaging other than a question on a particular stream, like an announcement, or general information about the story. ### Closing a Stream -Admins can close a comment stream which disallows new comments. Previous comments will remain displayed on the stream for readers to view. +Closing a stream will prevent new comments. Previous comments will remain displayed on the stream for readers to view. + ## Global Configuration + +Global configuration settings are available via Admin > Configure. These settings are site-wide and will affect all of your comment streams. + +### Stream Settings + +#### Limit Comment Length + +A maximum comment length across the site. + +#### Comment Stream Description + +Description text that will appear above every comment stream site-wide. We recommend linking to your Code of Conduct or Community Guidelines. [Read tips on how to write a Code of Conduct here](https://guides.coralproject.net/create-a-code-of-conduct/). + +#### Closed Stream Message + +A message that will display when streams are closed. + +#### Edit Comment Timeframe + +The timeframe in seconds in which commenters have to edit their comment. + +#### Close Comments After + +Default time after which all comment streams will close. + +### Moderation Settings + +#### Require Email Verification + +Require new users to verify their email address prior to commenting. + +#### Enable Pre-moderation + +Turn on pre-moderation across the site, meaning all comments will need to be moderated before they will be displayed. + +#### Pre-moderate Comments Containing Links + +Turn on pre-moderation for comments with links across the site, meaning all comments with links will need to be moderated before they will be displayed. + +#### Banned Words List + +A list of words that will trigger a comment to be automatically Rejected. + +#### Suspect Words List + +A list of words that will trigger a comment to be automatically Reported. Comments with suspect words will display until a moderator takes action on them. + +### Technical Settings + +#### Permitted Domains +A list of domains where your Talk instance is allowed to be embedded. Typical use is `localhost`, `staging.yourdomain.com`, `yourdomain.com`, etc. + +#### Embed Script + +This is the unique Talk script that is to be used to embed Talk on your website. + +#### Custom CSS URL + +The link to your custom stylesheet for Talk. This will override any default styles, so you can make Talk your own! From 3a8ed3033a0a11411addd30e9a26c2a623279e41 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 17 Oct 2017 10:28:56 +0100 Subject: [PATCH 013/344] Fix styling --- .../03-02-product-guide-commenter-features.md | 16 +++++++-------- docs/_docs/03-04-product-guide-trust.md | 4 ++-- .../03-05-product-guide-toxic-comments.md | 6 +++--- docs/_sass/talk.scss | 20 +++++++++++++++---- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/docs/_docs/03-02-product-guide-commenter-features.md b/docs/_docs/03-02-product-guide-commenter-features.md index 81ec5128d..cffd16316 100644 --- a/docs/_docs/03-02-product-guide-commenter-features.md +++ b/docs/_docs/03-02-product-guide-commenter-features.md @@ -17,13 +17,13 @@ We also provide a Facebook auth plugin that supports logging in with Facebook (y Talk supports a standard comment hierarchy. There are top-level (or parent) comments, and then replies to that comment (or children comments). -**Permalinks** +### Permalinks All levels of comments and replies are able to be linked to via permalink. Permalinks are structured using a `commentId` query param: https://domain.com/article-asset-slug/?commentId=0879edd7-1c4f-4918-abd2-a770db446b75 -**Threading** +### Threading Talk supports by default 3 levels of threading, meaning each top-level comment has a depth of 3 replies; replies beyond that are not nested below the 3rd level. You can adjust this using the `TALK_THREADING_LEVEL` configuration variable. We don’t recommend deep threading because it can cause issues with styling, especially on mobile. @@ -37,7 +37,7 @@ You can style threaded comments using these CSS classes: talk-stream-pending-comment ``` -**Automatic Updates** +### Automatic Updates Talk supports real-time loading and updating of comments, via subscriptions (specifically GraphQL Subscriptions); this enables us to not have to refresh to see new comments on a given comment stream. @@ -45,7 +45,7 @@ Talk enables this via “Load More” buttons for both top-level comments (this We’ve decided to go this route in order to make the viewing experience as smooth as possible, so that the feed of comments doesn’t change as you’re reading just because new comments are coming in. This could be especially disruptive on breaking news and/or controversial stories with very active discussions. -**Comment Character Limits** +### Comment Character Limits You can enable Talk to limit the character length for comments, for example, some newsrooms we’ve worked with prefer a limit between 2000 and 5000 characters. Commenters will be alerted that they have gone over that number and won’t be able to submit their comment until they’ve edited it. This can be a useful tool to ensure commenters are concise with their comments. @@ -125,18 +125,18 @@ The My Profile tab is where commenters can go to see their comment history, as w Talk leverages notification and messages on the stream to alert users to important information about their comment or their account. -**Pre-moderation of comments** +### Pre-moderation of comments If a stream is set to Pre-mod, or a commenter’s Trust karma score has fallen to negative, or if for any other reason their comment is being pre-moderated, they will get a notification letting them know this when they post a comment. -**Suspension because of Username** +### Suspension because of Username When a commenter has been suspended because their username is inappropriate, they will see a message at the top of their streams stating this. -**Timed Suspension** +### Timed Suspension When a commenter has been suspended for a block of time (aka a “time-out”), they will see a message at the top of their streams stating this. -**Ban** +### Ban When a commenter has been banned, they will see a message at the top of their streams staging this. diff --git a/docs/_docs/03-04-product-guide-trust.md b/docs/_docs/03-04-product-guide-trust.md index 42587d203..396ecd07d 100644 --- a/docs/_docs/03-04-product-guide-trust.md +++ b/docs/_docs/03-04-product-guide-trust.md @@ -6,7 +6,7 @@ permalink: /trust/ Trust is a set of components within Talk that incorporate automated moderation features based on a user's previous behavior. -### User Karma Score +## User Karma Score Using Trust’s calculations, Talk will automatically pre-moderate comments of users who have a negative karma score. All users start out with a `0` neutral @@ -32,7 +32,7 @@ You can configure your own Trust thresholds by using [TRUST_THRESHOLD]({{"/advan configuration. -### Reliable and Unreliable Flaggers +## Reliable and Unreliable Flaggers Trust also calculates how reliable users are in terms of the comments they report. This information is displayed to moderators in the User History drawer, diff --git a/docs/_docs/03-05-product-guide-toxic-comments.md b/docs/_docs/03-05-product-guide-toxic-comments.md index 56677a421..ac680916d 100644 --- a/docs/_docs/03-05-product-guide-toxic-comments.md +++ b/docs/_docs/03-05-product-guide-toxic-comments.md @@ -21,7 +21,7 @@ Talk (0.8 or 80% is the default), which works like this: Read more about Coral’s take on toxicity [on our blog](https://blog.coralproject.net/toxic-avenging/){:target="_blank"}. -### What is the Perspective API? +## What is the Perspective API? The likely toxicity of a comment is evaluated using scores generated from [Perspective API](http://perspectiveapi.com/){:target="_blank"}. This is part of @@ -48,12 +48,12 @@ We’ve talked with their team on several occasions, and have been impressed by their dedication and commitment to this issue. These are smart people who are trying to improve a broken part of the internet. -### How do I add the Toxic Comments plugin? +## How do I add the Toxic Comments plugin? To enable this behavior, visit the [talk-plugin-toxic-comments]({{ "/additional-plugins/#talk-plugin-toxic-comments" | relative_url }}) plugin documentation. -### Request an API Key +## Request an API Key You can read more about Google's Perspective API and/or request an API key here: [http://perspectiveapi.com/](http://perspectiveapi.com/). diff --git a/docs/_sass/talk.scss b/docs/_sass/talk.scss index 69e039951..e967d10e4 100644 --- a/docs/_sass/talk.scss +++ b/docs/_sass/talk.scss @@ -16,11 +16,25 @@ body { } h1 { - font-size: 1.7em; + font-size: 2rem; } -h2, h3, h4 { +h2 { margin: 1.6em 0 0.8em; + font-size: 1.75rem; + font-weight: 600; +} + +h3 { + margin: 1.6em 0 0.8em; + font-size: 1.5rem; + font-weight: 500; +} + +h4 { + margin: 1.6em 0 0.8em; + font-size: 1.2rem; + font-weight: 500; } .btn-coral { @@ -41,7 +55,6 @@ h2, h3, h4 { } article.configuration { - h2, .toc { @extend .param; } @@ -408,4 +421,3 @@ a.brand { } } } - From a586756ac9ad82289d83377e33bf8ac644988dea Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 17 Oct 2017 12:27:33 +0100 Subject: [PATCH 014/344] Add FAQ and Tutorial sections --- docs/_data/nav.yaml | 8 ++++++++ docs/_docs/05-01-faq.md | 16 ++++++++++++++++ docs/_docs/06-01-developer-tutorials.md | 18 ++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 docs/_docs/05-01-faq.md create mode 100644 docs/_docs/06-01-developer-tutorials.md diff --git a/docs/_data/nav.yaml b/docs/_data/nav.yaml index 41d0f7bda..753607355 100644 --- a/docs/_data/nav.yaml +++ b/docs/_data/nav.yaml @@ -37,3 +37,11 @@ items: url: /additional-plugins/ - title: Plugin Recipes url: /plugin-recipes/ + - title: FAQ + children: + - title: FAQ + url: /faq/ + - title: Developer Tutorials + children: + - title: Developer Tutorials + url: /developer-tutorials/ diff --git a/docs/_docs/05-01-faq.md b/docs/_docs/05-01-faq.md new file mode 100644 index 000000000..c084bc903 --- /dev/null +++ b/docs/_docs/05-01-faq.md @@ -0,0 +1,16 @@ +--- +title: FAQ +permalink: /faq/ +--- + +## How can I get help integrating Talk into my newsroom? + +We're here to help with newsrooms of all sizes. Email our Integration Engineer (jeff@mozillafoundation.org) to set up a meeting. + +## How do I request a feature or submit a bug? + +The best way is to [submit a Github issue](https://github.com/coralproject/talk/issues). Make sure you give plenty of details, our Core Team can usually respond within a few hours on weekdays. + +## How can our dev team contribute to Talk? + +We are lucky to work with newsroom dev teams and individual contributors who span the world, and come from newsrooms of all sizes. You can read our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) to get started, but feel free to reach out to us via Github, or get in touch directly with Jeff via jeff@mozillafoundation.org. diff --git a/docs/_docs/06-01-developer-tutorials.md b/docs/_docs/06-01-developer-tutorials.md new file mode 100644 index 000000000..e03b88e0c --- /dev/null +++ b/docs/_docs/06-01-developer-tutorials.md @@ -0,0 +1,18 @@ +--- +title: Developer Tutorials +permalink: /developer-tutorials/ +--- + +Coming soon! + +**How can I connect Talk to my own app/newsroom/blog’s auth?** + +**How can I display a story’s comment count externally?** + +**How can I add a moderation queue that filters by a tag?** + +**How can I manage Assets with Talk?** + +**How can I style Talk?** + +**How do I submit a PR to Talk?** From 39a209231038956b5d7585649b050b6e006e9023 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 17 Oct 2017 12:32:56 +0100 Subject: [PATCH 015/344] Add faq --- docs/_docs/06-01-developer-tutorials.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/_docs/06-01-developer-tutorials.md b/docs/_docs/06-01-developer-tutorials.md index e03b88e0c..de0065132 100644 --- a/docs/_docs/06-01-developer-tutorials.md +++ b/docs/_docs/06-01-developer-tutorials.md @@ -13,6 +13,8 @@ Coming soon! **How can I manage Assets with Talk?** +**How can I add a new translation for Talk?** + **How can I style Talk?** **How do I submit a PR to Talk?** From 8ce9f2d4bbbcf44ea361526544dc38eac91baca3 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Fri, 20 Oct 2017 11:28:03 +0100 Subject: [PATCH 016/344] Clean up docs, fix links, remove Tutorials section for now --- docs/_data/nav.yaml | 4 ---- .../03-02-product-guide-commenter-features.md | 6 +++--- .../03-03-product-guide-moderator-features.md | 8 ++++---- .../03-06-product-guide-configuring-talk.md | 4 ++-- docs/_docs/06-01-developer-tutorials.md | 20 ------------------- 5 files changed, 9 insertions(+), 33 deletions(-) delete mode 100644 docs/_docs/06-01-developer-tutorials.md diff --git a/docs/_data/nav.yaml b/docs/_data/nav.yaml index 753607355..9138d6603 100644 --- a/docs/_data/nav.yaml +++ b/docs/_data/nav.yaml @@ -41,7 +41,3 @@ items: children: - title: FAQ url: /faq/ - - title: Developer Tutorials - children: - - title: Developer Tutorials - url: /developer-tutorials/ diff --git a/docs/_docs/03-02-product-guide-commenter-features.md b/docs/_docs/03-02-product-guide-commenter-features.md index cffd16316..76cd81cd1 100644 --- a/docs/_docs/03-02-product-guide-commenter-features.md +++ b/docs/_docs/03-02-product-guide-commenter-features.md @@ -25,7 +25,7 @@ https://domain.com/article-asset-slug/?commentId=0879edd7-1c4f-4918-abd2-a770db4 ### Threading -Talk supports by default 3 levels of threading, meaning each top-level comment has a depth of 3 replies; replies beyond that are not nested below the 3rd level. You can adjust this using the `TALK_THREADING_LEVEL` configuration variable. We don’t recommend deep threading because it can cause issues with styling, especially on mobile. +Talk supports by default 3 levels of threading, meaning each top-level comment has a depth of 3 replies; replies beyond that are not nested below the 3rd level. You can adjust this using the [`TALK_THREADING_LEVEL`]({{ "/advanced-configuration/#talk_threading_level" | relative_url }})configuration variable. We don’t recommend deep threading because it can cause issues with styling, especially on mobile. You can style threaded comments using these CSS classes: @@ -51,7 +51,7 @@ You can enable Talk to limit the character length for comments, for example, som ## Comment Reactions -Talk comes with a `respect` button out of the box. Why a “respect” button, you ask? [Read more here](https://mediaengagement.org/research/engagement-buttons/). +Talk comes with a `respect` button out of the box. Why a “respect” button, you ask? [Read more here](https://mediaengagement.org/research/engagement-buttons/){:target="_blank"}. We also have 2 more plugins, `like` and `love`, that you can turn on and experiment with on your own Talk install. @@ -113,7 +113,7 @@ The Staff user badge that shows when a commenter has an Admin, Moderator, or Sta The Featured comment badge shows when a comment has been featured. -Another optional badge is the Subscriber badge (which is available as a [Recipe](/plugin-recipes/#recipe-subscriber). +Another optional badge is the Subscriber badge (which is available as a [Recipe]({{ "/plugin-recipes/#recipe-subscriber" | relative_url}}). Badges are another easy part of Talk to customize by creating a new `tag`, then setting some rules for when it should show, and how the badge should be styled. diff --git a/docs/_docs/03-03-product-guide-moderator-features.md b/docs/_docs/03-03-product-guide-moderator-features.md index 98fa5fc4c..73252de7a 100644 --- a/docs/_docs/03-03-product-guide-moderator-features.md +++ b/docs/_docs/03-03-product-guide-moderator-features.md @@ -53,11 +53,11 @@ The History badge signifies comments that have been flagged because of a user’ **Toxic** -The Toxic badge signifies comments that are above the set Toxicity Probability Threshold. Note you must have `talk-plugin-toxic-comments` enabled. [Read more about Toxic Comments here](LINK). +The Toxic badge signifies comments that are above the set Toxicity Probability Threshold. Note you must have `talk-plugin-toxic-comments` enabled. [Read more about Toxic Comments here]({{ "/toxic-comments/" | relative_url}}). **Suspect** -The Suspect badge signifies comments that contain a Suspect Word. [Read more about Suspect Words here](LINK). +The Suspect badge signifies comments that contain a Suspect Word. **Contains Link** @@ -107,7 +107,7 @@ This shows the % of comments a commenter has had rejected by moderators, or auto **Reports** -This shows if a commenter is a reliable flagger, an unreliable flagger, or a neutral flagger. [Read more about reliable and unreliable flaggers here](LINK). +This shows if a commenter is a reliable flagger, an unreliable flagger, or a neutral flagger. [Read more about reliable and unreliable flaggers here]({{ "/trust/#reliable-and-unreliable-flaggers" | relative_url }}). **Moderating from this View** @@ -145,7 +145,7 @@ Any usernames that have been reported will show in the Reported Usernames sub-ta All your team and commenters show in the People sub-tab. From here, you can manage your team members’ roles (Admins, Moderators, Staff), as well as search for commenters and take action on them (e.g. Ban/Un-ban, Suspend, etc.). ### Configure -See [Configuring Talk](LINK). +See [Configuring Talk]({{ "/configuring-talk/" | relative_url }}). ## Moderating via the Comment Stream diff --git a/docs/_docs/03-06-product-guide-configuring-talk.md b/docs/_docs/03-06-product-guide-configuring-talk.md index 845c6ec19..3225b31bb 100644 --- a/docs/_docs/03-06-product-guide-configuring-talk.md +++ b/docs/_docs/03-06-product-guide-configuring-talk.md @@ -17,7 +17,7 @@ Allows toggling of pre-moderating comments that have links. ### Ask Readers a Question & Question Icons -Admins can choose to Ask Readers a Question in order to help guide the discussion. [Read more about why this is important on our blog](https://blog.coralproject.net/the-empty-box/). +Admins can choose to Ask Readers a Question in order to help guide the discussion. [Read more about why this is important on our blog](https://blog.coralproject.net/the-empty-box/){:target="_blank"}. There are a selection of icons to display different messaging other than a question on a particular stream, like an announcement, or general information about the story. @@ -38,7 +38,7 @@ A maximum comment length across the site. #### Comment Stream Description -Description text that will appear above every comment stream site-wide. We recommend linking to your Code of Conduct or Community Guidelines. [Read tips on how to write a Code of Conduct here](https://guides.coralproject.net/create-a-code-of-conduct/). +Description text that will appear above every comment stream site-wide. We recommend linking to your Code of Conduct or Community Guidelines. [Read tips on how to write a Code of Conduct here](https://guides.coralproject.net/create-a-code-of-conduct/){:target="_blank"}. #### Closed Stream Message diff --git a/docs/_docs/06-01-developer-tutorials.md b/docs/_docs/06-01-developer-tutorials.md deleted file mode 100644 index de0065132..000000000 --- a/docs/_docs/06-01-developer-tutorials.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Developer Tutorials -permalink: /developer-tutorials/ ---- - -Coming soon! - -**How can I connect Talk to my own app/newsroom/blog’s auth?** - -**How can I display a story’s comment count externally?** - -**How can I add a moderation queue that filters by a tag?** - -**How can I manage Assets with Talk?** - -**How can I add a new translation for Talk?** - -**How can I style Talk?** - -**How do I submit a PR to Talk?** From ee548d71802577cb695f149090fc5c0f16a3a6f4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 20 Oct 2017 14:45:12 +0200 Subject: [PATCH 017/344] Disable network logging --- nightwatch-browserstack.conf.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nightwatch-browserstack.conf.js b/nightwatch-browserstack.conf.js index 788bfe97f..604691c81 100644 --- a/nightwatch-browserstack.conf.js +++ b/nightwatch-browserstack.conf.js @@ -23,7 +23,9 @@ const nightwatch_config = { 'browserstack.key': process.env.BROWSERSTACK_KEY, 'browserstack.local': true, 'browserstack.debug': true, - 'browserstack.networkLogs': true, + + // Disable this, as it makes bs slow and brittle. + 'browserstack.networkLogs': false, } }, chrome: { @@ -49,8 +51,6 @@ const nightwatch_config = { ie: { desiredCapabilities: { browser: 'internet explorer', - - // Windows 10 + IE seems to have troubles with the browserstack-local tunnel (10.17.17). os: 'Windows', os_version: '8.1', browser_version: '11', From bef3e3e33c55b139c3e3702712dfc045fea1d201 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 20 Oct 2017 17:19:44 +0200 Subject: [PATCH 018/344] Turn off notifications for flagged and edited --- .../src/routes/Moderation/containers/Moderation.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 8787d77cd..1161ad5dc 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -115,17 +115,14 @@ class ModerationContainer extends Component { document: COMMENT_EDITED_SUBSCRIPTION, variables, updateQuery: (prev, {subscriptionData: {data: {commentEdited: comment}}}) => { - const notifyText = t('modqueue.notify_edited', comment.user.username, prepareNotificationText(comment.body)); - return this.handleCommentChange(prev, comment, notifyText); + return this.handleCommentChange(prev, comment); }, }, { document: COMMENT_FLAGGED_SUBSCRIPTION, variables, updateQuery: (prev, {subscriptionData: {data: {commentFlagged: comment}}}) => { - const user = comment.actions[comment.actions.length - 1].user; - const notifyText = t('modqueue.notify_flagged', user.username, prepareNotificationText(comment.body)); - return this.handleCommentChange(prev, comment, notifyText); + return this.handleCommentChange(prev, comment); }, }, ]; From 5695599f743c3b1173905e000bb45511ad1ad85f Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 20 Oct 2017 17:23:31 +0200 Subject: [PATCH 019/344] Temporarily disable IE in browserstack ci --- scripts/e2e-ci.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/e2e-ci.sh b/scripts/e2e-ci.sh index f1376abcb..1e4f45691 100755 --- a/scripts/e2e-ci.sh +++ b/scripts/e2e-ci.sh @@ -21,7 +21,9 @@ if [[ "${CIRCLE_BRANCH}" == "master" ]]; then # Test using browserstack. browserstack chrome browserstack firefox - browserstack ie + + # temporarily turn off ci, please fix https://www.pivotaltracker.com/story/show/152144406. + # browserstack ie # Safari >= 8 has issues connecting to browserstack-local. Safari < 8 is too old. # browserstack safari From 1bfecdd77c157137e1648223839f555df98d42f7 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 20 Oct 2017 17:55:37 +0200 Subject: [PATCH 020/344] Don't show notification when featuring comment --- .../talk-plugin-featured-comments/client/components/ModTag.js | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/talk-plugin-featured-comments/client/components/ModTag.js b/plugins/talk-plugin-featured-comments/client/components/ModTag.js index 7dfd36f2d..0664f07cd 100644 --- a/plugins/talk-plugin-featured-comments/client/components/ModTag.js +++ b/plugins/talk-plugin-featured-comments/client/components/ModTag.js @@ -32,7 +32,6 @@ export default class ModTag extends React.Component { postTag = async () => { try { await this.props.postTag(); - this.props.notify('success', t('talk-plugin-featured-comments.notify_self_featured', this.props.comment.user.username)); } catch(err) { this.props.notify('error', getErrorMessages(err)); From e0713a5ec40444de1f96db499bc85b4ad5efd2fa Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Fri, 20 Oct 2017 17:56:24 +0100 Subject: [PATCH 021/344] Update auth section --- docs/_docs/03-02-product-guide-commenter-features.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_docs/03-02-product-guide-commenter-features.md b/docs/_docs/03-02-product-guide-commenter-features.md index 76cd81cd1..07c64bd0f 100644 --- a/docs/_docs/03-02-product-guide-commenter-features.md +++ b/docs/_docs/03-02-product-guide-commenter-features.md @@ -7,9 +7,9 @@ permalink: /commenter-features/ There are 2 ways that newsrooms can support signup/login functionality with Talk: -Use Talk’s auth plugin out of the box (supports account registration with username and password, as well as features like forgot password) +*Use Talk’s auth plugin out of the box (supports account registration with username and password, as well as features like forgot password) -Create their own auth plugin (you can use ours as a template) to integrate with your own auth systems +*Create their own auth plugin to integrate with your own auth systems We also provide a Facebook auth plugin that supports logging in with Facebook (you must provide your own Facebook App ID and Secret, which you can read more about here: https://developers.facebook.com) From c1b954965f02e2f75296558063ed5e848b9f8f35 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 20 Oct 2017 11:40:55 -0600 Subject: [PATCH 022/344] Fixed incorrect plugin naming --- docs/_docs/04-02-default-plugins.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/_docs/04-02-default-plugins.md b/docs/_docs/04-02-default-plugins.md index aff17ebcc..5c987172d 100644 --- a/docs/_docs/04-02-default-plugins.md +++ b/docs/_docs/04-02-default-plugins.md @@ -98,17 +98,17 @@ Requires: [talk-plugin-viewing-options](#talk-plugin-viewing-options){:.param} Provides a sort for the newest comments first. -## talk-plugin-most-respected +## talk-plugin-sort-most-respected -Source: [plugins/talk-plugin-most-respected](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-most-respected){:target="_blank"} +Source: [plugins/talk-plugin-sort-most-respected](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-sort-most-respected){:target="_blank"} Requires: [talk-plugin-viewing-options](#talk-plugin-viewing-options){:.param}, [talk-plugin-respect](#talk-plugin-respect){:.param} Provides a sort for the comments with the most `respect` reactions first. -## talk-plugin-most-replied +## talk-plugin-sort-most-replied -Source: [plugins/talk-plugin-most-replied](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-most-replied){:target="_blank"} +Source: [plugins/talk-plugin-sort-most-replied](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-sort-most-replied){:target="_blank"} Requires: [talk-plugin-viewing-options](#talk-plugin-viewing-options){:.param} From a9cb206415f5dbfdf07e6f031a16fdd98ffcfae8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 20 Oct 2017 13:14:00 -0600 Subject: [PATCH 023/344] formatting --- .../03-02-product-guide-commenter-features.md | 136 +++++++++++++----- .../03-03-product-guide-moderator-features.md | 86 +++++++---- .../03-06-product-guide-configuring-talk.md | 35 +++-- docs/_docs/05-01-faq.md | 3 +- 4 files changed, 184 insertions(+), 76 deletions(-) diff --git a/docs/_docs/03-02-product-guide-commenter-features.md b/docs/_docs/03-02-product-guide-commenter-features.md index 76cd81cd1..d36fc2b27 100644 --- a/docs/_docs/03-02-product-guide-commenter-features.md +++ b/docs/_docs/03-02-product-guide-commenter-features.md @@ -9,134 +9,192 @@ There are 2 ways that newsrooms can support signup/login functionality with Talk Use Talk’s auth plugin out of the box (supports account registration with username and password, as well as features like forgot password) -Create their own auth plugin (you can use ours as a template) to integrate with your own auth systems +Create their own auth plugin to integrate with your own auth systems -We also provide a Facebook auth plugin that supports logging in with Facebook (you must provide your own Facebook App ID and Secret, which you can read more about here: https://developers.facebook.com) +We also provide a Facebook auth plugin that supports logging in with Facebook (you must provide your own Facebook App ID and Secret, which you can read more about here: [https://developers.facebook.com](https://developers.facebook.com){:target="_blank"}) ## Comments and Replies -Talk supports a standard comment hierarchy. There are top-level (or parent) comments, and then replies to that comment (or children comments). +Talk supports a standard comment hierarchy. There are top-level (or parent) comments, and then replies to that comment (or children comments). ### Permalinks All levels of comments and replies are able to be linked to via permalink. Permalinks are structured using a `commentId` query param: -https://domain.com/article-asset-slug/?commentId=0879edd7-1c4f-4918-abd2-a770db446b75 +```text +https://?commentId= +``` +{:.no-copy} ### Threading -Talk supports by default 3 levels of threading, meaning each top-level comment has a depth of 3 replies; replies beyond that are not nested below the 3rd level. You can adjust this using the [`TALK_THREADING_LEVEL`]({{ "/advanced-configuration/#talk_threading_level" | relative_url }})configuration variable. We don’t recommend deep threading because it can cause issues with styling, especially on mobile. +Talk supports by default 3 levels of threading, meaning each top-level comment +has a depth of 3 replies; replies beyond that are not nested below the 3rd +level. You can adjust this using the +[TALK_THREADING_LEVEL]({{ "/advanced-configuration/#talk_threading_level" | relative_url }}){:.param} +configuration variable. We don’t recommend deep threading because it can cause +issues with styling, especially on mobile. You can style threaded comments using these CSS classes: - ``` - talk-stream-comment-wrapper-level-${depth} - talk-stream-comment - talk-stream-comment-level-${depth} - talk-stream-highlighted-comment - talk-stream-pending-comment ``` +talk-stream-comment-wrapper-level-${depth} +talk-stream-comment +talk-stream-comment-level-${depth} +talk-stream-highlighted-comment +talk-stream-pending-comment +``` +{:.no-copy} ### Automatic Updates -Talk supports real-time loading and updating of comments, via subscriptions (specifically GraphQL Subscriptions); this enables us to not have to refresh to see new comments on a given comment stream. +Talk supports real-time loading and updating of comments, via subscriptions +(specifically GraphQL Subscriptions); this enables us to not have to refresh to +see new comments on a given comment stream. -Talk enables this via “Load More” buttons for both top-level comments (this button appears at the top of the stream), and within conversation threads (this button appears in situ for replies). +Talk enables this via “Load More” buttons for both top-level comments (this +button appears at the top of the stream), and within conversation threads (this +button appears in situ for replies). -We’ve decided to go this route in order to make the viewing experience as smooth as possible, so that the feed of comments doesn’t change as you’re reading just because new comments are coming in. This could be especially disruptive on breaking news and/or controversial stories with very active discussions. +We’ve decided to go this route in order to make the viewing experience as smooth +as possible, so that the feed of comments doesn’t change as you’re reading just +because new comments are coming in. This could be especially disruptive on +breaking news and/or controversial stories with very active discussions. ### Comment Character Limits -You can enable Talk to limit the character length for comments, for example, some newsrooms we’ve worked with prefer a limit between 2000 and 5000 characters. Commenters will be alerted that they have gone over that number and won’t be able to submit their comment until they’ve edited it. This can be a useful tool to ensure commenters are concise with their comments. +You can enable Talk to limit the character length for comments, for example, +some newsrooms we’ve worked with prefer a limit between 2000 and 5000 +characters. Commenters will be alerted that they have gone over that number and +won’t be able to submit their comment until they’ve edited it. This can be a +useful tool to ensure commenters are concise with their comments. ## Comment Reactions -Talk comes with a `respect` button out of the box. Why a “respect” button, you ask? [Read more here](https://mediaengagement.org/research/engagement-buttons/){:target="_blank"}. +Talk comes with a `respect` button out of the box. Why a “respect” button, you +ask? +[Read more here](https://mediaengagement.org/research/engagement-buttons/){:target="_blank"}. -We also have 2 more plugins, `like` and `love`, that you can turn on and experiment with on your own Talk install. +We also have 2 more plugins, `like` and `love`, that you can turn on and +experiment with on your own Talk install. -And our plugin architecture makes it easy to create your own custom reaction buttons too. +And our plugin architecture makes it easy to create your own custom reaction +buttons too. ## Reporting Comments -Readers can report comments if they feel they’re unsuitable. They can choose one of the following reasons: +Readers can report comments if they feel they’re unsuitable. They can choose one +of the following reasons: * This comment is offensive * This looks like an ad/marketing * I don’t agree with this comment * Other -They can also include more information and this shows for moderators in the Flag Detail area on the comments in the moderation queues. +They can also include more information and this shows for moderators in the Flag +Detail area on the comments in the moderation queues. -Comments that are reported go to the Reported queue, with the exception of “I don’t agree with this comment”. This option is a useful way to let other readers vent their frustration, but since just disagreeing with something doesn’t mean it’s not suitable, we leave it be. +Comments that are reported go to the Reported queue, with the exception of “I +don’t agree with this comment”. This option is a useful way to let other readers +vent their frustration, but since just disagreeing with something doesn’t mean +it’s not suitable, we leave it be. ## Reporting Usernames -Usernames can also be reported by readers, if the username is inappropriate or offensive. They can choose one of the following reasons: +Usernames can also be reported by readers, if the username is inappropriate or +offensive. They can choose one of the following reasons: * This username is offensive * I don't like this username * This user is impersonating * This looks like an ad/marketing -Reported usernames go to the Reported Usernames queue which is located in the Community tab. If a username is rejected by a moderator, the commenter is prompted to change their username and they are suspended from commenting, replying or reacting to comments until they do so. They receive an email, and also a message at the top of their comment streams that let’s them know they’re suspended. +Reported usernames go to the Reported Usernames queue which is located in the +Community tab. If a username is rejected by a moderator, the commenter is +prompted to change their username and they are suspended from commenting, +replying or reacting to comments until they do so. They receive an email, and +also a message at the top of their comment streams that let’s them know they’re +suspended. -If the commenter changes their username, it goes back to the Reported Usernames queue for approval. If the updated username is accepted by a moderator, the commenter is no longer suspended and continue interacting with the community. If the username is rejected, the commenter remains suspended until they change their username to something appropriate. +If the commenter changes their username, it goes back to the Reported Usernames +queue for approval. If the updated username is accepted by a moderator, the +commenter is no longer suspended and continue interacting with the community. If +the username is rejected, the commenter remains suspended until they change +their username to something appropriate. -Approved usernames that are reported do not show up in the Reported Usernames queues any longer, since they have been specifically OK’ed by a moderator. +Approved usernames that are reported do not show up in the Reported Usernames +queues any longer, since they have been specifically OK’ed by a moderator. ## Ignoring Users -Commenters can ignore other commenters and essentially mute them entirely from the comment platform. Commenters can manage their ignored users list in their My Profile tab. +Commenters can ignore other commenters and essentially mute them entirely from +the comment platform. Commenters can manage their ignored users list in their My +Profile tab. ## Featured Comments -Moderators can feature comments that they want to highlight and recommend to their community. Featured comments show up on a separate tab, that is the default for the comment stream. Featured comments within the stream show a Featured badge. +Moderators can feature comments that they want to highlight and recommend to +their community. Featured comments show up on a separate tab, that is the +default for the comment stream. Featured comments within the stream show a +Featured badge. ## Sorting the Stream -Readers can sort the stream in 4 ways based on their viewing preferencet: +Readers can sort the stream in 4 ways based on their viewing preferences: * Oldest first * Newest first -* Most respect first (or most liked, most loved, etc., depending on what reactions you use) +* Most respect first (or most liked, most loved, etc., depending on what + reactions you use) * Most replied first We also make it easy to add more sorts via custom plugins. ## Badges -Badges differentiate users and comments on the stream. By default, Talk has two badges. +Badges differentiate users and comments on the stream. By default, Talk has two +badges. -The Staff user badge that shows when a commenter has an Admin, Moderator, or Staff role. +The Staff user badge that shows when a commenter has an Admin, Moderator, or +Staff role. The Featured comment badge shows when a comment has been featured. -Another optional badge is the Subscriber badge (which is available as a [Recipe]({{ "/plugin-recipes/#recipe-subscriber" | relative_url}}). +Another optional badge is the Subscriber badge (which is available as a +[Recipe]({{ "/plugin-recipes/#recipe-subscriber" | relative_url}}). -Badges are another easy part of Talk to customize by creating a new `tag`, then setting some rules for when it should show, and how the badge should be styled. +Badges are another easy part of Talk to customize by creating a new `tag`, then +setting some rules for when it should show, and how the badge should be styled. ## My Profile -The My Profile tab is where commenters can go to see their comment history, as well as reactions and replies to their comments. They can also see their email address associated with Talk, and manage their Ignored Users list here. +The My Profile tab is where commenters can go to see their comment history, as +well as reactions and replies to their comments. They can also see their email +address associated with Talk, and manage their Ignored Users list here. ## Notifications & Error Messaging -Talk leverages notification and messages on the stream to alert users to important information about their comment or their account. +Talk leverages notification and messages on the stream to alert users to +important information about their comment or their account. ### Pre-moderation of comments -If a stream is set to Pre-mod, or a commenter’s Trust karma score has fallen to negative, or if for any other reason their comment is being pre-moderated, they will get a notification letting them know this when they post a comment. +If a stream is set to Pre-mod, or a commenter’s Trust karma score has fallen to +negative, or if for any other reason their comment is being pre-moderated, they +will get a notification letting them know this when they post a comment. ### Suspension because of Username -When a commenter has been suspended because their username is inappropriate, they will see a message at the top of their streams stating this. +When a commenter has been suspended because their username is inappropriate, +they will see a message at the top of their streams stating this. ### Timed Suspension -When a commenter has been suspended for a block of time (aka a “time-out”), they will see a message at the top of their streams stating this. +When a commenter has been suspended for a block of time (aka a “time-out”), they +will see a message at the top of their streams stating this. ### Ban -When a commenter has been banned, they will see a message at the top of their streams staging this. +When a commenter has been banned, they will see a message at the top of their +streams staging this. diff --git a/docs/_docs/03-03-product-guide-moderator-features.md b/docs/_docs/03-03-product-guide-moderator-features.md index 73252de7a..45516b1a5 100644 --- a/docs/_docs/03-03-product-guide-moderator-features.md +++ b/docs/_docs/03-03-product-guide-moderator-features.md @@ -5,15 +5,21 @@ permalink: /moderator-features/ ## The Talk Admin -The Admin is your moderators will moderate your comments, and your Admins will configure and manage the different parts of Talk. +The Admin is your moderators will moderate your comments, and your Admins will +configure and manage the different parts of Talk. ### Dashboard -The Dashboard provides real-time information to moderators so they know at a glance where they should direct their attention. It shows what articles are receiving the most reports and where the most active conversations are happening. +The Dashboard provides real-time information to moderators so they know at a +glance where they should direct their attention. It shows what articles are +receiving the most reports and where the most active conversations are +happening. ### Moderate -This is the tab where Moderators will spend the majority of their time. They can choose (via the dropdown) which story they would like to moderate, or moderate site-wide. +This is the tab where Moderators will spend the majority of their time. They can +choose (via the dropdown) which story they would like to moderate, or moderate +site-wide. #### Default Mod Queues @@ -31,13 +37,15 @@ The Approved queue contains all approved comments. **Rejected** -The Rejected queue contains all comments that have been rejected, either manually by moderators or automatically, e.g. they have used a banned word. +The Rejected queue contains all comments that have been rejected, either +manually by moderators or automatically, e.g. they have used a banned word. **All** -The All queue contains all comments that have been submitted either article or site-wide. +The All queue contains all comments that have been submitted either article or +site-wide. -#### Moderation Badges +#### Moderation Badges **Pre-mod** @@ -49,11 +57,14 @@ The User badge signifies comments that have been reported by another user. **History** -The History badge signifies comments that have been flagged because of a user’s history. +The History badge signifies comments that have been flagged because of a user’s +history. **Toxic** -The Toxic badge signifies comments that are above the set Toxicity Probability Threshold. Note you must have `talk-plugin-toxic-comments` enabled. [Read more about Toxic Comments here]({{ "/toxic-comments/" | relative_url}}). +The Toxic badge signifies comments that are above the set Toxicity Probability +Threshold. Note you must have [talk-plugin-toxic-comments]({{ "/additional-plugins/#talk-plugin-toxic-comments" | relative_url }}){:.param} enabled. +[Read more about Toxic Comments here]({{ "/toxic-comments/" | relative_url}}). **Suspect** @@ -61,11 +72,13 @@ The Suspect badge signifies comments that contain a Suspect Word. **Contains Link** -The Contains Link badge signifies a comment that contains a link, which can sometimes mean it is a spam or ad comment. +The Contains Link badge signifies a comment that contains a link, which can +sometimes mean it is a spam or ad comment. **Flag Details View** -At the bottom of each comment in the moderation queues, you can see more information about a comment’s flags by clicking on More Detail. +At the bottom of each comment in the moderation queues, you can see more +information about a comment’s flags by clicking on More Detail. #### Moderator Actions @@ -79,19 +92,25 @@ Rejecting a comment removes the comment from the stream. **Feature** -Featuring a comment adds that comment to the Featured Comments tab on the stream. +Featuring a comment adds that comment to the Featured Comments tab on the +stream. **Suspend User** -Suspending a user allows a moderator to give a commenter a “time-out”; during that time they won’t be allowed to post comments or react to comments. +Suspending a user allows a moderator to give a commenter a “time-out”; during +that time they won’t be allowed to post comments or react to comments. **Ban User** -Banning a user allows a moderator to permanently disallow a commenter to interact with their community. The commenter’s previous comments will remain on the site. This action can only be un-done manually by a moderator. +Banning a user allows a moderator to permanently disallow a commenter to +interact with their community. The commenters previous comments will remain on +the site. This action can only be un-done manually by a moderator. #### Viewing a User’s Comment History -In order to get an idea of what sort of a commenter someone is, moderators can click on the commenter’s username in any moderation queue and see details about their history. +In order to get an idea of what sort of a commenter someone is, moderators can +click on the commenters username in any moderation queue and see details about +their history. **Username, Email and Member Since Date** @@ -99,23 +118,27 @@ This shows the basic details about a commenter. **Total Comments** -This shows the number of comments that a commenter has made that currently display on the site. +This shows the number of comments that a commenter has made that currently +display on the site. **Reject Rate** -This shows the % of comments a commenter has had rejected by moderators, or automatically. +This shows the % of comments a commenter has had rejected by moderators, or +automatically. **Reports** -This shows if a commenter is a reliable flagger, an unreliable flagger, or a neutral flagger. [Read more about reliable and unreliable flaggers here]({{ "/trust/#reliable-and-unreliable-flaggers" | relative_url }}). +This shows if a commenter is a reliable flagger, an unreliable flagger, or a +neutral flagger. [Read more about reliable and unreliable flaggers here]({{"/trust/#reliable-and-unreliable-flaggers" | relative_url }}). **Moderating from this View** -Talk also allows you to moderate a commenter’s recent comments from this view. +Talk also allows you to moderate a commenters recent comments from this view. #### Keyboard Shortcuts -Talk also supports a number of keyboard shortcuts that moderators can leverage to moderate quickly: +Talk also supports a number of keyboard shortcuts that moderators can leverage +to moderate quickly: | Shortcut | Action | | -------- | ------------------------------- | @@ -130,23 +153,36 @@ Talk also supports a number of keyboard shortcuts that moderators can leverage t ### Stories -In the Stories tab moderators can view all the stories that have Talk comments embedded on them, as well as be able to Open or Close comment streams on stories. +In the Stories tab moderators can view all the stories that have Talk comments +embedded on them, as well as be able to Open or Close comment streams on +stories. ### Community -The Community tab houses everything having to do with your team and your commenters. +The Community tab houses everything having to do with your team and your +commenters. #### Moderating Usernames -Any usernames that have been reported will show in the Reported Usernames sub-tab. Moderators can approve usernames if they’re suitable, or reject a username. If a username is rejected, the commenter will be notified that they need to change their username; until they do, they will be suspended from Talk. The updated username then again appears in this queue for a decision by moderators. +Any usernames that have been reported will show in the Reported Usernames +sub-tab. Moderators can approve usernames if they’re suitable, or reject a +username. If a username is rejected, the commenter will be notified that they +need to change their username; until they do, they will be suspended from Talk. +The updated username then again appears in this queue for a decision by +moderators. #### Managing People & Roles -All your team and commenters show in the People sub-tab. From here, you can manage your team members’ roles (Admins, Moderators, Staff), as well as search for commenters and take action on them (e.g. Ban/Un-ban, Suspend, etc.). -### Configure +All your team and commenters show in the People sub-tab. From here, you can +manage your team members’ roles (Admins, Moderators, Staff), as well as search +for commenters and take action on them (e.g. Ban/Un-ban, Suspend, etc.). ### +Configure See [Configuring Talk]({{ "/configuring-talk/" | relative_url }}). ## Moderating via the Comment Stream -Moderators can also choose to moderate comments in situ. If you are logged in as a Moderator or Admin, you will see a caret dropdown on each comment that allows you to Approve, Reject, or Feature comments, or Ban a User directly from the comment stream. +Moderators can also choose to moderate comments in situ. If you are logged in as +a Moderator or Admin, you will see a caret dropdown on each comment that allows +you to Approve, Reject, or Feature comments, or Ban a User directly from the +comment stream. diff --git a/docs/_docs/03-06-product-guide-configuring-talk.md b/docs/_docs/03-06-product-guide-configuring-talk.md index 3225b31bb..ba6647435 100644 --- a/docs/_docs/03-06-product-guide-configuring-talk.md +++ b/docs/_docs/03-06-product-guide-configuring-talk.md @@ -5,7 +5,8 @@ permalink: /configuring-talk/ ## Configuring an Individual Stream -There are two ways Admins can configure Talk - the first is via the Configure tab on the comment stream. +There are two ways Admins can configure Talk - the first is via the Configure +tab on the comment stream. ### Enable Pre-moderation @@ -17,18 +18,23 @@ Allows toggling of pre-moderating comments that have links. ### Ask Readers a Question & Question Icons -Admins can choose to Ask Readers a Question in order to help guide the discussion. [Read more about why this is important on our blog](https://blog.coralproject.net/the-empty-box/){:target="_blank"}. +Admins can choose to Ask Readers a Question in order to help guide the +discussion. [Read more about why this is important on our blog](https://blog.coralproject.net/the-empty-box/){:target="_blank"}. -There are a selection of icons to display different messaging other than a question on a particular stream, like an announcement, or general information about the story. +There are a selection of icons to display different messaging other than a +question on a particular stream, like an announcement, or general information +about the story. ### Closing a Stream -Closing a stream will prevent new comments. Previous comments will remain displayed on the stream for readers to view. +Closing a stream will prevent new comments. Previous comments will remain +displayed on the stream for readers to view. ## Global Configuration -Global configuration settings are available via Admin > Configure. These settings are site-wide and will affect all of your comment streams. +Global configuration settings are available via Admin > Configure. These +settings are site-wide and will affect all of your comment streams. ### Stream Settings @@ -38,7 +44,8 @@ A maximum comment length across the site. #### Comment Stream Description -Description text that will appear above every comment stream site-wide. We recommend linking to your Code of Conduct or Community Guidelines. [Read tips on how to write a Code of Conduct here](https://guides.coralproject.net/create-a-code-of-conduct/){:target="_blank"}. +Description text that will appear above every comment stream site-wide. We +recommend linking to your Code of Conduct or Community Guidelines. [Read tips on how to write a Code of Conduct here](https://guides.coralproject.net/create-a-code-of-conduct/){:target="_blank"}. #### Closed Stream Message @@ -60,11 +67,13 @@ Require new users to verify their email address prior to commenting. #### Enable Pre-moderation -Turn on pre-moderation across the site, meaning all comments will need to be moderated before they will be displayed. +Turn on pre-moderation across the site, meaning all comments will need to be +moderated before they will be displayed. #### Pre-moderate Comments Containing Links -Turn on pre-moderation for comments with links across the site, meaning all comments with links will need to be moderated before they will be displayed. +Turn on pre-moderation for comments with links across the site, meaning all +comments with links will need to be moderated before they will be displayed. #### Banned Words List @@ -72,12 +81,15 @@ A list of words that will trigger a comment to be automatically Rejected. #### Suspect Words List -A list of words that will trigger a comment to be automatically Reported. Comments with suspect words will display until a moderator takes action on them. +A list of words that will trigger a comment to be automatically Reported. +Comments with suspect words will display until a moderator takes action on them. ### Technical Settings #### Permitted Domains -A list of domains where your Talk instance is allowed to be embedded. Typical use is `localhost`, `staging.yourdomain.com`, `yourdomain.com`, etc. + +A list of domains where your Talk instance is allowed to be embedded. Typical +use is `localhost`, `staging.yourdomain.com`, `yourdomain.com`, etc. #### Embed Script @@ -85,4 +97,5 @@ This is the unique Talk script that is to be used to embed Talk on your website. #### Custom CSS URL -The link to your custom stylesheet for Talk. This will override any default styles, so you can make Talk your own! +The link to your custom stylesheet for Talk. This will override any default +styles, so you can make Talk your own! diff --git a/docs/_docs/05-01-faq.md b/docs/_docs/05-01-faq.md index c084bc903..06d0b0669 100644 --- a/docs/_docs/05-01-faq.md +++ b/docs/_docs/05-01-faq.md @@ -5,7 +5,8 @@ permalink: /faq/ ## How can I get help integrating Talk into my newsroom? -We're here to help with newsrooms of all sizes. Email our Integration Engineer (jeff@mozillafoundation.org) to set up a meeting. +We're here to help with newsrooms of all sizes. Email our Integration Engineer +([jeff@mozillafoundation.org](mailto:jeff@mozillafoundation.org)) to set up a meeting. ## How do I request a feature or submit a bug? From 9c38bab83bbcfaf28bec7a1c558a2c22f6234019 Mon Sep 17 00:00:00 2001 From: Facu Date: Fri, 20 Oct 2017 17:01:52 -0300 Subject: [PATCH 024/344] notify error messages --- client/coral-admin/src/components/UserDetail.css | 11 ++++++----- client/coral-admin/src/components/UserDetail.js | 10 ++++++++-- client/coral-admin/src/containers/UserDetail.js | 4 +++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index 4deb35550..8b86a133c 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -131,11 +131,12 @@ .bulkAction:last-child { margin-left: -10px; } - span { - align-self: center; - font-weight: 500; - margin-left: 15px; - } +} + +.selectedCommentsInfo { + align-self: center; + font-weight: 500; + margin-left: 15px; } .loadMore>button { diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 2ff5efc1e..1c6362791 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -12,6 +12,7 @@ import capitalize from 'lodash/capitalize'; import {getReliability} from 'coral-framework/utils/user'; import ApproveButton from './ApproveButton'; import RejectButton from './RejectButton'; +import {getErrorMessages} from 'coral-framework/utils'; export default class UserDetail extends React.Component { @@ -33,7 +34,8 @@ export default class UserDetail extends React.Component { activeTab: PropTypes.string.isRequired, selectedCommentIds: PropTypes.array.isRequired, viewUserDetail: PropTypes.any.isRequired, - loadMore: PropTypes.any.isRequired + loadMore: PropTypes.any.isRequired, + notify: PropTypes.func.isRequired } rejectThenReload = async (info) => { @@ -44,6 +46,7 @@ export default class UserDetail extends React.Component { // TODO: handle error. console.error(err); + this.props.notify('error', getErrorMessages(err)); } } @@ -55,6 +58,7 @@ export default class UserDetail extends React.Component { // TODO: handle error. console.error(err); + this.props.notify('error', getErrorMessages(err)); } } @@ -66,6 +70,7 @@ export default class UserDetail extends React.Component { // TODO: handle error. console.error(err); + this.props.notify('error', getErrorMessages(err)); } } @@ -77,6 +82,7 @@ export default class UserDetail extends React.Component { // TODO: handle error. console.error(err); + this.props.notify('error', getErrorMessages(err)); } } @@ -191,7 +197,7 @@ export default class UserDetail extends React.Component { onClick={this.bulkRejectThenReload} minimal /> - {selectedCommentIds.length} comments selected + {selectedCommentIds.length} comments selected
    ) } diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 3383af1b5..47d8e1c79 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -16,6 +16,7 @@ import { import {withSetCommentStatus} from 'coral-framework/graphql/mutations'; import UserDetailComment from './UserDetailComment'; import update from 'immutability-helper'; +import {notify} from 'coral-framework/actions/notification'; const commentConnectionFragment = gql` fragment CoralAdmin_Moderation_CommentConnection on CommentConnection { @@ -190,7 +191,8 @@ const mapDispatchToProps = (dispatch) => ({ toggleSelectCommentInUserDetail, viewUserDetail, hideUserDetail, - toggleSelectAllCommentInUserDetail + toggleSelectAllCommentInUserDetail, + notify }, dispatch) }); From dae8dc0509954da55898c0315bf6b73461eb9001 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 23 Oct 2017 09:51:34 -0600 Subject: [PATCH 025/344] patched config bug --- config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js b/config.js index a67273c61..368fcf0e3 100644 --- a/config.js +++ b/config.js @@ -119,7 +119,7 @@ const CONFIG = { //------------------------------------------------------------------------------ // Port to bind to. - PORT: process.env.TALK_PORT || process.env.PORT || process.env.NODE_ENV === 'test' ? '3001' : '3000', + PORT: process.env.TALK_PORT || process.env.PORT || (process.env.NODE_ENV === 'test' ? '3001' : '3000'), // The URL for this Talk Instance as viewable from the outside. ROOT_URL: process.env.TALK_ROOT_URL || null, From d75ae3aca8db5f0fa5cd5387426ef56fc42ad0b7 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 23 Oct 2017 18:46:41 +0200 Subject: [PATCH 026/344] Revive resend verification feature --- client/coral-embed-stream/src/actions/auth.js | 23 ++-- .../coral-embed-stream/src/constants/auth.js | 1 + .../coral-embed-stream/src/reducers/auth.js | 15 ++- locales/en.yml | 2 + .../client/components/ResendVerification.css | 15 +++ .../client/components/ResendVerification.js | 43 +++++++ .../client/components/SignDialog.js | 13 +- .../client/components/SignInContainer.js | 20 +-- .../client/components/SignInContent.js | 114 ++++++++---------- .../talk-plugin-auth/client/translations.yml | 6 +- 10 files changed, 150 insertions(+), 102 deletions(-) create mode 100644 plugins/talk-plugin-auth/client/components/ResendVerification.css create mode 100644 plugins/talk-plugin-auth/client/components/ResendVerification.js diff --git a/client/coral-embed-stream/src/actions/auth.js b/client/coral-embed-stream/src/actions/auth.js index b96d6f459..22d46a768 100644 --- a/client/coral-embed-stream/src/actions/auth.js +++ b/client/coral-embed-stream/src/actions/auth.js @@ -23,6 +23,10 @@ export const hideSignInDialog = () => (dispatch) => { dispatch({type: actions.HIDE_SIGNIN_DIALOG}); }; +export const resetSignInDialog = () => (dispatch) => { + dispatch({type: actions.HIDE_SIGNIN_DIALOG}); +}; + export const focusSignInDialog = () => ({ type: actions.FOCUS_SIGNIN_DIALOG, }); @@ -94,8 +98,9 @@ export const cleanState = () => ({ // Sign In Actions -const signInRequest = () => ({ - type: actions.FETCH_SIGNIN_REQUEST +const signInRequest = (email) => ({ + type: actions.FETCH_SIGNIN_REQUEST, + email, }); const signInFailure = (error) => ({ @@ -122,7 +127,7 @@ export const handleAuthToken = (token) => (dispatch, _, {storage}) => { export const fetchSignIn = (formData) => { return (dispatch, _, {rest}) => { - dispatch(signInRequest()); + dispatch(signInRequest(formData.email)); return rest('/auth/local', {method: 'POST', body: formData}) .then(({token}) => { @@ -144,8 +149,7 @@ export const fetchSignIn = (formData) => { // invalid credentials dispatch(signInFailure(t('error.email_password'), error.metadata)); } else { - const str = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch(signInFailure(str)); + dispatch(signInFailure(error)); } }); }; @@ -349,8 +353,9 @@ const verifyEmailSuccess = () => ({ type: actions.VERIFY_EMAIL_SUCCESS }); -const verifyEmailFailure = () => ({ - type: actions.VERIFY_EMAIL_FAILURE +const verifyEmailFailure = (error) => ({ + type: actions.VERIFY_EMAIL_FAILURE, + error, }); export const requestConfirmEmail = (email) => (dispatch, getState, {rest}) => { @@ -366,8 +371,8 @@ export const requestConfirmEmail = (email) => (dispatch, getState, {rest}) => { }) .catch((error) => { console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch(verifyEmailFailure(errorMessage)); + dispatch(verifyEmailFailure(error)); + throw error; }); }; diff --git a/client/coral-embed-stream/src/constants/auth.js b/client/coral-embed-stream/src/constants/auth.js index 324f75f44..bb2ea6c15 100644 --- a/client/coral-embed-stream/src/constants/auth.js +++ b/client/coral-embed-stream/src/constants/auth.js @@ -53,3 +53,4 @@ export const UPDATE_USERNAME = 'UPDATE_USERNAME'; export const SET_REQUIRE_EMAIL_VERIFICATION = 'SET_REQUIRE_EMAIL_VERIFICATION'; export const SET_REDIRECT_URI = 'SET_REDIRECT_URI'; +export const RESET_SIGNIN_DIALOG = 'RESET_SIGNIN_DIALOG'; diff --git a/client/coral-embed-stream/src/reducers/auth.js b/client/coral-embed-stream/src/reducers/auth.js index 1e18ddffa..679e8d858 100644 --- a/client/coral-embed-stream/src/reducers/auth.js +++ b/client/coral-embed-stream/src/reducers/auth.js @@ -10,7 +10,7 @@ const initialState = { showCreateUsernameDialog: false, checkedInitialLogin: false, view: 'SIGNIN', - error: '', + error: null, passwordRequestSuccess: null, passwordRequestFailure: null, emailVerificationFailure: false, @@ -45,14 +45,15 @@ export default function auth (state = initialState, action) { showSignInDialog: true, signInDialogFocus: true, }; - case actions.HIDE_SIGNIN_DIALOG : + case actions.RESET_SIGNIN_DIALOG: + case actions.HIDE_SIGNIN_DIALOG: return { ...state, isLoading: false, showSignInDialog: false, signInDialogFocus: false, view: 'SIGNIN', - error: '', + error: null, passwordRequestFailure: null, passwordRequestSuccess: null, emailVerificationFailure: false, @@ -74,7 +75,7 @@ export default function auth (state = initialState, action) { return { ...state, showCreateUsernameDialog: false, - error: '', + error: null, }; case actions.CREATE_USERNAME_FAILURE: return { @@ -92,6 +93,7 @@ export default function auth (state = initialState, action) { case actions.FETCH_SIGNIN_REQUEST: return { ...state, + email: action.email, isLoading: true, }; case actions.CHECK_LOGIN_FAILURE: @@ -120,6 +122,7 @@ export default function auth (state = initialState, action) { isLoading: false, error: action.error, user: null, + view: action.error.translation_key === 'EMAIL_NOT_VERIFIED' ? 'RESEND_VERIFICATION' : state.view, }; case actions.FETCH_SIGNUP_FACEBOOK_REQUEST: return { @@ -175,7 +178,7 @@ export default function auth (state = initialState, action) { case actions.VALID_FORM: return { ...state, - error: '', + error: null, }; case actions.FETCH_FORGOT_PASSWORD_SUCCESS: return { @@ -200,7 +203,7 @@ export default function auth (state = initialState, action) { case actions.VERIFY_EMAIL_FAILURE: return { ...state, - emailVerificationFailure: true, + emailVerificationFailure: action.error, emailVerificationLoading: false, }; case actions.VERIFY_EMAIL_REQUEST: diff --git a/locales/en.yml b/locales/en.yml index 2d560fedc..76a434dfc 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -194,8 +194,10 @@ en: NO_SPECIAL_CHARACTERS: "Usernames can contain letters numbers and _ only" PASSWORD_LENGTH: "Password is too short" PROFANITY_ERROR: "Usernames must not contain profanity. Please contact the administrator if you believe this to be in error." + RATE_LIMIT_EXCEEDED: "Rate limit exceeded" USERNAME_IN_USE: "Username already in use" USERNAME_REQUIRED: "Must input a username" + EMAIL_NOT_VERIFIED: "E-mail address not verified" EDIT_WINDOW_ENDED: "You can no longer edit this comment. The time window to do so has expired." EDIT_USERNAME_NOT_AUTHORIZED: "You do not have permission to update your username." SAME_USERNAME_PROVIDED: "You must submit a different username." diff --git a/plugins/talk-plugin-auth/client/components/ResendVerification.css b/plugins/talk-plugin-auth/client/components/ResendVerification.css new file mode 100644 index 000000000..dc59a9ff1 --- /dev/null +++ b/plugins/talk-plugin-auth/client/components/ResendVerification.css @@ -0,0 +1,15 @@ +.header { + margin-bottom: 20px; + text-align: center; + font-size: 1.2em; +} + +.notVerified { + padding: 10px; + margin-bottom: 20px; + border-radius: 2px; + border: solid 1px #dddd00; + background: #FFFF9C; + color: #777700; +} + diff --git a/plugins/talk-plugin-auth/client/components/ResendVerification.js b/plugins/talk-plugin-auth/client/components/ResendVerification.js new file mode 100644 index 000000000..b345577a9 --- /dev/null +++ b/plugins/talk-plugin-auth/client/components/ResendVerification.js @@ -0,0 +1,43 @@ +import React from 'react'; +import {Button, Spinner, Success, Alert} from 'plugin-api/beta/client/components/ui'; +import PropTypes from 'prop-types'; +import styles from './ResendVerification.css'; +import t from 'coral-framework/services/i18n'; + +class ResendVerification extends React.Component { + render() { + const {resendVerification, error, loading, success, email} = this.props; + return ( +
    +

    + {t('sign_in.email_verify_cta')} +

    + + {error && + + {error.translation_key ? t(`error.${error.translation_key}`) : error.toString()} + } +
    + {t('error.email_not_verified', email)} +
    +
    + + {loading && } + {success && } +
    +
    + ); + } +} + +ResendVerification.propTypes = { + resendVerification: PropTypes.bool.isRequired, + error: PropTypes.object, + loading: PropTypes.bool, + success: PropTypes.bool, + email: PropTypes.string.isRequired, +}; + +export default ResendVerification; diff --git a/plugins/talk-plugin-auth/client/components/SignDialog.js b/plugins/talk-plugin-auth/client/components/SignDialog.js index 542f53f98..652148d75 100644 --- a/plugins/talk-plugin-auth/client/components/SignDialog.js +++ b/plugins/talk-plugin-auth/client/components/SignDialog.js @@ -5,13 +5,22 @@ import styles from './styles.css'; import SignInContent from './SignInContent'; import SignUpContent from './SignUpContent'; import ForgotContent from './ForgotContent'; +import ResendVerification from './ResendVerification'; -const SignDialog = ({open, view, hideSignInDialog, ...props}) => ( +const SignDialog = ({open, view, resetSignInDialog, ...props}) => ( - × + {view !== 'SIGNIN' && ×} {view === 'SIGNIN' && } {view === 'SIGNUP' && } {view === 'FORGOT' && } + {view === 'RESEND_VERIFICATION' && + } ); diff --git a/plugins/talk-plugin-auth/client/components/SignInContainer.js b/plugins/talk-plugin-auth/client/components/SignInContainer.js index 011ce45f1..c22ad72c4 100644 --- a/plugins/talk-plugin-auth/client/components/SignInContainer.js +++ b/plugins/talk-plugin-auth/client/components/SignInContainer.js @@ -15,6 +15,7 @@ import { fetchSignUpFacebook, fetchForgotPassword, requestConfirmEmail, + resetSignInDialog, facebookCallback, invalidForm, validForm, @@ -31,7 +32,6 @@ class SignInContainer extends React.Component { password: '', confirmPassword: '' }, - emailToBeResent: '', errors: {}, showErrors: false }; @@ -80,26 +80,15 @@ class SignInContainer extends React.Component { ); }; - handleChangeEmail = (e) => { - const {value} = e.target; - this.setState({emailToBeResent: value}); - }; - - handleResendVerification = (e) => { - e.preventDefault(); + resendVerification = () => { this.props - .requestConfirmEmail( - this.state.emailToBeResent, - ) + .requestConfirmEmail(this.props.auth.email) .then(() => { setTimeout(() => { // allow success UI to be shown for a second, and then close the modal - this.props.hideSignInDialog(); + this.props.resetSignInDialog(); }, 2500); - }) - .catch((err) => { - console.error(err); }); }; @@ -194,6 +183,7 @@ const mapDispatchToProps = (dispatch) => requestConfirmEmail, changeView, hideSignInDialog, + resetSignInDialog, invalidForm, validForm }, diff --git a/plugins/talk-plugin-auth/client/components/SignInContent.js b/plugins/talk-plugin-auth/client/components/SignInContent.js index d1b4caec1..956176b32 100644 --- a/plugins/talk-plugin-auth/client/components/SignInContent.js +++ b/plugins/talk-plugin-auth/client/components/SignInContent.js @@ -1,16 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Button, TextField, Spinner, Success, Alert} from 'plugin-api/beta/client/components/ui'; +import {Button, TextField, Spinner, Alert} from 'plugin-api/beta/client/components/ui'; import styles from './styles.css'; import t from 'coral-framework/services/i18n'; const SignInContent = ({ handleChange, - handleChangeEmail, - emailToBeResent, - handleResendVerification, - emailVerificationLoading, - emailVerificationSuccess, formData, changeView, handleSignIn, @@ -21,71 +16,56 @@ const SignInContent = ({

    - {auth.emailVerificationFailure - ? t('sign_in.email_verify_cta') - : t('sign_in.sign_in_to_join')} + {t('sign_in.sign_in_to_join')}

    - {auth.error && {auth.error}} - {auth.emailVerificationFailure - ?
    -

    {t('sign_in.request_new_verify_email')}

    + {auth.error && + + {auth.error.translation_key ? t(`error.${auth.error.translation_key}`) : auth.error.toString()} + } +
    +
    + +
    +
    +

    + {t('sign_in.or')} +

    +
    + - - {emailVerificationLoading && } - {emailVerificationSuccess && } + +
    + {!auth.isLoading + ? + : } +
    - :
    -
    - -
    -
    -

    - {t('sign_in.or')} -

    -
    -
    - - -
    - {!auth.isLoading - ? - : } -
    - -
    } +
    changeView('FORGOT')}> @@ -111,12 +91,12 @@ SignInContent.propTypes = { }).isRequired, fetchSignInFacebook: PropTypes.func.isRequired, handleSignIn: PropTypes.func.isRequired, + handleChange: PropTypes.func.isRequired, changeView: PropTypes.func.isRequired, emailVerificationLoading: PropTypes.bool.isRequired, emailVerificationSuccess: PropTypes.bool.isRequired, - handleResendVerification: PropTypes.func.isRequired, - handleChangeEmail: PropTypes.func.isRequired, - emailToBeResent: PropTypes.string.isRequired + resendVerification: PropTypes.func.isRequired, + formData: PropTypes.object, }; export default SignInContent; diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index 189c3d9da..f410ec2d3 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -1,7 +1,7 @@ en: sign_in: email_verify_cta: "Please verify your email address." - request_new_verify_email: "Request another email:" + request_new_verify_email: "Request another email" verify_email: "Thank you for creating an account! We sent an email to the address you provided to verify your account." verify_email2: "You must verify your account before engaging with the community." not_you: "Not you?" @@ -45,7 +45,7 @@ en: es: sign_in: email_verify_cta: "Por favor confirme su correo." - request_new_verify_email: "Enviar otro correo:" + request_new_verify_email: "Enviar otro correo" verify_email: "¡Gracias por crear una cuenta! Le enviamos un correo a la direcció\ n que dio para confirmar su cuenta." verify_email2: "Debe confirmarla antes de poder involucrarse en la comunidad." @@ -92,7 +92,7 @@ es: fr: sign_in: email_verify_cta: "Merci de vérifier votre adresse e-mail." - request_new_verify_email: "Demander un nouvel envoi d'e-mail :" + request_new_verify_email: "Demander un nouvel envoi d'e-mail" verify_email: "Merci d'avoir créé un compte ! Nous avons envoyé un courrier électronique à l'adresse que vous avez fournie pour vérifier votre adresse e-mail." verify_email2: "Vous devez vérifier votre adresse e-mail avant de vous engager auprès de la communauté." not_you: "Pas vous ?" From fac028c22b0ef945a707947e5ff28174164c3f5a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 23 Oct 2017 10:50:29 -0600 Subject: [PATCH 027/344] update to docker build process --- Dockerfile.onbuild | 8 +++++++- docs/_docs/01-02-installation-from-docker.md | 9 +++++++-- docs/_docs/02-02-advanced-configuration.md | 12 ++++++++++++ docs/_sass/talk.scss | 3 ++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Dockerfile.onbuild b/Dockerfile.onbuild index b9cf8ce87..a4bff7095 100644 --- a/Dockerfile.onbuild +++ b/Dockerfile.onbuild @@ -1,5 +1,11 @@ FROM coralproject/talk:latest +# Setup the build arguments +ONBUILD ARG TALK_THREADING_LEVEL=3 +ONBUILD ARG TALK_DEFAULT_STREAM_TAB=all +ONBUILD ARG TALK_DEFAULT_LANG=en +ONBUILD ARG TALK_PLUGINS_JSON + # Bundle app source ONBUILD COPY . /usr/src/app @@ -8,4 +14,4 @@ ONBUILD COPY . /usr/src/app # clear out the development dependancies again. After this we of course need to # clear out the yarn cache, this saves quite a lot of size. ONBUILD RUN cli plugins reconcile && \ - yarn build + yarn build \ No newline at end of file diff --git a/docs/_docs/01-02-installation-from-docker.md b/docs/_docs/01-02-installation-from-docker.md index 0126c0901..fc699f127 100644 --- a/docs/_docs/01-02-installation-from-docker.md +++ b/docs/_docs/01-02-installation-from-docker.md @@ -95,10 +95,12 @@ FROM coralproject/talk:latest-onbuild And running the following to build the docker image: ```bash -docker build -t my-awesome-talk-image . +docker build -t my-awesome-talk-image --build-arg TALK_DEFAULT_LANG=es . ``` -Don't forget to replace `my-awesome-talk-image` with your own image name. +Don't forget to replace `my-awesome-talk-image` with your own image name, and +specify your build variables with the `--build-arg`. Refer to [Dockerfile.onbuild](https://github.com/coralproject/talk/blob/master/Dockerfile.onbuild){:target="_blank"} for the +available build variables. {: .code-aside} This accomplishes a lot: @@ -108,6 +110,9 @@ This accomplishes a lot: 2. Installs any new dependencies that were required by any new plugins. 3. Builds the new static bundles so that they are ready to serve when the image is running. +4. Specifies a build time variable [TALK_DEFAULT_LANG]({{ "/advanced-configuration/#talk_default_lang" | relative_url }}){:.param}. Refer +to [Dockerfile.onbuild](https://github.com/coralproject/talk/blob/master/Dockerfile.onbuild){:target="_blank"} for the +available build variables. This means that you can create a repository for your organization that simply includes the above `Dockerfile`, a directory of `plugins`, and a `plugins.json` diff --git a/docs/_docs/02-02-advanced-configuration.md b/docs/_docs/02-02-advanced-configuration.md index de2ab0e68..09dccce0b 100644 --- a/docs/_docs/02-02-advanced-configuration.md +++ b/docs/_docs/02-02-advanced-configuration.md @@ -26,10 +26,18 @@ Configure the duration for which comment counts are cached for, parsed by ### TALK_DEFAULT_LANG +This is a **Build Variable** and must be consumed during build. If using the +[Docker-onbuild]({{ "/installation-from-docker/#onbuild" | relative_url }}) +image you can specify it with `--build-arg TALK_DEFAULT_LANG=en`. + Specify the default translation language. (Default `en`) ### TALK_DEFAULT_STREAM_TAB +This is a **Build Variable** and must be consumed during build. If using the +[Docker-onbuild]({{ "/installation-from-docker/#onbuild" | relative_url }}) +image you can specify it with `--build-arg TALK_DEFAULT_STREAM_TAB=all`. + Specify the default stream tab in the admin. (Default `all`) ### TALK_DISABLE_AUTOFLAG_SUSPECT_WORDS @@ -397,6 +405,10 @@ CDN/Storage url. (Defaults to value of ### TALK_THREADING_LEVEL +This is a **Build Variable** and must be consumed during build. If using the +[Docker-onbuild]({{ "/installation-from-docker/#onbuild" | relative_url }}) +image you can specify it with `--build-arg TALK_THREADING_LEVEL=3`. + Specify the maximum depth of the comment thread. (Default `3`) **Note that a high value for `TALK_THREADING_LEVEL` will result in large diff --git a/docs/_sass/talk.scss b/docs/_sass/talk.scss index 85e5f1c93..28bfea81c 100644 --- a/docs/_sass/talk.scss +++ b/docs/_sass/talk.scss @@ -263,7 +263,8 @@ pre { p a:not(.plain-link) { @extend .coral-link; } - ul:not(.toc__menu) li a { + ul:not(.toc__menu) li a, + ol li a { @extend .coral-link; } } From f004573ed2f4ffa6e83a4deb4e365e046623a8ff Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 23 Oct 2017 23:47:05 -0300 Subject: [PATCH 028/344] Replacing Pagination Component --- client/coral-ui/components/Pager.css | 19 ----------- client/coral-ui/components/Pager.js | 43 ------------------------- client/coral-ui/components/Paginate.css | 31 ++++++++++++++++++ client/coral-ui/components/Paginate.js | 34 +++++++++++++++++++ client/coral-ui/index.js | 2 +- 5 files changed, 66 insertions(+), 63 deletions(-) delete mode 100644 client/coral-ui/components/Pager.css delete mode 100644 client/coral-ui/components/Pager.js create mode 100644 client/coral-ui/components/Paginate.css create mode 100644 client/coral-ui/components/Paginate.js diff --git a/client/coral-ui/components/Pager.css b/client/coral-ui/components/Pager.css deleted file mode 100644 index 42559d11d..000000000 --- a/client/coral-ui/components/Pager.css +++ /dev/null @@ -1,19 +0,0 @@ -.pager { - text-align: center; - - li { - display: inline-block; - margin-right: 5px; - color: white; - height: 30px; - text-align: center; - vertical-align: middle; - line-height: 30px; - width: 30px; - } -} - -.current { - background: #696969; - box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); -} diff --git a/client/coral-ui/components/Pager.js b/client/coral-ui/components/Pager.js deleted file mode 100644 index 8f560593e..000000000 --- a/client/coral-ui/components/Pager.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import styles from './Pager.css'; - -const Rows = (curr, total, onClickHandler) => Array.from(Array(total)).map((e, i) => -
  • onClickHandler(i + 1)}> - {i + 1} -
  • -); - -const Pager = ({totalPages, page, onNewPageHandler}) => ( -
    -
      - { - (totalPages > page && totalPages > 1) ? -
    • onNewPageHandler(page - 1)}> - Prev -
    • - : - null - } - {Rows(page, totalPages, onNewPageHandler)} - { - (page < totalPages && totalPages > 1) ? -
    • onNewPageHandler(page + 1)}> - Next -
    • - : - null - } -
    -
    -); - -Pager.propTypes = { - totalPages: PropTypes.number.isRequired, - page: PropTypes.number.isRequired, -}; - -export default Pager; diff --git a/client/coral-ui/components/Paginate.css b/client/coral-ui/components/Paginate.css new file mode 100644 index 000000000..dbb25b844 --- /dev/null +++ b/client/coral-ui/components/Paginate.css @@ -0,0 +1,31 @@ +.container { + text-align: center; +} + +.page, .previous, .next { + display: inline-block; + margin-right: 5px; + height: 30px; + text-align: center; + vertical-align: middle; + line-height: 30px; + width: 30px; + list-style: none; +} + +.pageLink, .previousLink, .nextLink { + color: #696969; + cursor: default; +} + +.previousLink, .nextLink { + font-size: 1.8em; +} + +.active { + background-color: #696969; + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); + a { + color: white; + } +} diff --git a/client/coral-ui/components/Paginate.js b/client/coral-ui/components/Paginate.js new file mode 100644 index 000000000..1d7d55398 --- /dev/null +++ b/client/coral-ui/components/Paginate.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ReactPaginate from 'react-paginate'; +import styles from './Paginate.css'; +import Icon from './Icon'; + +const Paginate = ({pageCount, onPageChange}) => ( + ...
    } + breakClassName={styles.break} + containerClassName={styles.container} + pageClassName={styles.page} + pageLinkClassName={styles.pageLink} + activeClassName={styles.active} + previousLabel={} + previousClassName={styles.previous} + previousLinkClassName={styles.previousLink} + nextLabel={} + nextClassName={styles.next} + nextLinkClassName={styles.nextLink} + /> +); + +Paginate.propTypes = { + pageCount: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, +}; + +export default Paginate; diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index c688857a1..a2d8049f7 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -18,7 +18,7 @@ export {default as Item} from './components/Item'; export {default as Card} from './components/Card'; export {default as TextField} from './components/TextField'; export {default as Success} from './components/Success'; -export {default as Pager} from './components/Pager'; +export {default as Paginate} from './components/Paginate'; export {default as Wizard} from './components/Wizard'; export {default as WizardNav} from './components/WizardNav'; export {default as SnackBar} from './components/SnackBar'; From d363c977dca91aadaeef3f769d788418f408e7c6 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 23 Oct 2017 23:47:43 -0300 Subject: [PATCH 029/344] Refactor --- client/coral-admin/src/actions/community.js | 20 ++-- client/coral-admin/src/constants/community.js | 7 +- client/coral-admin/src/reducers/community.js | 26 ++--- .../routes/Community/components/Community.css | 4 + .../routes/Community/components/Community.js | 101 ++++------------- .../src/routes/Community/components/People.js | 53 ++++----- .../src/routes/Community/components/Table.js | 28 ++++- .../routes/Community/containers/Community.js | 48 +-------- .../src/routes/Community/containers/People.js | 102 ++++++++++++++++++ .../src/routes/Community/containers/Table.js | 43 -------- .../src/routes/Stories/components/Stories.js | 6 +- 11 files changed, 202 insertions(+), 236 deletions(-) create mode 100644 client/coral-admin/src/routes/Community/components/Community.css create mode 100644 client/coral-admin/src/routes/Community/containers/People.js delete mode 100644 client/coral-admin/src/routes/Community/containers/Table.js diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 921f0a0a2..b7102b8ff 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -1,9 +1,9 @@ import queryString from 'query-string'; import { - FETCH_COMMENTERS_REQUEST, - FETCH_COMMENTERS_SUCCESS, - FETCH_COMMENTERS_FAILURE, + FETCH_USERS_REQUEST, + FETCH_USERS_SUCCESS, + FETCH_USERS_FAILURE, SORT_UPDATE, COMMENTERS_NEW_PAGE, SET_ROLE, @@ -16,13 +16,13 @@ import { import t from 'coral-framework/services/i18n'; -export const fetchAccounts = (query = {}) => (dispatch, _, {rest}) => { - dispatch(requestFetchAccounts()); +export const fetchUsers = (query = {}) => (dispatch, _, {rest}) => { + dispatch(requestFetchUsers()); rest(`/users?${queryString.stringify(query)}`) .then(({result, page, count, limit, totalPages}) =>{ dispatch({ - type: FETCH_COMMENTERS_SUCCESS, - accounts: result, + type: FETCH_USERS_SUCCESS, + users: result, page, count, limit, @@ -32,12 +32,12 @@ export const fetchAccounts = (query = {}) => (dispatch, _, {rest}) => { .catch((error) => { console.error(error); const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: FETCH_COMMENTERS_FAILURE, error: errorMessage}); + dispatch({type: FETCH_USERS_FAILURE, error: errorMessage}); }); }; -const requestFetchAccounts = () => ({ - type: FETCH_COMMENTERS_REQUEST +const requestFetchUsers = () => ({ + type: FETCH_USERS_REQUEST }); export const updateSorting = (sort) => ({ diff --git a/client/coral-admin/src/constants/community.js b/client/coral-admin/src/constants/community.js index be11e9b0c..dc11572ed 100644 --- a/client/coral-admin/src/constants/community.js +++ b/client/coral-admin/src/constants/community.js @@ -1,6 +1,7 @@ -export const FETCH_COMMENTERS_REQUEST = 'FETCH_COMMENTERS_REQUEST'; -export const FETCH_COMMENTERS_SUCCESS = 'FETCH_COMMENTERS_SUCCESS'; -export const FETCH_COMMENTERS_FAILURE = 'FETCH_COMMENTERS_FAILURE'; +export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST'; +export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS'; +export const FETCH_USERS_FAILURE = 'FETCH_USERS_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/reducers/community.js b/client/coral-admin/src/reducers/community.js index 9f724c8d6..31fb6aa17 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -1,7 +1,7 @@ import { - FETCH_COMMENTERS_REQUEST, - FETCH_COMMENTERS_FAILURE, - FETCH_COMMENTERS_SUCCESS, + FETCH_USERS_REQUEST, + FETCH_USERS_SUCCESS, + FETCH_USERS_FAILURE, SORT_UPDATE, SET_ROLE, SET_COMMENTER_STATUS, @@ -15,7 +15,7 @@ const initialState = { community: {}, isFetchingPeople: false, errorPeople: '', - accounts: [], + users: [], fieldPeople: 'created_at', ascPeople: false, totalPagesPeople: 0, @@ -27,19 +27,19 @@ const initialState = { export default function community (state = initialState, action) { switch (action.type) { - case FETCH_COMMENTERS_REQUEST : + case FETCH_USERS_REQUEST : return { ...state, isFetchingPeople: true, }; - case FETCH_COMMENTERS_FAILURE : + case FETCH_USERS_FAILURE : return { ...state, isFetchingPeople: false, errorPeople: action.error, }; - case FETCH_COMMENTERS_SUCCESS : { - const {accounts, type, page, count, limit, totalPages, ...rest} = action; // eslint-disable-line + case FETCH_USERS_SUCCESS : { + const {users, type, page, count, limit, totalPages, ...rest} = action; // eslint-disable-line return { ...state, isFetchingPeople: false, @@ -49,27 +49,27 @@ export default function community (state = initialState, action) { limitPeople: limit, totalPagesPeople: totalPages, ...rest, - accounts, // Sets to normal array + users, // Sets to normal array }; } case SET_ROLE : { - const commenters = state.accounts; + const commenters = state.users; const idx = commenters.findIndex((el) => el.id === action.id); commenters[idx].roles[0] = action.role; return { ...state, - accounts: commenters.map((id) => id), + users: commenters.map((id) => id), }; } case SET_COMMENTER_STATUS: { - const commenters = state.accounts; + const commenters = state.users; const idx = commenters.findIndex((el) => el.id === action.id); commenters[idx].status = action.status; return { ...state, - accounts: commenters.map((id) => id), + users: commenters.map((id) => id), }; } diff --git a/client/coral-admin/src/routes/Community/components/Community.css b/client/coral-admin/src/routes/Community/components/Community.css new file mode 100644 index 000000000..8f7641418 --- /dev/null +++ b/client/coral-admin/src/routes/Community/components/Community.css @@ -0,0 +1,4 @@ +.container { + max-width: 1280px; + margin: 0 auto; +} \ No newline at end of file diff --git a/client/coral-admin/src/routes/Community/components/Community.js b/client/coral-admin/src/routes/Community/components/Community.js index bf300a166..1b0c6cc2a 100644 --- a/client/coral-admin/src/routes/Community/components/Community.js +++ b/client/coral-admin/src/routes/Community/components/Community.js @@ -1,78 +1,18 @@ import React, {Component} from 'react'; - -import CommunityMenu from './CommunityMenu'; -import People from './People'; -import FlaggedAccounts from '../containers/FlaggedAccounts'; -import RejectUsernameDialog from './RejectUsernameDialog'; import PropTypes from 'prop-types'; +import styles from './Community.css'; +import People from '../containers/People'; +import CommunityMenu from './CommunityMenu'; +import RejectUsernameDialog from './RejectUsernameDialog'; +import FlaggedAccounts from '../containers/FlaggedAccounts'; -export default class Community extends Component { - - state = { - searchValue: '', - timer: null - }; - - onKeyDownHandler = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - this.search(); - } - } - - onSearchChange = (e) => { - const value = e.target.value; - this.setState((prevState) => { - prevState.searchValue = value; - clearTimeout(prevState.timer); - const fetchAccounts = this.props.fetchAccounts; - prevState.timer = setTimeout(() => { - fetchAccounts({value}); - }, 350); - return prevState; - }); - } - - onHeaderClickHandler = (sort) => { - this.props.updateSorting(sort); - this.search(); - } - - onNewPageHandler = (page) => { - this.props.newPage(page); - this.search({page}); - } - - search(query = {}) { - const {community} = this.props; - - this.props.fetchAccounts({ - value: this.state.searchValue, - field: community.fieldPeople, - asc: community.ascPeople, - ...query - }); - } - - getTabContent(searchValue, props) { - const {community} = props; - const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path; +class Community extends Component { + renderTab() { + const {route, community, ...props} = this.props; + const activeTab = route.path === ':id' ? 'flagged' : route.path; if (activeTab === 'people') { - return ( - - ); + return ; } return ( @@ -82,37 +22,34 @@ export default class Community extends Component { root={this.props.root} />
    ); } render() { - const {searchValue} = this.state; - const tab = this.getTabContent(searchValue, this.props); const {root: {flaggedUsernamesCount}} = this.props; return (
    -
    {tab}
    +
    + {this.renderTab()} +
    ); } } Community.propTypes = { - community: PropTypes.object, - fetchAccounts: PropTypes.func, - hideRejectUsernameDialog: PropTypes.func, - updateSorting: PropTypes.func, - newPage: PropTypes.func, route: PropTypes.object, - rejectUsername: PropTypes.func, + community: PropTypes.object, data: PropTypes.object, - root: PropTypes.object + root: PropTypes.object, }; + +export default Community; diff --git a/client/coral-admin/src/routes/Community/components/People.js b/client/coral-admin/src/routes/Community/components/People.js index b0524f273..3edf620db 100644 --- a/client/coral-admin/src/routes/Community/components/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -1,33 +1,24 @@ import React from 'react'; - import styles from './styles.css'; -import Table from '../containers/Table'; -import {Pager, Icon} from 'coral-ui'; +import Table from './Table'; +import {Paginate, Icon} from 'coral-ui'; import EmptyCard from '../../../components/EmptyCard'; import t from 'coral-framework/services/i18n'; + import PropTypes from 'prop-types'; -const tableHeaders = [ - { - title: t('community.username_and_email'), - field: 'username' - }, - { - title: t('community.account_creation_date'), - field: 'created_at' - }, - { - title: t('community.status'), - field: 'status' - }, - { - title: t('community.newsroom_role'), - field: 'role' - } -]; +const People = (props) => { + const { + users = [], + searchValue, + onSearchChange, + onHeaderClickHandler, + onNewPageHandler, + totalPages, + } = props; + + const hasResults = !!users.length; -const People = ({commenters, searchValue, onSearchChange, ...props}) => { - const hasResults = !!commenters.length; return (
    @@ -47,16 +38,15 @@ const People = ({commenters, searchValue, onSearchChange, ...props}) => { { hasResults ? : {t('community.no_results')} } - @@ -64,7 +54,8 @@ const People = ({commenters, searchValue, onSearchChange, ...props}) => { }; People.propTypes = { - commenters: PropTypes.array, + onHeaderClickHandler: PropTypes.func, + users: PropTypes.array, searchValue: PropTypes.string, onSearchChange: PropTypes.func, totalPages: PropTypes.number, diff --git a/client/coral-admin/src/routes/Community/components/Table.js b/client/coral-admin/src/routes/Community/components/Table.js index 28b20b82f..9071e0575 100644 --- a/client/coral-admin/src/routes/Community/components/Table.js +++ b/client/coral-admin/src/routes/Community/components/Table.js @@ -5,7 +5,26 @@ import PropTypes from 'prop-types'; import {Dropdown, Option} from 'coral-ui'; import cn from 'classnames'; -const Table = ({headers, commenters, onHeaderClickHandler, onRoleChange, onCommenterStatusChange, viewUserDetail}) => ( +const headers = [ + { + title: t('community.username_and_email'), + field: 'username' + }, + { + title: t('community.account_creation_date'), + field: 'created_at' + }, + { + title: t('community.status'), + field: 'status' + }, + { + title: t('community.newsroom_role'), + field: 'role' + } +]; + +const Table = ({users, onHeaderClickHandler, onRoleChange, onCommenterStatusChange, viewUserDetail}) => (
    @@ -21,7 +40,7 @@ const Table = ({headers, commenters, onHeaderClickHandler, onRoleChange, onComme - {commenters.map((row, i)=> ( + {users.map((row, i)=> (
    @@ -57,9 +76,8 @@ const Table = ({headers, commenters, onHeaderClickHandler, onRoleChange, onComme ); Table.propTypes = { - headers: PropTypes.array, - commenters: PropTypes.array, - onHeaderClickHandler: PropTypes.func, + users: PropTypes.array, + onHeaderClickHandler: PropTypes.func.isRequired, onRoleChange: PropTypes.func, onCommenterStatusChange: PropTypes.func, viewUserDetail: PropTypes.func, diff --git a/client/coral-admin/src/routes/Community/containers/Community.js b/client/coral-admin/src/routes/Community/containers/Community.js index 6e20a3167..4cf9eeb8a 100644 --- a/client/coral-admin/src/routes/Community/containers/Community.js +++ b/client/coral-admin/src/routes/Community/containers/Community.js @@ -1,65 +1,21 @@ -import React, {Component} from 'react'; import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; -import PropTypes from 'prop-types'; import {compose, gql} from 'react-apollo'; import withQuery from 'coral-framework/hocs/withQuery'; import {getDefinitionName} from 'coral-framework/utils'; import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations'; import FlaggedAccounts from '../containers/FlaggedAccounts'; import FlaggedUser from '../containers/FlaggedUser'; - -import { - fetchAccounts, - updateSorting, - newPage, - hideRejectUsernameDialog -} from '../../../actions/community'; - +import {hideRejectUsernameDialog} from '../../../actions/community'; import Community from '../components/Community'; -class CommunityContainer extends Component { - - componentWillMount() { - this.props.fetchAccounts({}); - } - - render() { - return ; - } -} const mapStateToProps = (state) => ({ community: state.community, }); -CommunityContainer.propTypes = { - community: PropTypes.object, - fetchAccounts: PropTypes.func, - hideRejectUsernameDialog: PropTypes.func, - updateSorting: PropTypes.func, - newPage: PropTypes.func, - route: PropTypes.object, - rejectUsername: PropTypes.func, - data: PropTypes.object, - root: PropTypes.object -}; - const mapDispatchToProps = (dispatch) => bindActionCreators({ - fetchAccounts, hideRejectUsernameDialog, - updateSorting, - newPage, }, dispatch); const withData = withQuery(gql` @@ -89,4 +45,4 @@ export default compose( withSetUserStatus, withRejectUsername, withData -)(CommunityContainer); +)(Community); diff --git a/client/coral-admin/src/routes/Community/containers/People.js b/client/coral-admin/src/routes/Community/containers/People.js new file mode 100644 index 000000000..fb383917f --- /dev/null +++ b/client/coral-admin/src/routes/Community/containers/People.js @@ -0,0 +1,102 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import People from '../components/People'; +import PropTypes from 'prop-types'; + +import {viewUserDetail} from '../../../actions/userDetail'; + +import { + fetchUsers, + updateSorting, + newPage, + hideRejectUsernameDialog, + setCommenterStatus, + setRole, +} from '../../../actions/community'; + +class PeopleContainer extends React.Component { + state = { + searchValue: '', + timer: null + }; + + componentWillMount() { + this.props.fetchUsers(); + } + + onKeyDownHandler = (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + this.search(); + } + } + + onSearchChange = (e) => { + const value = e.target.value; + + this.setState((prevState) => { + prevState.searchValue = value; + clearTimeout(prevState.timer); + const fetchAccounts = this.props.fetchUsers; + prevState.timer = setTimeout(() => { + fetchAccounts({value}); + }, 350); + return prevState; + }); + } + + onHeaderClickHandler = (sort) => { + this.props.updateSorting(sort); + this.search(); + } + + onNewPageHandler = ({selected}) => { + const page = selected + 1; + this.props.newPage(page); + this.search({page}); + } + + search(query = {}) { + const {community} = this.props; + + this.props.fetchUsers({ + value: this.state.searchValue, + field: community.fieldPeople, + asc: community.ascPeople, + ...query + }); + } + render() { + return ; + } +} + +PeopleContainer.propTypes = { + newPage: PropTypes.func, + fetchUsers: PropTypes.func, + updateSorting: PropTypes.func, + setRole: PropTypes.func, + setCommenterStatus: PropTypes.func, + community: PropTypes.object, +}; + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + newPage, + fetchUsers, + updateSorting, + hideRejectUsernameDialog, + setCommenterStatus, + setRole, + viewUserDetail, + }, dispatch); + +export default connect(null, mapDispatchToProps)(PeopleContainer); diff --git a/client/coral-admin/src/routes/Community/containers/Table.js b/client/coral-admin/src/routes/Community/containers/Table.js deleted file mode 100644 index 086a56638..000000000 --- a/client/coral-admin/src/routes/Community/containers/Table.js +++ /dev/null @@ -1,43 +0,0 @@ -import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import {setRole, setCommenterStatus} from '../../../actions/community'; -import Table from '../components/Table'; -import {viewUserDetail} from '../../../actions/userDetail'; -import PropTypes from 'prop-types'; - -class TableContainer extends Component { - - constructor (props) { - super(props); - } - - render () { - return ; - } -} - -TableContainer.propTypes = { - setRole: PropTypes.func, - setCommenterStatus: PropTypes.func, - commenters: PropTypes.array, -}; - -const mapStateToProps = (state) => ({ - commenters: state.community.accounts, -}); - -const mapDispatchToProps = (dispatch) => - bindActionCreators({ - setCommenterStatus, - setRole, - viewUserDetail, - }, dispatch); - -export default connect(mapStateToProps, mapDispatchToProps)(TableContainer); - diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index 746c733d7..57caa2744 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import {Link} from 'react-router'; import PropTypes from 'prop-types'; import sortBy from 'lodash/sortBy'; -import {Dropdown, Option, Pager, Icon} from 'coral-ui'; +import {Dropdown, Option, Paginate, Icon} from 'coral-ui'; import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; import t from 'coral-framework/services/i18n'; import styles from './Stories.css'; @@ -139,10 +139,10 @@ class Stories extends Component { {t('streams.status')} - + onNewPageHandler={this.onPageClick} /> */} : {t('streams.empty_result')} } From 9f4a91365511141b2b39411e74999dcae108ce0b Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 00:03:35 -0300 Subject: [PATCH 030/344] Passing props --- .../src/routes/Community/components/Community.js | 2 ++ .../src/routes/Community/components/People.js | 6 ++++++ .../src/routes/Community/components/Table.js | 10 +++++----- .../src/routes/Community/containers/People.js | 6 ++++-- package.json | 1 + routes/api/users/index.js | 2 +- script | 0 script.sh | 4 ++++ 8 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 script create mode 100755 script.sh diff --git a/client/coral-admin/src/routes/Community/components/Community.js b/client/coral-admin/src/routes/Community/components/Community.js index 1b0c6cc2a..bd41fbbea 100644 --- a/client/coral-admin/src/routes/Community/components/Community.js +++ b/client/coral-admin/src/routes/Community/components/Community.js @@ -48,6 +48,8 @@ class Community extends Component { Community.propTypes = { route: PropTypes.object, community: PropTypes.object, + rejectUsername: PropTypes.func.isRequired, + hideRejectUsernameDialog: PropTypes.func.isRequired, data: PropTypes.object, root: PropTypes.object, }; diff --git a/client/coral-admin/src/routes/Community/components/People.js b/client/coral-admin/src/routes/Community/components/People.js index 3edf620db..2be222c05 100644 --- a/client/coral-admin/src/routes/Community/components/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -15,6 +15,8 @@ const People = (props) => { onHeaderClickHandler, onNewPageHandler, totalPages, + setRole, + setCommenterStatus, } = props; const hasResults = !!users.length; @@ -39,6 +41,8 @@ const People = (props) => { hasResults ?
    : {t('community.no_results')} @@ -60,6 +64,8 @@ People.propTypes = { onSearchChange: PropTypes.func, totalPages: PropTypes.number, onNewPageHandler: PropTypes.func, + setCommenterStatus: PropTypes.func.isRequired, + setRole: PropTypes.func.isRequired, }; export default People; diff --git a/client/coral-admin/src/routes/Community/components/Table.js b/client/coral-admin/src/routes/Community/components/Table.js index 9071e0575..28a3e4c42 100644 --- a/client/coral-admin/src/routes/Community/components/Table.js +++ b/client/coral-admin/src/routes/Community/components/Table.js @@ -24,7 +24,7 @@ const headers = [ } ]; -const Table = ({users, onHeaderClickHandler, onRoleChange, onCommenterStatusChange, viewUserDetail}) => ( +const Table = ({users, onHeaderClickHandler, setRole, setCommenterStatus, viewUserDetail}) => (
    @@ -53,7 +53,7 @@ const Table = ({users, onHeaderClickHandler, onRoleChange, onCommenterStatusChan onCommenterStatusChange(row.id, status)}> + onChange={(status) => setCommenterStatus(row.id, status)}> @@ -62,7 +62,7 @@ const Table = ({users, onHeaderClickHandler, onRoleChange, onCommenterStatusChan onRoleChange(row.id, role)}> + onChange={(role) => setRole(row.id, role)}>
    : {t('community.no_results')} } - + ( +const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUserDetail}) => (
    {headers.map((header, i) =>( + + {users.map((row, i)=> ( + + + + + + + ))} + +
    onHeaderClickHandler({field: header.field})}> {header.title} @@ -80,7 +80,7 @@ Table.propTypes = { onHeaderClickHandler: PropTypes.func.isRequired, setRole: PropTypes.func.isRequired, setCommenterStatus: PropTypes.func.isRequired, - viewUserDetail: PropTypes.func, + viewUserDetail: PropTypes.func.isRequired, }; export default Table; diff --git a/client/coral-admin/src/routes/Community/containers/People.js b/client/coral-admin/src/routes/Community/containers/People.js index c45361c9f..5cd6f6e9c 100644 --- a/client/coral-admin/src/routes/Community/containers/People.js +++ b/client/coral-admin/src/routes/Community/containers/People.js @@ -77,6 +77,7 @@ class PeopleContainer extends React.Component { totalPages={this.props.community.totalPagesPeople} setCommenterStatus={this.props.setCommenterStatus} setRole={this.props.setRole} + viewUserDetail={this.props.viewUserDetail} />; } } @@ -87,6 +88,7 @@ PeopleContainer.propTypes = { updateSorting: PropTypes.func, setRole: PropTypes.func.isRequired, setCommenterStatus: PropTypes.func.isRequired, + viewUserDetail: PropTypes.func.isRequired, community: PropTypes.object, }; diff --git a/client/coral-ui/components/Paginate.css b/client/coral-ui/components/Paginate.css index dbb25b844..86b7c91c5 100644 --- a/client/coral-ui/components/Paginate.css +++ b/client/coral-ui/components/Paginate.css @@ -2,7 +2,7 @@ text-align: center; } -.page, .previous, .next { +.page, .previous, .next, .break { display: inline-block; margin-right: 5px; height: 30px; diff --git a/client/coral-ui/components/Paginate.js b/client/coral-ui/components/Paginate.js index 1d7d55398..3a404db69 100644 --- a/client/coral-ui/components/Paginate.js +++ b/client/coral-ui/components/Paginate.js @@ -11,7 +11,6 @@ const Paginate = ({pageCount, onPageChange}) => ( pageRangeDisplayed={5} marginPagesDisplayed={2} onPageChange={onPageChange} - breakLabel={...} breakClassName={styles.break} containerClassName={styles.container} pageClassName={styles.page} From 0b973f24ff456d98ddaa1289716f019ed703e171 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 00:14:47 -0300 Subject: [PATCH 032/344] Replacing paginate from Stories --- .../coral-admin/src/routes/Stories/components/Stories.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index 57caa2744..966e8a9c3 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -139,10 +139,9 @@ class Stories extends Component { {t('streams.status')} - {/* */} + : {t('streams.empty_result')} } From 34334b81521bd6686d8134e5c5e222f305581303 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 00:18:03 -0300 Subject: [PATCH 033/344] Pagination only shows when there are results to show --- .../src/routes/Community/components/People.js | 9 +- .../src/routes/Community/components/Table.js | 106 ++++++++++-------- 2 files changed, 60 insertions(+), 55 deletions(-) diff --git a/client/coral-admin/src/routes/Community/components/People.js b/client/coral-admin/src/routes/Community/components/People.js index 0878873ac..9626dd53f 100644 --- a/client/coral-admin/src/routes/Community/components/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -1,7 +1,7 @@ import React from 'react'; import styles from './styles.css'; import Table from './Table'; -import {Paginate, Icon} from 'coral-ui'; +import {Icon} from 'coral-ui'; import EmptyCard from '../../../components/EmptyCard'; import t from 'coral-framework/services/i18n'; @@ -46,14 +46,11 @@ const People = (props) => { viewUserDetail={viewUserDetail} setCommenterStatus={setCommenterStatus} onHeaderClickHandler={onHeaderClickHandler} + pageCount={totalPages} + onPageChange={onNewPageHandler} /> : {t('community.no_results')} } - - ); diff --git a/client/coral-admin/src/routes/Community/components/Table.js b/client/coral-admin/src/routes/Community/components/Table.js index 33cbb87e8..8d650f165 100644 --- a/client/coral-admin/src/routes/Community/components/Table.js +++ b/client/coral-admin/src/routes/Community/components/Table.js @@ -2,7 +2,7 @@ import React from 'react'; import styles from './Table.css'; import t from 'coral-framework/services/i18n'; import PropTypes from 'prop-types'; -import {Dropdown, Option} from 'coral-ui'; +import {Paginate, Dropdown, Option} from 'coral-ui'; import cn from 'classnames'; const headers = [ @@ -24,55 +24,61 @@ const headers = [ } ]; -const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUserDetail}) => ( - - - - {headers.map((header, i) =>( - - ))} - - - - {users.map((row, i)=> ( - - - - - +const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUserDetail, pageCount, onPageChange}) => ( +
    +
    onHeaderClickHandler({field: header.field})}> - {header.title} -
    - - {row.profiles.map(({id}) => id)} - - {row.created_at} - - setCommenterStatus(row.id, status)}> - - - setRole(row.id, role)}> - -
    + + + {headers.map((header, i) =>( + + ))} - ))} - -
    onHeaderClickHandler({field: header.field})}> + {header.title} +
    +
    + + {row.profiles.map(({id}) => id)} + + {row.created_at} + + setCommenterStatus(row.id, status)}> + + + setRole(row.id, role)}> + +
    + + ); Table.propTypes = { @@ -81,6 +87,8 @@ Table.propTypes = { setRole: PropTypes.func.isRequired, setCommenterStatus: PropTypes.func.isRequired, viewUserDetail: PropTypes.func.isRequired, + pageCount: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, }; export default Table; From 38abf63cc9d9ee9bb7228c58595a1ae689c1c988 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 00:21:26 -0300 Subject: [PATCH 034/344] removing unused files --- script | 0 script.sh | 4 ---- 2 files changed, 4 deletions(-) delete mode 100644 script delete mode 100755 script.sh diff --git a/script b/script deleted file mode 100644 index e69de29bb..000000000 diff --git a/script.sh b/script.sh deleted file mode 100755 index 156d7fb27..000000000 --- a/script.sh +++ /dev/null @@ -1,4 +0,0 @@ -for i in {1..100} -do - ./bin/cli -c .env users create --email "user-x${i}@coralproject.net" --password "mysecretpassword" --name "userxxx${i}" -f -done From b189c9d2b0f8011faeff5746371e467b56449b60 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 24 Oct 2017 12:08:46 +0200 Subject: [PATCH 035/344] Add cursor pointer and increase clickable area --- client/coral-ui/components/Paginate.css | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/client/coral-ui/components/Paginate.css b/client/coral-ui/components/Paginate.css index 86b7c91c5..813786d66 100644 --- a/client/coral-ui/components/Paginate.css +++ b/client/coral-ui/components/Paginate.css @@ -4,18 +4,20 @@ .page, .previous, .next, .break { display: inline-block; + list-style: none; margin-right: 5px; +} + +.pageLink, .previousLink, .nextLink { + display: inline-block; + color: #696969; + cursor: pointer; height: 30px; text-align: center; vertical-align: middle; line-height: 30px; width: 30px; - list-style: none; -} - -.pageLink, .previousLink, .nextLink { - color: #696969; - cursor: default; + user-select: none; } .previousLink, .nextLink { From 46359b4a1b6d49fe19e85a98784510f0f949d4de Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 24 Oct 2017 14:49:32 +0200 Subject: [PATCH 036/344] Show errors --- client/coral-admin/src/components/UserDetail.js | 4 ---- client/coral-admin/src/containers/UserDetail.js | 14 ++++---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 1c6362791..140774eae 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -44,7 +44,6 @@ export default class UserDetail extends React.Component { this.props.data.refetch(); } catch (err) { - // TODO: handle error. console.error(err); this.props.notify('error', getErrorMessages(err)); } @@ -56,7 +55,6 @@ export default class UserDetail extends React.Component { this.props.data.refetch(); } catch (err) { - // TODO: handle error. console.error(err); this.props.notify('error', getErrorMessages(err)); } @@ -68,7 +66,6 @@ export default class UserDetail extends React.Component { this.props.data.refetch(); } catch (err) { - // TODO: handle error. console.error(err); this.props.notify('error', getErrorMessages(err)); } @@ -80,7 +77,6 @@ export default class UserDetail extends React.Component { this.props.data.refetch(); } catch (err) { - // TODO: handle error. console.error(err); this.props.notify('error', getErrorMessages(err)); } diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 47d8e1c79..97021ec75 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -43,22 +43,16 @@ class UserDetailContainer extends React.Component { return this.props.setCommentStatus({commentId, status}); }); - try { - await Promise.all(changes); - this.props.clearUserDetailSelections(); // un-select everything - } catch (err) { - - // TODO: handle error. - console.error(err); - } + await Promise.all(changes); + this.props.clearUserDetailSelections(); // un-select everything } bulkReject = () => { - this.bulkSetCommentStatus('REJECTED'); + return this.bulkSetCommentStatus('REJECTED'); } bulkAccept = () => { - this.bulkSetCommentStatus('ACCEPTED'); + return this.bulkSetCommentStatus('ACCEPTED'); } acceptComment = ({commentId}) => { From 24ec898a15496d501111b5345e716e16c785461f Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 24 Oct 2017 14:14:26 +0100 Subject: [PATCH 037/344] Fix formatting --- docs/_docs/03-02-product-guide-commenter-features.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_docs/03-02-product-guide-commenter-features.md b/docs/_docs/03-02-product-guide-commenter-features.md index b46c5f054..7f56880db 100644 --- a/docs/_docs/03-02-product-guide-commenter-features.md +++ b/docs/_docs/03-02-product-guide-commenter-features.md @@ -7,9 +7,9 @@ permalink: /commenter-features/ There are 2 ways that newsrooms can support signup/login functionality with Talk: -*Use Talk’s auth plugin out of the box (supports account registration with username and password, as well as features like forgot password) +* Use Talk’s auth plugin out of the box (supports account registration with username and password, as well as features like forgot password) -*Create their own auth plugin to integrate with your own auth systems +* Create their own auth plugin to integrate with your own auth systems We also provide a Facebook auth plugin that supports logging in with Facebook (you must provide your own Facebook App ID and Secret, which you can read more about here: [https://developers.facebook.com](https://developers.facebook.com){:target="_blank"}) From b7b9da9509e290694bf2d9e689cb9bdc28022767 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 24 Oct 2017 15:16:45 +0200 Subject: [PATCH 038/344] Change fetchPolicy --- client/coral-admin/src/containers/UserDetail.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 97021ec75..3a458ff83 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -165,7 +165,8 @@ export const withUserDetailQuery = withQuery(gql` `, { options: ({userId, statuses}) => { return { - variables: {author_id: userId, statuses} + variables: {author_id: userId, statuses}, + fetchPolicy: 'network-only', }; }, skip: (ownProps) => !ownProps.userId, From fed6654f5208bcc62a6bcb32bc8d35238d67f4d9 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 10:45:25 -0300 Subject: [PATCH 039/344] Support pagination for search --- routes/api/users/index.js | 18 ++++++++++++------ services/users.js | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 00b776e5b..0cf1c997b 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -9,8 +9,8 @@ const { ROOT_URL } = require('../../../config'); -// get a list of users. router.get('/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, next) => { + const { value = '', field = 'created_at', @@ -21,13 +21,20 @@ router.get('/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, nex try { + const queryOpts = { + sort: {[field]: (asc === 'true') ? 1 : -1}, + skip: (page - 1) * limit, + limit + }; + let [result, count] = await Promise.all([ UsersService .search(value) - .sort({[field]: (asc === 'true') ? 1 : -1}) - .skip((page - 1) * limit) - .limit(limit), - UsersService.count() + .sort(queryOpts.sort) + .skip(queryOpts.skip) + .limit(queryOpts.limit) + .lean(), + UsersService.search(value).count() ]); res.json({ @@ -41,7 +48,6 @@ router.get('/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, nex } catch (e) { next(e); } - }); router.post('/:user_id/role', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, next) => { diff --git a/services/users.js b/services/users.js index 7968f7e83..4d974f4a4 100644 --- a/services/users.js +++ b/services/users.js @@ -668,8 +668,8 @@ module.exports = class UsersService { * Returns a count of the current users. * @return {Promise} */ - static count() { - return UserModel.count(); + static count(query = {}) { + return UserModel.count(query); } /** From 55b2208a498235c45e7dcdbe48545ce77535c064 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 11:29:17 -0300 Subject: [PATCH 040/344] Pagination and refactor for Stories --- client/coral-admin/src/actions/assets.js | 13 ++-- client/coral-admin/src/reducers/assets.js | 2 + .../src/routes/Stories/components/Stories.js | 72 +++++++++++++------ .../src/routes/Stories/containers/Stories.js | 16 +---- routes/api/assets/index.js | 30 +++++--- routes/api/users/index.js | 4 +- 6 files changed, 86 insertions(+), 51 deletions(-) diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index 1ce6ad6ac..d7c6d9448 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -1,3 +1,5 @@ +import queryString from 'query-string'; + import { FETCH_ASSETS_REQUEST, FETCH_ASSETS_SUCCESS, @@ -16,13 +18,16 @@ import t from 'coral-framework/services/i18n'; // Fetch a page of assets // Get comments to fill each of the three lists on the mod queue -export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filter = '') => (dispatch, _, {rest}) => { +export const fetchAssets = (query = {}) => (dispatch, _, {rest}) => { dispatch({type: FETCH_ASSETS_REQUEST}); - return rest(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`) - .then(({result, count}) => + return rest(`/assets?${queryString.stringify(query)}`) + .then(({result, page, count, limit, totalPages}) => dispatch({type: FETCH_ASSETS_SUCCESS, assets: result, - count + page, + count, + limit, + totalPages, })) .catch((error) => { console.error(error); diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/assets.js index bb8751973..b4ea8d21f 100644 --- a/client/coral-admin/src/reducers/assets.js +++ b/client/coral-admin/src/reducers/assets.js @@ -16,6 +16,8 @@ export default function assets (state = initialState, action) { }, {}); return update(state, { + totalPages: {$set: action.totalPages}, + page: {$set: action.page}, byId: {$set: assets}, count: {$set: action.count}, ids: {$set: Object.keys(assets)}, diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index 966e8a9c3..c37a7f8f6 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -8,38 +8,54 @@ import t from 'coral-framework/services/i18n'; import styles from './Stories.css'; import EmptyCard from 'coral-admin/src/components/EmptyCard'; -const limit = 25; - class Stories extends Component { state = { - search: '', + searchValue: '', sort: 'desc', filter: 'all', statusMenus: {}, timer: null, - page: 0 } componentDidMount () { - this.props.fetchAssets(0, limit, '', this.state.sortBy); + const {sort} = this.state; + + this.props.fetchAssets({ + sort, + }); } onSettingChange = (setting) => (e) => { - let options = this.state; - this.setState({[setting]: e.target.value}); - options[setting] = e.target.value; - this.props.fetchAssets(0, limit, options.search, options.sort, options.filter); + const {searchValue, sort, filter} = this.state; + + this.setState({ + [setting]: e.target.value + }); + + this.props.fetchAssets({ + value: searchValue, + sort, + filter, + }); } onSearchChange = (e) => { - const search = e.target.value; + const {fetchAssets} = this.props; + const {value} = e.target; + const {sort, filter, limit} = this.state; + this.setState((prevState) => { - prevState.search = search; + prevState.searchValue = value; clearTimeout(prevState.timer); - const fetchAssets = this.props.fetchAssets; + prevState.timer = setTimeout(() => { - fetchAssets(0, limit, search, this.state.sort, this.state.filter); + fetchAssets({ + value, + sort, + filter, + limit, + }); }, 350); return prevState; }); @@ -51,10 +67,17 @@ class Stories extends Component { } onStatusChange = async (closeStream, id) => { + const {fetchAssets, updateAssetState} = this.props; + const {searchValue, sort, filter, limit} = this.state; + try { - this.props.updateAssetState(id, closeStream ? Date.now() : null); - const {search, sort, filter, page} = this.state; - this.props.fetchAssets(page, limit, search, sort, filter); + updateAssetState(id, closeStream ? Date.now() : null); + fetchAssets({ + value: searchValue, + sort, + filter, + limit, + }); } catch(err) { console.error(err); } @@ -74,10 +97,17 @@ class Stories extends Component { ); } - onPageClick = (page) => { - this.setState({page}); - const {search, sort, filter} = this.state; - this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter); + onPageClick = ({selected}) => { + const page = selected + 1; + const {searchValue, sort, filter, limit} = this.state; + + this.props.fetchAssets({ + page, + value: searchValue, + sort, + filter, + limit, + }); } render () { @@ -140,7 +170,7 @@ class Stories extends Component { : {t('streams.empty_result')} diff --git a/client/coral-admin/src/routes/Stories/containers/Stories.js b/client/coral-admin/src/routes/Stories/containers/Stories.js index f645f310c..b940e46e6 100644 --- a/client/coral-admin/src/routes/Stories/containers/Stories.js +++ b/client/coral-admin/src/routes/Stories/containers/Stories.js @@ -1,19 +1,9 @@ -import React, {Component} from 'react'; import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; -import {compose} from 'react-apollo'; import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets'; import Stories from '../components/Stories'; -class StoriesContainer extends Component { - render () { - return ; - } -} - -const mapStateToProps = (state) => ({ - assets: state.assets -}); +const mapStateToProps = ({assets}) => ({assets}); const mapDispatchToProps = (dispatch) => bindActionCreators({ @@ -21,7 +11,5 @@ const mapDispatchToProps = (dispatch) => updateAssetState, }, dispatch); -export default compose( - connect(mapStateToProps, mapDispatchToProps), -)(StoriesContainer); +export default connect(mapStateToProps, mapDispatchToProps)(Stories); diff --git a/routes/api/assets/index.js b/routes/api/assets/index.js index 1b2b0c7a9..4007517de 100644 --- a/routes/api/assets/index.js +++ b/routes/api/assets/index.js @@ -37,34 +37,44 @@ const FilterOpenAssets = (query, filter) => { router.get('/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, next) => { const { - limit = 20, - skip = 0, - sort = 'asc', + value = '', field = 'created_at', + page = 1, + asc = 'false', filter = 'all', - search = '' + limit = 20, } = req.query; try { + const queryOpts = { + sort: {[field]: (asc === 'true') ? 1 : -1}, + skip: (page - 1) * limit, + limit + }; + // Find all the assets. let [result, count] = await Promise.all([ // Find the actuall assets. - FilterOpenAssets(AssetsService.search({value: search}), filter) - .sort({[field]: (sort === 'asc') ? 1 : -1}) - .skip(parseInt(skip)) - .limit(parseInt(limit)), + FilterOpenAssets(AssetsService.search({value}), filter) + .sort(queryOpts.sort) + .skip(parseInt(queryOpts.skip)) + .limit(parseInt(queryOpts.limit)) + .lean(), // Get the count of actual assets. - FilterOpenAssets(AssetsService.search({value: search}), filter) + FilterOpenAssets(AssetsService.search({value}), filter) .count() ]); // Send back the asset data. res.json({ result, - count + limit: Number(limit), + count, + page: Number(page), + totalPages: Math.ceil(count / (limit === 0 ? 1 : limit)) }); } catch (e) { return next(e); diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 0cf1c997b..98cdcdead 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -31,8 +31,8 @@ router.get('/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, nex UsersService .search(value) .sort(queryOpts.sort) - .skip(queryOpts.skip) - .limit(queryOpts.limit) + .skip(parseInt(queryOpts.skip)) + .limit(parseInt(queryOpts.limit)) .lean(), UsersService.search(value).count() ]); From 3777b4b1ce5581ac97b68d77314569f885778a68 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 11:35:27 -0300 Subject: [PATCH 041/344] Filter Streams fully working --- .../src/routes/Stories/components/Stories.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index c37a7f8f6..ed4a7a595 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -27,16 +27,14 @@ class Stories extends Component { } onSettingChange = (setting) => (e) => { - const {searchValue, sort, filter} = this.state; - - this.setState({ - [setting]: e.target.value - }); + const {searchValue} = this.state; + const criteria = {[setting]: e.target.value}; + + this.setState(criteria); this.props.fetchAssets({ value: searchValue, - sort, - filter, + ...criteria, }); } From 61108337ccfd4ca9113b7592920976f49bf943d1 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 11:42:21 -0300 Subject: [PATCH 042/344] Even tinier --- .../src/routes/Stories/components/Stories.js | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index ed4a7a595..bd0dd9f86 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -19,11 +19,7 @@ class Stories extends Component { } componentDidMount () { - const {sort} = this.state; - - this.props.fetchAssets({ - sort, - }); + this.fetchAssets(); } onSettingChange = (setting) => (e) => { @@ -39,21 +35,14 @@ class Stories extends Component { } onSearchChange = (e) => { - const {fetchAssets} = this.props; const {value} = e.target; - const {sort, filter, limit} = this.state; this.setState((prevState) => { prevState.searchValue = value; clearTimeout(prevState.timer); prevState.timer = setTimeout(() => { - fetchAssets({ - value, - sort, - filter, - limit, - }); + this.fetchAssets(); }, 350); return prevState; }); @@ -64,18 +53,24 @@ class Stories extends Component { return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; } - onStatusChange = async (closeStream, id) => { - const {fetchAssets, updateAssetState} = this.props; + fetchAssets = (query) => { const {searchValue, sort, filter, limit} = this.state; + this.props.fetchAssets({ + value: searchValue, + sort, + filter, + limit, + ...query + }); + }; + + onStatusChange = async (closeStream, id) => { + const {updateAssetState} = this.props; + try { updateAssetState(id, closeStream ? Date.now() : null); - fetchAssets({ - value: searchValue, - sort, - filter, - limit, - }); + this.fetchAssets(); } catch(err) { console.error(err); } @@ -97,15 +92,7 @@ class Stories extends Component { onPageClick = ({selected}) => { const page = selected + 1; - const {searchValue, sort, filter, limit} = this.state; - - this.props.fetchAssets({ - page, - value: searchValue, - sort, - filter, - limit, - }); + this.props.fetchAssets({page}); } render () { From f3bf9d28a7dbe4ab285d8664d6da59adb05968ae Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 11:47:18 -0300 Subject: [PATCH 043/344] Search Value --- client/coral-admin/src/routes/Stories/components/Stories.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index bd0dd9f86..84eaf9fbb 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -96,7 +96,7 @@ class Stories extends Component { } render () { - const {search, sort, filter} = this.state; + const {searchValue, sort, filter} = this.state; const {assets} = this.props; const assetsIds = sortBy(assets.ids.map((id) => assets.byId[id]), 'publication_date'); @@ -112,7 +112,7 @@ class Stories extends Component { From ca1ce9ba763a56adc4e564f852c806aeae30bac4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 24 Oct 2017 17:23:10 +0200 Subject: [PATCH 044/344] Dont cache auth --- routes/api/auth/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 791573898..1af57fb54 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -12,6 +12,10 @@ router.get('/', (req, res, next) => { return; } + res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); + res.header('Expires', '-1'); + res.header('Pragma', 'no-cache'); + // Send back the user object. res.json({user: req.user}); }); From a0929fd773e9deeb501b7b32468507e81dd56e18 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 12:30:07 -0300 Subject: [PATCH 045/344] Updated tests --- test/server/routes/api/assets/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/server/routes/api/assets/index.js b/test/server/routes/api/assets/index.js index a07b154d4..0dacccbad 100644 --- a/test/server/routes/api/assets/index.js +++ b/test/server/routes/api/assets/index.js @@ -56,7 +56,7 @@ describe('/api/v1/assets', () => { it('should return assets that we search for', async () => { for (const role of ['ADMIN', 'MODERATOR']) { const res = await chai.request(app) - .get('/api/v1/assets?search=term2') + .get('/api/v1/assets?value=term2') .set(passport.inject({roles: [role]})); const body = res.body; @@ -78,7 +78,7 @@ describe('/api/v1/assets', () => { it('should not return assets that we do not search for', async () => { for (const role of ['ADMIN', 'MODERATOR']) { const res = await chai.request(app) - .get('/api/v1/assets?search=term3') + .get('/api/v1/assets?value=term3') .set(passport.inject({roles: [role]})); const body = res.body; From be994d033b0f99406c9cc3c52752b5f6750abd2f Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 12:58:15 -0300 Subject: [PATCH 046/344] Adding classes --- client/coral-admin/src/components/ui/Header.js | 13 +++++++------ .../src/routes/Community/components/Community.js | 2 +- .../src/routes/Stories/components/Stories.js | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 62f6a7fd1..c5af63786 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -1,4 +1,5 @@ import React from 'react'; +import cn from 'classnames'; import PropTypes from 'prop-types'; import {Navigation, Header, IconButton, MenuItem, Menu} from 'react-mdl'; import {Link, IndexLink} from 'react-router'; @@ -24,7 +25,7 @@ const CoralHeader = ({ {t('configure.dashboard')} @@ -33,7 +34,7 @@ const CoralHeader = ({ can(auth.user, 'MODERATE_COMMENTS') && ( {t('configure.moderate')} @@ -42,8 +43,8 @@ const CoralHeader = ({ ) } {t('configure.stories')} @@ -51,7 +52,7 @@ const CoralHeader = ({ {t('configure.community')} @@ -62,7 +63,7 @@ const CoralHeader = ({ can(auth.user, 'UPDATE_CONFIG') && ( {t('configure.configure')} diff --git a/client/coral-admin/src/routes/Community/components/Community.js b/client/coral-admin/src/routes/Community/components/Community.js index bd41fbbea..3b60b357a 100644 --- a/client/coral-admin/src/routes/Community/components/Community.js +++ b/client/coral-admin/src/routes/Community/components/Community.js @@ -35,7 +35,7 @@ class Community extends Component { const {root: {flaggedUsernamesCount}} = this.props; return ( -
    +
    {this.renderTab()} diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index 84eaf9fbb..73f5c60dc 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import cn from 'classnames'; import {Link} from 'react-router'; import PropTypes from 'prop-types'; import sortBy from 'lodash/sortBy'; @@ -106,7 +107,7 @@ class Stories extends Component { } return ( -
    +
    From 7ef9e2783b71fef6d76421c08a31bde0b5f4e9c1 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 12:58:27 -0300 Subject: [PATCH 047/344] Initial e2e --- test/e2e/page_objects/admin.js | 5 +++++ test/e2e/specs/02_admin.js | 26 +++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/test/e2e/page_objects/admin.js b/test/e2e/page_objects/admin.js index c4fcb83c1..ffdcf8a29 100644 --- a/test/e2e/page_objects/admin.js +++ b/test/e2e/page_objects/admin.js @@ -14,5 +14,10 @@ module.exports = { 'emailInput': '.talk-admin-login-sign-in #email', 'passwordInput': '.talk-admin-login-sign-in #password', 'signInButton': '.talk-admin-login-sign-in-button', + 'storiesNav': '.talk-admin-nav-stories', + 'storiesSection': '.talk-admin-stories', + 'communityNav': '.talk-admin-nav-community', + 'communitySection': '.talk-admin-community', + 'moderationContainer': '.talk-admin-moderation-container' } }; diff --git a/test/e2e/specs/02_admin.js b/test/e2e/specs/02_admin.js index 236a1c6ca..463249eeb 100644 --- a/test/e2e/specs/02_admin.js +++ b/test/e2e/specs/02_admin.js @@ -1,6 +1,10 @@ module.exports = { '@tags': ['admin', 'login'], + beforeEach: (client) => { + // Testing Desktop + client.resizeWindow(1280, 800); + }, 'Admin logs in': (client) => { const adminPage = client.page.admin(); const {testData: {admin}} = client.globals; @@ -12,8 +16,28 @@ module.exports = { .setValue('@emailInput', admin.email) .setValue('@passwordInput', admin.password) .waitForElementVisible('@signInButton') - .click('@signInButton'); + .click('@signInButton') + .waitForElementVisible('@moderationContainer'); + }, + 'Admin goes to Stories': (client) => { + const adminPage = client.page.admin(); + + adminPage + .navigate() + .waitForElementVisible('@storiesNav') + .click('@storiesNav') + .waitForElementVisible('@storiesSection'); + }, + + 'Admin goes to Community': (client) => { + const adminPage = client.page.admin(); + + adminPage + .navigate() + .waitForElementVisible('@communityNav') + .click('@communityNav') + .waitForElementVisible('@communitySection'); }, after: (client) => { client.end(); From dd7ca9fd0c06cb4d695a136a980bf5f8a28499e0 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 23 Oct 2017 11:24:02 -0600 Subject: [PATCH 048/344] added docs --- services/limit.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/services/limit.js b/services/limit.js index 7acc78f0c..9d1101643 100644 --- a/services/limit.js +++ b/services/limit.js @@ -3,6 +3,9 @@ const errors = require('../errors'); const {createClientFactory} = require('./redis'); const client = createClientFactory(); +/** + * Limit is designed to support rate limiting a resource. + */ class Limit { constructor(prefix, max, duration) { this.ttl = ms(duration) / 1000; @@ -10,16 +13,37 @@ class Limit { this.max = max; } + /** + * key will compose the redis key used to store the rate limit information. + * + * @param {String} value the string to use that is being limited + * @returns {String} the redis key to set + */ key(value) { return `limit[${this.prefix}][${value}]`; } + /** + * get will fetch the current number of attempts within the given window + * duration. + * + * @param {String} value the value to limit with + * @returns {Integer} the number of tries within the current window + */ async get(value) { const key = this.key(value); return client().get(key); } + /** + * test will increment the number of tries, reset the window length and + * will throw an error if the number of tries exceed the maximum for the + * window duration. + * + * @param {String} value the value to limit with + * @returns {Promise} resolves to the number of tries, or throws an error + */ async test(value) { const key = this.key(value); From 812483931d3d1b4eec55b68e64f3f41cbb6b46c1 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 24 Oct 2017 13:13:44 -0300 Subject: [PATCH 049/344] ref --- .../src/routes/Community/containers/People.js | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/client/coral-admin/src/routes/Community/containers/People.js b/client/coral-admin/src/routes/Community/containers/People.js index 5cd6f6e9c..561e0fcfb 100644 --- a/client/coral-admin/src/routes/Community/containers/People.js +++ b/client/coral-admin/src/routes/Community/containers/People.js @@ -21,14 +21,25 @@ class PeopleContainer extends React.Component { timer: null }; + fetchUsers = (query = {}) => { + const {community} = this.props; + + this.props.fetchUsers({ + value: this.state.searchValue, + field: community.fieldPeople, + asc: community.ascPeople, + ...query + }); + } + componentWillMount() { - this.props.fetchUsers(); + this.fetchUsers(); } onKeyDownHandler = (e) => { if (e.key === 'Enter') { e.preventDefault(); - this.search(); + this.fetchUsers(); } } @@ -38,9 +49,9 @@ class PeopleContainer extends React.Component { this.setState((prevState) => { prevState.searchValue = value; clearTimeout(prevState.timer); - const fetchAccounts = this.props.fetchUsers; + prevState.timer = setTimeout(() => { - fetchAccounts({value}); + this.fetchUsers({value}); }, 350); return prevState; }); @@ -48,25 +59,15 @@ class PeopleContainer extends React.Component { onHeaderClickHandler = (sort) => { this.props.updateSorting(sort); - this.search(); + this.fetchUsers(); } onNewPageHandler = ({selected}) => { const page = selected + 1; this.props.newPage(page); - this.search({page}); + this.fetchUsers({page}); } - search(query = {}) { - const {community} = this.props; - - this.props.fetchUsers({ - value: this.state.searchValue, - field: community.fieldPeople, - asc: community.ascPeople, - ...query - }); - } render() { return Date: Tue, 24 Oct 2017 18:47:55 +0200 Subject: [PATCH 050/344] Simplified Spinner for IE and Edge for Spinner (using css hacks) --- client/coral-ui/components/Spinner.css | 65 +++++++++----------------- 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/client/coral-ui/components/Spinner.css b/client/coral-ui/components/Spinner.css index 00b7f93a5..da09758f1 100644 --- a/client/coral-ui/components/Spinner.css +++ b/client/coral-ui/components/Spinner.css @@ -4,49 +4,28 @@ } .spinner { - -webkit-animation: rotator 1.4s linear infinite; animation: rotator 1.4s linear infinite; } -@-webkit-keyframes rotator { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(270deg); - transform: rotate(270deg); - } -} - @keyframes rotator { 0% { - -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { - -webkit-transform: rotate(270deg); transform: rotate(270deg); } } + + + .path { + stroke: #f67150; stroke-dasharray: 187; stroke-dashoffset: 0; - -webkit-transform-origin: center; transform-origin: center; - -webkit-animation: dash 1.4s ease-in-out infinite, colors 5.6s ease-in-out infinite; animation: dash 1.4s ease-in-out infinite, colors 5.6s ease-in-out infinite; } -@-webkit-keyframes colors { - 0% { - stroke: #f67150; - } - 100% { - stroke: #f6a47e; - } -} - @keyframes colors { 0% { stroke: #f67150; @@ -55,33 +34,35 @@ stroke: #f6a47e; } } -@-webkit-keyframes dash { - 0% { - stroke-dashoffset: 187; - } - 50% { - stroke-dashoffset: 46.75; - -webkit-transform: rotate(135deg); - transform: rotate(135deg); - } - 100% { - stroke-dashoffset: 187; - -webkit-transform: rotate(450deg); - transform: rotate(450deg); - } -} + @keyframes dash { 0% { stroke-dashoffset: 187; } 50% { stroke-dashoffset: 46.75; - -webkit-transform: rotate(135deg); transform: rotate(135deg); } 100% { stroke-dashoffset: 187; - -webkit-transform: rotate(450deg); transform: rotate(450deg); } } + +@keyframes fullRotator { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Hack for IE and Edge as they don't support css animations on SVG elements. */ +_:-ms-lang(x), .path { + stroke-dasharray: 160; +} + +_:-ms-lang(x), .spinner { + animation: fullRotator 1.4s linear infinite; +} From 1dec169520b642b986be753d69453d6da26005ed Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 24 Oct 2017 11:03:15 -0600 Subject: [PATCH 051/344] added port parsing for smtp --- config.js | 2 +- services/mailer.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/config.js b/config.js index 368fcf0e3..4601683c1 100644 --- a/config.js +++ b/config.js @@ -163,7 +163,7 @@ const CONFIG = { SMTP_FROM_ADDRESS: process.env.TALK_SMTP_FROM_ADDRESS, SMTP_HOST: process.env.TALK_SMTP_HOST, SMTP_PASSWORD: process.env.TALK_SMTP_PASSWORD, - SMTP_PORT: process.env.TALK_SMTP_PORT, + SMTP_PORT: process.env.TALK_SMTP_PORT ? parseInt(process.env.TALK_SMTP_PORT) : undefined, SMTP_USERNAME: process.env.TALK_SMTP_USERNAME, //------------------------------------------------------------------------------ diff --git a/services/mailer.js b/services/mailer.js index 8d650726d..f04a6dc92 100644 --- a/services/mailer.js +++ b/services/mailer.js @@ -64,7 +64,11 @@ const options = { }; if (SMTP_PORT) { - options.port = SMTP_PORT; + try { + options.port = parseInt(SMTP_PORT); + } catch (e) { + throw new Error('TALK_SMTP_PORT is not an integer'); + } } else { options.port = 25; } From 27eb159a75dc85807c464cd28d51a61f4cf329de Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 13:04:41 +0200 Subject: [PATCH 052/344] Add special popup handling for ie 11 --- client/coral-framework/components/Popup.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/coral-framework/components/Popup.js b/client/coral-framework/components/Popup.js index 33041442f..41a37237a 100644 --- a/client/coral-framework/components/Popup.js +++ b/client/coral-framework/components/Popup.js @@ -22,6 +22,9 @@ export default class Popup extends Component { ); this.setCallbacks(); + + // For some reasons IE needs a timeout before setting the callbacks... + setTimeout(() => this.setCallbacks(), 1000); } setCallbacks() { From 16bf8871614574e5c747982a415f75b3f1d854ff Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 13:06:00 +0200 Subject: [PATCH 053/344] Add ie to ci testing --- scripts/e2e-ci.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/e2e-ci.sh b/scripts/e2e-ci.sh index 1e4f45691..f1376abcb 100755 --- a/scripts/e2e-ci.sh +++ b/scripts/e2e-ci.sh @@ -21,9 +21,7 @@ if [[ "${CIRCLE_BRANCH}" == "master" ]]; then # Test using browserstack. browserstack chrome browserstack firefox - - # temporarily turn off ci, please fix https://www.pivotaltracker.com/story/show/152144406. - # browserstack ie + browserstack ie # Safari >= 8 has issues connecting to browserstack-local. Safari < 8 is too old. # browserstack safari From 823bf18335af4463dd7ac5ab22f65aa94888bca6 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 14:19:13 +0200 Subject: [PATCH 054/344] Add edge to ci --- scripts/e2e-ci.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/e2e-ci.sh b/scripts/e2e-ci.sh index f1376abcb..e0e94b4bf 100755 --- a/scripts/e2e-ci.sh +++ b/scripts/e2e-ci.sh @@ -22,13 +22,11 @@ if [[ "${CIRCLE_BRANCH}" == "master" ]]; then browserstack chrome browserstack firefox browserstack ie + browserstack edge # Safari >= 8 has issues connecting to browserstack-local. Safari < 8 is too old. # browserstack safari - # Edge 14 & 15 randomly fails when switching from the login popup back to the main window. - # browserstack edge - exit $exitCode else # When browserstack is not available test locally using chrome headless. From 833922f0aaf15bbe4779da46c6884e6a4212e5d4 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 25 Oct 2017 09:35:08 -0300 Subject: [PATCH 055/344] Missing reference to the dropdownContainer class --- .../src/routes/Moderation/components/ViewOptions.css | 2 +- .../src/routes/Moderation/components/ViewOptions.js | 1 + client/coral-ui/components/Dropdown.js | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/components/ViewOptions.css b/client/coral-admin/src/routes/Moderation/components/ViewOptions.css index e5dfc8c01..3e5aae32d 100644 --- a/client/coral-admin/src/routes/Moderation/components/ViewOptions.css +++ b/client/coral-admin/src/routes/Moderation/components/ViewOptions.css @@ -53,7 +53,7 @@ list-style: none; } -.dropdow { +.dropdownContainer { position: relative; margin-top: 5px; diff --git a/client/coral-admin/src/routes/Moderation/components/ViewOptions.js b/client/coral-admin/src/routes/Moderation/components/ViewOptions.js index c46b7f869..a65af66a6 100644 --- a/client/coral-admin/src/routes/Moderation/components/ViewOptions.js +++ b/client/coral-admin/src/routes/Moderation/components/ViewOptions.js @@ -23,6 +23,7 @@ class ViewOptions extends React.Component {
  • Sort Comments -
    +
    Date: Wed, 25 Oct 2017 16:47:51 +0200 Subject: [PATCH 056/344] Fix ci --- nightwatch-browserstack.conf.js | 2 +- nightwatch.conf.js | 2 +- yarn.lock | 212 +++++++------------------------- 3 files changed, 46 insertions(+), 170 deletions(-) diff --git a/nightwatch-browserstack.conf.js b/nightwatch-browserstack.conf.js index 604691c81..9e8e334c5 100644 --- a/nightwatch-browserstack.conf.js +++ b/nightwatch-browserstack.conf.js @@ -31,7 +31,7 @@ const nightwatch_config = { chrome: { desiredCapabilities: { browser: 'chrome', - browser_version: '60', + browser_version: '62', }, }, firefox: { diff --git a/nightwatch.conf.js b/nightwatch.conf.js index fddbce229..f843b881a 100644 --- a/nightwatch.conf.js +++ b/nightwatch.conf.js @@ -43,7 +43,7 @@ module.exports = { 'chrome-headless': { desiredCapabilities: { chromeOptions : { - args: ['--headless', '--disable-gpu'], + args: ['--headless', '--disable-gpu', 'window-size=1280,800'], }, }, }, diff --git a/yarn.lock b/yarn.lock index dc23d05f8..6ea60fc82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -72,11 +72,7 @@ abab@^1.0.0, abab@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - -abbrev@1.0.x: +abbrev@1, abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" @@ -403,18 +399,12 @@ async@2.1.4: dependencies: lodash "^4.14.0" -async@2.4.1: +async@2.4.1, async@^2.1.2, async@^2.1.4: version "2.4.1" resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" dependencies: lodash "^4.14.0" -async@^2.1.2, async@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" - dependencies: - lodash "^4.14.0" - async@~0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" @@ -1933,14 +1923,10 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" -cookiejar@2.0.x: +cookiejar@2.0.x, cookiejar@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.0.6.tgz#0abf356ad00d1c5a219d88d44518046dd026acfe" -cookiejar@^2.0.6: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" - copy-webpack-plugin@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.1.1.tgz#53ae69e04955ebfa9fda411f54cbb968531d71fd" @@ -2375,14 +2361,10 @@ diff@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" -diff@3.2.0: +diff@3.2.0, diff@^3.1.0, diff@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" -diff@^3.1.0, diff@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" - diffie-hellman@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" @@ -2425,40 +2407,27 @@ domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" -domelementtype@1, domelementtype@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" - -domelementtype@~1.1.1: +domelementtype@1, domelementtype@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" -domhandler@2.3: +domelementtype@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + +domhandler@2.3, domhandler@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" dependencies: domelementtype "1" -domhandler@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" - dependencies: - domelementtype "1" - -domutils@1.5, domutils@1.5.1: +domutils@1.5, domutils@1.5.1, domutils@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: dom-serializer "0" domelementtype "1" -domutils@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" - dependencies: - dom-serializer "0" - domelementtype "1" - dont-sniff-mimetype@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58" @@ -2890,7 +2859,7 @@ exports-loader@^0.6.4: loader-utils "^1.0.2" source-map "0.5.x" -express@4.16.0: +express@4.16.0, express@^4.12.2: version "4.16.0" resolved "https://registry.yarnpkg.com/express/-/express-4.16.0.tgz#b519638e4eb58e7178c81b498ef22f798cb2e255" dependencies: @@ -2925,41 +2894,6 @@ express@4.16.0: utils-merge "1.0.1" vary "~1.1.2" -express@^4.12.2: - version "4.16.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" - dependencies: - accepts "~1.3.4" - array-flatten "1.1.1" - body-parser "1.18.2" - content-disposition "0.5.2" - content-type "~1.0.4" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.1" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.1.0" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.2" - path-to-regexp "0.1.7" - proxy-addr "~2.0.2" - qs "6.5.1" - range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.1" - serve-static "1.13.1" - setprototypeof "1.1.0" - statuses "~1.3.1" - type-is "~1.6.15" - utils-merge "1.0.1" - vary "~1.1.2" - extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -3008,7 +2942,7 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.9: +fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.9: version "0.8.16" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" dependencies: @@ -4047,7 +3981,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -4185,7 +4119,7 @@ ip@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ip/-/ip-1.0.1.tgz#c7e356cdea225ae71b36d70f2e71a92ba4e42590" -ip@^1.1.2, ip@^1.1.4: +ip@^1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -5515,9 +5449,9 @@ lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" -lru-cache@^2.5.0: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" +lru-cache@^2.5.0, lru-cache@~2.6.5: + version "2.6.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" lru-cache@^4.0.1: version "4.1.1" @@ -5526,10 +5460,6 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" -lru-cache@~2.6.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" - macaddress@^0.2.8: version "0.2.8" resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" @@ -5707,7 +5637,7 @@ minimatch@3.0.3: dependencies: brace-expansion "^1.0.0" -minimist@0.0.8: +minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -5715,10 +5645,6 @@ minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -7115,7 +7041,7 @@ promised-io@*: version "0.3.5" resolved "https://registry.yarnpkg.com/promised-io/-/promised-io-0.3.5.tgz#4ad217bb3658bcaae9946b17a8668ecd851e1356" -prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8: +prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: @@ -7387,6 +7313,14 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-addons-create-fragment@^15.0.0: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-addons-create-fragment/-/react-addons-create-fragment-15.6.2.tgz#a394de7c2c7becd6b5475ba1b97ac472ce7c74f8" + dependencies: + fbjs "^0.8.4" + loose-envify "^1.3.1" + object-assign "^4.1.0" + react-apollo@^1.4.12: version "1.4.16" resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-1.4.16.tgz#62a623458b67a174ff8ef25f64e7b42531518e19" @@ -7435,6 +7369,14 @@ react-mdl@^1.7.1, react-mdl@^1.7.2: lodash.isequal "^4.4.0" prop-types "^15.5.0" +react-paginate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-5.0.0.tgz#b5c12191ea81adc6d4d1b339b805e81841eaa8ea" + dependencies: + classnames "^2.2.5" + prop-types "^15.6.0" + react-addons-create-fragment "^15.0.0" + react-recaptcha@^2.2.6: version "2.3.5" resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.3.5.tgz#a5db337125bb00fb13c2fa2e4ebfbe8b0cd06bb7" @@ -7466,20 +7408,13 @@ react-tagsinput@^3.17.0: version "3.18.0" resolved "https://registry.yarnpkg.com/react-tagsinput/-/react-tagsinput-3.18.0.tgz#40e036fc0f4c3d6b4689858189ab02926717a818" -react-test-renderer@15.5: +react-test-renderer@15.5, react-test-renderer@^15.5.0: version "15.5.4" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.5.4.tgz#d4ebb23f613d685ea8f5390109c2d20fbf7c83bc" dependencies: fbjs "^0.8.9" object-assign "^4.1.0" -react-test-renderer@^15.5.0: - version "15.6.2" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.6.2.tgz#d0333434fc2c438092696ca770da5ed48037efa8" - dependencies: - fbjs "^0.8.9" - object-assign "^4.1.0" - react-test-renderer@^16.0.0-0: version "16.0.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.0.0.tgz#9fe7b8308f2f71f29fc356d4102086f131c9cb15" @@ -7550,7 +7485,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@1.1: +readable-stream@1.1, readable-stream@1.1.x: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" dependencies: @@ -7559,28 +7494,7 @@ readable-stream@1.1: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@1.1.x: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -readable-stream@2.2.7: +readable-stream@2, readable-stream@2.2.7, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.7.tgz#07057acbe2467b22042d36f98c5ad507054e95b1" dependencies: @@ -7991,11 +7905,11 @@ rx@^2.4.3: version "2.5.3" resolved "https://registry.yarnpkg.com/rx/-/rx-2.5.3.tgz#21adc7d80f02002af50dae97fd9dbf248755f566" -safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" -samsam@1.1.2: +samsam@1.1.2, samsam@~1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" @@ -8003,10 +7917,6 @@ samsam@1.x, samsam@^1.1.3: version "1.3.0" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" -samsam@~1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" - sane@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" @@ -8097,24 +8007,6 @@ send@0.16.0: range-parser "~1.2.0" statuses "~1.3.1" -send@0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" - dependencies: - debug "2.6.9" - depd "~1.1.1" - destroy "~1.0.4" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.3.1" - serve-static@1.13.0: version "1.13.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.0.tgz#810c91db800e94ba287eae6b4e06caab9fdc16f1" @@ -8124,15 +8016,6 @@ serve-static@1.13.0: parseurl "~1.3.2" send "0.16.0" -serve-static@1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" - dependencies: - encodeurl "~1.0.1" - escape-html "~1.0.3" - parseurl "~1.3.2" - send "0.16.1" - set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -8261,7 +8144,7 @@ sliced@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" -smart-buffer@^1.0.13, smart-buffer@^1.0.4: +smart-buffer@^1.0.4: version "1.1.15" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16" @@ -8302,20 +8185,13 @@ socks-proxy-agent@2: extend "3" socks "~1.1.5" -socks@1.1.9: +socks@1.1.9, socks@~1.1.5: version "1.1.9" resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.9.tgz#628d7e4d04912435445ac0b6e459376cb3e6d691" dependencies: ip "^1.1.2" smart-buffer "^1.0.4" -socks@~1.1.5: - version "1.1.10" - resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a" - dependencies: - ip "^1.1.4" - smart-buffer "^1.0.13" - sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" @@ -8463,7 +8339,7 @@ string_decoder@^0.10.25, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" -string_decoder@~1.0.0, string_decoder@~1.0.3: +string_decoder@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" dependencies: From df079d29bdba34f5051261246c4df44ac42789ff Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 17:38:31 +0200 Subject: [PATCH 057/344] Set current page declaratively for People --- client/coral-admin/src/actions/community.js | 7 +++-- client/coral-admin/src/constants/community.js | 30 ++++++++++--------- client/coral-admin/src/reducers/community.js | 6 ++++ .../src/routes/Community/components/People.js | 6 ++-- .../src/routes/Community/containers/People.js | 16 +++++----- client/coral-ui/components/Paginate.js | 4 ++- 6 files changed, 40 insertions(+), 29 deletions(-) diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index b7102b8ff..89427f7bb 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -5,7 +5,7 @@ import { FETCH_USERS_SUCCESS, FETCH_USERS_FAILURE, SORT_UPDATE, - COMMENTERS_NEW_PAGE, + SET_PAGE, SET_ROLE, SET_COMMENTER_STATUS, SHOW_BANUSER_DIALOG, @@ -45,8 +45,9 @@ export const updateSorting = (sort) => ({ sort }); -export const newPage = () => ({ - type: COMMENTERS_NEW_PAGE +export const setPage = (page) => ({ + type: SET_PAGE, + page, }); export const setRole = (id, role) => (dispatch, _, {rest}) => { diff --git a/client/coral-admin/src/constants/community.js b/client/coral-admin/src/constants/community.js index dc11572ed..6e8a82bfe 100644 --- a/client/coral-admin/src/constants/community.js +++ b/client/coral-admin/src/constants/community.js @@ -1,18 +1,20 @@ -export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST'; -export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS'; -export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE'; +const prefix = 'COMMUNITY'; -export const SORT_UPDATE = 'SORT_UPDATE'; -export const COMMENTERS_NEW_PAGE = 'COMMENTERS_NEW_PAGE'; -export const SET_ROLE = 'SET_ROLE'; -export const SET_COMMENTER_STATUS = 'SET_COMMENTER_STATUS'; +export const FETCH_USERS_REQUEST = `${prefix}_FETCH_USERS_REQUEST`; +export const FETCH_USERS_SUCCESS = `${prefix}_FETCH_USERS_SUCCESS`; +export const FETCH_USERS_FAILURE = `${prefix}_FETCH_USERS_FAILURE`; -export const FETCH_FLAGGED_COMMENTERS_REQUEST = 'FETCH_FLAGGED_COMMENTERS_REQUEST'; -export const FETCH_FLAGGED_COMMENTERS_SUCCESS = 'FETCH_FLAGGED_COMMENTERS_SUCCESS'; -export const FETCH_FLAGGED_COMMENTERS_FAILURE = 'FETCH_FLAGGED_COMMENTERS_FAILURE'; +export const SORT_UPDATE = `${prefix}_SORT_UPDATE`; +export const SET_PAGE = `${prefix}_SET_PAGE`; +export const SET_ROLE = `${prefix}_SET_ROLE`; +export const SET_COMMENTER_STATUS = `${prefix}_SET_COMMENTER_STATUS`; -export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG'; -export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG'; +export const FETCH_FLAGGED_COMMENTERS_REQUEST = `${prefix}_FETCH_FLAGGED_COMMENTERS_REQUEST`; +export const FETCH_FLAGGED_COMMENTERS_SUCCESS = `${prefix}_FETCH_FLAGGED_COMMENTERS_SUCCESS`; +export const FETCH_FLAGGED_COMMENTERS_FAILURE = `${prefix}_FETCH_FLAGGED_COMMENTERS_FAILURE`; -export const SHOW_REJECT_USERNAME_DIALOG = 'SHOW_REJECT_USERNAME_DIALOG'; -export const HIDE_REJECT_USERNAME_DIALOG = 'HIDE_REJECT_USERNAME_DIALOG'; +export const SHOW_BANUSER_DIALOG = `${prefix}_SHOW_BANUSER_DIALOG`; +export const HIDE_BANUSER_DIALOG = `${prefix}_HIDE_BANUSER_DIALOG`; + +export const SHOW_REJECT_USERNAME_DIALOG = `${prefix}_SHOW_REJECT_USERNAME_DIALOG`; +export const HIDE_REJECT_USERNAME_DIALOG = `${prefix}_HIDE_REJECT_USERNAME_DIALOG`; diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index 31fb6aa17..cb71634cc 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -3,6 +3,7 @@ import { FETCH_USERS_SUCCESS, FETCH_USERS_FAILURE, SORT_UPDATE, + SET_PAGE, SET_ROLE, SET_COMMENTER_STATUS, SHOW_BANUSER_DIALOG, @@ -52,6 +53,11 @@ export default function community (state = initialState, action) { users, // Sets to normal array }; } + case SET_PAGE: + return { + ...state, + pagePeople: action.page, + }; case SET_ROLE : { const commenters = state.users; const idx = commenters.findIndex((el) => el.id === action.id); diff --git a/client/coral-admin/src/routes/Community/components/People.js b/client/coral-admin/src/routes/Community/components/People.js index 9626dd53f..664173cb0 100644 --- a/client/coral-admin/src/routes/Community/components/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -13,7 +13,7 @@ const People = (props) => { searchValue, onSearchChange, onHeaderClickHandler, - onNewPageHandler, + onPageChange, totalPages, setRole, setCommenterStatus, @@ -47,7 +47,7 @@ const People = (props) => { setCommenterStatus={setCommenterStatus} onHeaderClickHandler={onHeaderClickHandler} pageCount={totalPages} - onPageChange={onNewPageHandler} + onPageChange={onPageChange} /> : {t('community.no_results')} } @@ -62,7 +62,7 @@ People.propTypes = { searchValue: PropTypes.string, onSearchChange: PropTypes.func, totalPages: PropTypes.number, - onNewPageHandler: PropTypes.func, + onPageChange: PropTypes.func, setCommenterStatus: PropTypes.func.isRequired, setRole: PropTypes.func.isRequired, viewUserDetail: PropTypes.func.isRequired, diff --git a/client/coral-admin/src/routes/Community/containers/People.js b/client/coral-admin/src/routes/Community/containers/People.js index 561e0fcfb..5780529e8 100644 --- a/client/coral-admin/src/routes/Community/containers/People.js +++ b/client/coral-admin/src/routes/Community/containers/People.js @@ -9,7 +9,7 @@ import {viewUserDetail} from '../../../actions/userDetail'; import { fetchUsers, updateSorting, - newPage, + setPage, hideRejectUsernameDialog, setCommenterStatus, setRole, @@ -23,7 +23,7 @@ class PeopleContainer extends React.Component { fetchUsers = (query = {}) => { const {community} = this.props; - + this.props.fetchUsers({ value: this.state.searchValue, field: community.fieldPeople, @@ -62,19 +62,19 @@ class PeopleContainer extends React.Component { this.fetchUsers(); } - onNewPageHandler = ({selected}) => { + onPageChange = ({selected}) => { const page = selected + 1; - this.props.newPage(page); + this.props.setPage(page); this.fetchUsers({page}); } render() { - return bindActionCreators({ - newPage, + setPage, fetchUsers, updateSorting, hideRejectUsernameDialog, diff --git a/client/coral-ui/components/Paginate.js b/client/coral-ui/components/Paginate.js index 3a404db69..df19450c3 100644 --- a/client/coral-ui/components/Paginate.js +++ b/client/coral-ui/components/Paginate.js @@ -4,9 +4,10 @@ import ReactPaginate from 'react-paginate'; import styles from './Paginate.css'; import Icon from './Icon'; -const Paginate = ({pageCount, onPageChange}) => ( +const Paginate = ({pageCount, page, onPageChange}) => ( ( ); Paginate.propTypes = { + page: PropTypes.number.isRequired, pageCount: PropTypes.number.isRequired, onPageChange: PropTypes.func.isRequired, }; From 23da613f2b30466cd4932da2771db118d510678c Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 18:02:18 +0200 Subject: [PATCH 058/344] Declaratively set page --- .../src/actions/{assets.js => stories.js} | 9 ++++++++- client/coral-admin/src/constants/assets.js | 9 --------- client/coral-admin/src/constants/stories.js | 13 +++++++++++++ client/coral-admin/src/reducers/index.js | 4 ++-- .../src/reducers/{assets.js => stories.js} | 7 ++++++- .../src/routes/Community/components/People.js | 3 +++ .../src/routes/Community/components/Table.js | 10 ++++++---- .../src/routes/Community/containers/People.js | 1 + .../src/routes/Stories/components/Stories.js | 7 +++++-- .../src/routes/Stories/containers/Stories.js | 7 +++++-- 10 files changed, 49 insertions(+), 21 deletions(-) rename client/coral-admin/src/actions/{assets.js => stories.js} (93%) delete mode 100644 client/coral-admin/src/constants/assets.js create mode 100644 client/coral-admin/src/constants/stories.js rename client/coral-admin/src/reducers/{assets.js => stories.js} (86%) diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/stories.js similarity index 93% rename from client/coral-admin/src/actions/assets.js rename to client/coral-admin/src/actions/stories.js index d7c6d9448..536f478e1 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/stories.js @@ -4,11 +4,12 @@ import { FETCH_ASSETS_REQUEST, FETCH_ASSETS_SUCCESS, FETCH_ASSETS_FAILURE, + SET_PAGE, UPDATE_ASSET_STATE_REQUEST, UPDATE_ASSET_STATE_SUCCESS, UPDATE_ASSET_STATE_FAILURE, UPDATE_ASSETS -} from '../constants/assets'; +} from '../constants/stories'; import t from 'coral-framework/services/i18n'; @@ -52,3 +53,9 @@ export const updateAssetState = (id, closedAt) => (dispatch, _, {rest}) => { export const updateAssets = (assets) => (dispatch) => { dispatch({type: UPDATE_ASSETS, assets}); }; + +export const setPage = (page) => ({ + type: SET_PAGE, + page, +}); + diff --git a/client/coral-admin/src/constants/assets.js b/client/coral-admin/src/constants/assets.js deleted file mode 100644 index 20ec0a9c3..000000000 --- a/client/coral-admin/src/constants/assets.js +++ /dev/null @@ -1,9 +0,0 @@ -export const FETCH_ASSETS_REQUEST = 'FETCH_ASSETS_REQUEST'; -export const FETCH_ASSETS_SUCCESS = 'FETCH_ASSETS_SUCCESS'; -export const FETCH_ASSETS_FAILURE = 'FETCH_ASSETS_FAILURE'; - -export const UPDATE_ASSET_STATE_REQUEST = 'UPDATE_ASSET_STATE_REQUEST'; -export const UPDATE_ASSET_STATE_SUCCESS = 'UPDATE_ASSET_STATE_SUCCESS'; -export const UPDATE_ASSET_STATE_FAILURE = 'UPDATE_ASSET_STATE_FAILURE'; - -export const UPDATE_ASSETS = 'UPDATE_ASSETS'; diff --git a/client/coral-admin/src/constants/stories.js b/client/coral-admin/src/constants/stories.js new file mode 100644 index 000000000..52a9f9943 --- /dev/null +++ b/client/coral-admin/src/constants/stories.js @@ -0,0 +1,13 @@ +const prefix = 'STORIES'; + +export const FETCH_ASSETS_REQUEST = `${prefix}_FETCH_ASSETS_REQUEST`; +export const FETCH_ASSETS_SUCCESS = `${prefix}_FETCH_ASSETS_SUCCESS`; +export const FETCH_ASSETS_FAILURE = `${prefix}_FETCH_ASSETS_FAILURE`; + +export const UPDATE_ASSET_STATE_REQUEST = `${prefix}_UPDATE_ASSET_STATE_REQUEST`; +export const UPDATE_ASSET_STATE_SUCCESS = `${prefix}_UPDATE_ASSET_STATE_SUCCESS`; +export const UPDATE_ASSET_STATE_FAILURE = `${prefix}_UPDATE_ASSET_STATE_FAILURE`; + +export const UPDATE_ASSETS = `${prefix}_UPDATE_ASSETS`; + +export const SET_PAGE = `${prefix}_SET_PAGE`; diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 372fedbae..f7bbf3a3b 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,5 +1,5 @@ import auth from './auth'; -import assets from './assets'; +import stories from './stories'; import dashboard from './dashboard'; import configure from './configure'; import community from './community'; @@ -17,7 +17,7 @@ export default { configure, suspendUserDialog, userDetail, - assets, + stories, community, moderation, install, diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/stories.js similarity index 86% rename from client/coral-admin/src/reducers/assets.js rename to client/coral-admin/src/reducers/stories.js index b4ea8d21f..60c02bc99 100644 --- a/client/coral-admin/src/reducers/assets.js +++ b/client/coral-admin/src/reducers/stories.js @@ -1,4 +1,4 @@ -import * as actions from '../constants/assets'; +import * as actions from '../constants/stories'; import update from 'immutability-helper'; const initialState = { @@ -35,6 +35,11 @@ export default function assets (state = initialState, action) { return update(state, { assets: {$set: action.assets}, }); + case actions.SET_PAGE: + return { + ...state, + page: action.page, + }; default: return state; } diff --git a/client/coral-admin/src/routes/Community/components/People.js b/client/coral-admin/src/routes/Community/components/People.js index 664173cb0..75d08429a 100644 --- a/client/coral-admin/src/routes/Community/components/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -15,6 +15,7 @@ const People = (props) => { onHeaderClickHandler, onPageChange, totalPages, + page, setRole, setCommenterStatus, viewUserDetail, @@ -48,6 +49,7 @@ const People = (props) => { onHeaderClickHandler={onHeaderClickHandler} pageCount={totalPages} onPageChange={onPageChange} + page={page} /> : {t('community.no_results')} } @@ -59,6 +61,7 @@ const People = (props) => { People.propTypes = { onHeaderClickHandler: PropTypes.func, users: PropTypes.array, + page: PropTypes.number.isRequired, searchValue: PropTypes.string, onSearchChange: PropTypes.func, totalPages: PropTypes.number, diff --git a/client/coral-admin/src/routes/Community/components/Table.js b/client/coral-admin/src/routes/Community/components/Table.js index 8d650f165..22b02cfe4 100644 --- a/client/coral-admin/src/routes/Community/components/Table.js +++ b/client/coral-admin/src/routes/Community/components/Table.js @@ -24,7 +24,7 @@ const headers = [ } ]; -const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUserDetail, pageCount, onPageChange}) => ( +const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUserDetail, pageCount, page, onPageChange}) => (
    @@ -51,13 +51,13 @@ const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUs {row.created_at}
    - setCommenterStatus(row.id, status)}> + onChange={(status) => setCommenterStatus(row.id, status)}> + @@ -88,6 +89,7 @@ Table.propTypes = { setCommenterStatus: PropTypes.func.isRequired, viewUserDetail: PropTypes.func.isRequired, pageCount: PropTypes.number.isRequired, + page: PropTypes.number.isRequired, onPageChange: PropTypes.func.isRequired, }; diff --git a/client/coral-admin/src/routes/Community/containers/People.js b/client/coral-admin/src/routes/Community/containers/People.js index 5780529e8..1f52846c9 100644 --- a/client/coral-admin/src/routes/Community/containers/People.js +++ b/client/coral-admin/src/routes/Community/containers/People.js @@ -78,6 +78,7 @@ class PeopleContainer extends React.Component { totalPages={this.props.community.totalPagesPeople} setCommenterStatus={this.props.setCommenterStatus} setRole={this.props.setRole} + page={this.props.community.pagePeople} viewUserDetail={this.props.viewUserDetail} />; } diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index 73f5c60dc..3b2787aab 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -91,8 +91,9 @@ class Stories extends Component { ); } - onPageClick = ({selected}) => { + onPageChange = ({selected}) => { const page = selected + 1; + this.props.setPage(page); this.props.fetchAssets({page}); } @@ -157,7 +158,8 @@ class Stories extends Component { + page={assets.page - 1} + onPageChange={this.onPageChange} /> : {t('streams.empty_result')} } @@ -168,6 +170,7 @@ class Stories extends Component { Stories.propTypes = { assets: PropTypes.object, + setPage: PropTypes.func, fetchAssets: PropTypes.func, updateAssetState: PropTypes.func, }; diff --git a/client/coral-admin/src/routes/Stories/containers/Stories.js b/client/coral-admin/src/routes/Stories/containers/Stories.js index b940e46e6..28048f39d 100644 --- a/client/coral-admin/src/routes/Stories/containers/Stories.js +++ b/client/coral-admin/src/routes/Stories/containers/Stories.js @@ -1,12 +1,15 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; -import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets'; +import {fetchAssets, updateAssetState, setPage} from 'coral-admin/src/actions/stories'; import Stories from '../components/Stories'; -const mapStateToProps = ({assets}) => ({assets}); +const mapStateToProps = ({stories}) => ({ + assets: stories, +}); const mapDispatchToProps = (dispatch) => bindActionCreators({ + setPage, fetchAssets, updateAssetState, }, dispatch); From 20b5525c48be493f9829f213763bae7029bb3971 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 19:07:14 +0200 Subject: [PATCH 059/344] Refactor and fix query --- client/coral-admin/src/actions/stories.js | 12 +++ client/coral-admin/src/constants/stories.js | 2 + client/coral-admin/src/reducers/stories.js | 50 +++++++--- .../src/routes/Stories/components/Stories.js | 92 +++--------------- .../src/routes/Stories/containers/Stories.js | 94 ++++++++++++++++++- 5 files changed, 157 insertions(+), 93 deletions(-) diff --git a/client/coral-admin/src/actions/stories.js b/client/coral-admin/src/actions/stories.js index 536f478e1..2524b0c3c 100644 --- a/client/coral-admin/src/actions/stories.js +++ b/client/coral-admin/src/actions/stories.js @@ -5,6 +5,8 @@ import { FETCH_ASSETS_SUCCESS, FETCH_ASSETS_FAILURE, SET_PAGE, + SET_SEARCH_VALUE, + SET_CRITERIA, UPDATE_ASSET_STATE_REQUEST, UPDATE_ASSET_STATE_SUCCESS, UPDATE_ASSET_STATE_FAILURE, @@ -59,3 +61,13 @@ export const setPage = (page) => ({ page, }); +export const setSearchValue = (value) => ({ + type: SET_SEARCH_VALUE, + value, +}); + +export const setCriteria = (criteria) => ({ + type: SET_CRITERIA, + criteria, +}); + diff --git a/client/coral-admin/src/constants/stories.js b/client/coral-admin/src/constants/stories.js index 52a9f9943..d1994e19a 100644 --- a/client/coral-admin/src/constants/stories.js +++ b/client/coral-admin/src/constants/stories.js @@ -11,3 +11,5 @@ export const UPDATE_ASSET_STATE_FAILURE = `${prefix}_UPDATE_ASSET_STATE_FAILURE` export const UPDATE_ASSETS = `${prefix}_UPDATE_ASSETS`; export const SET_PAGE = `${prefix}_SET_PAGE`; +export const SET_SEARCH_VALUE = `${prefix}_SET_SEARCH_VALUE`; +export const SET_CRITERIA = `${prefix}_SET_CRITERIA`; diff --git a/client/coral-admin/src/reducers/stories.js b/client/coral-admin/src/reducers/stories.js index 60c02bc99..0d640e6c2 100644 --- a/client/coral-admin/src/reducers/stories.js +++ b/client/coral-admin/src/reducers/stories.js @@ -2,9 +2,16 @@ import * as actions from '../constants/stories'; import update from 'immutability-helper'; const initialState = { - byId: {}, - ids: [], - assets: [] + assets: { + byId: {}, + ids: [], + assets: [] + }, + searchValue: '', + criteria: { + sort: 'desc', + filter: 'all', + }, }; export default function assets (state = initialState, action) { @@ -16,30 +23,49 @@ export default function assets (state = initialState, action) { }, {}); return update(state, { - totalPages: {$set: action.totalPages}, - page: {$set: action.page}, - byId: {$set: assets}, - count: {$set: action.count}, - ids: {$set: Object.keys(assets)}, + assets: { + totalPages: {$set: action.totalPages}, + page: {$set: action.page}, + byId: {$set: assets}, + count: {$set: action.count}, + ids: {$set: Object.keys(assets)}, + }, }); } case actions.UPDATE_ASSET_STATE_REQUEST: return update(state, { - byId: { - [action.id]: { - closedAt: {$set: action.closedAt}, + assets: { + byId: { + [action.id]: { + closedAt: {$set: action.closedAt}, + }, }, }, }); case actions.UPDATE_ASSETS: return update(state, { - assets: {$set: action.assets}, + assets: { + assets: {$set: action.assets}, + }, }); case actions.SET_PAGE: return { ...state, page: action.page, }; + case actions.SET_SEARCH_VALUE: + return { + ...state, + searchValue: action.value, + }; + case actions.SET_CRITERIA: + return { + ...state, + criteria: { + ...state.criteria, + ...action.criteria, + }, + }; default: return state; } diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index 3b2787aab..cc99416bc 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -11,72 +11,11 @@ import EmptyCard from 'coral-admin/src/components/EmptyCard'; class Stories extends Component { - state = { - searchValue: '', - sort: 'desc', - filter: 'all', - statusMenus: {}, - timer: null, - } - - componentDidMount () { - this.fetchAssets(); - } - - onSettingChange = (setting) => (e) => { - const {searchValue} = this.state; - const criteria = {[setting]: e.target.value}; - - this.setState(criteria); - - this.props.fetchAssets({ - value: searchValue, - ...criteria, - }); - } - - onSearchChange = (e) => { - const {value} = e.target; - - this.setState((prevState) => { - prevState.searchValue = value; - clearTimeout(prevState.timer); - - prevState.timer = setTimeout(() => { - this.fetchAssets(); - }, 350); - return prevState; - }); - } - renderDate = (date) => { const d = new Date(date); return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; } - fetchAssets = (query) => { - const {searchValue, sort, filter, limit} = this.state; - - this.props.fetchAssets({ - value: searchValue, - sort, - filter, - limit, - ...query - }); - }; - - onStatusChange = async (closeStream, id) => { - const {updateAssetState} = this.props; - - try { - updateAssetState(id, closeStream ? Date.now() : null); - this.fetchAssets(); - } catch(err) { - console.error(err); - } - } - renderTitle = (title, {id}) => {title} renderStatus = (closedAt, {id}) => { @@ -84,26 +23,19 @@ class Stories extends Component { return ( this.onStatusChange(value, id)}> + onChange={(value) => this.props.onStatusChange(value, id)}> ); } - onPageChange = ({selected}) => { - const page = selected + 1; - this.props.setPage(page); - this.props.fetchAssets({page}); - } - render () { - const {searchValue, sort, filter} = this.state; - const {assets} = this.props; + const {assets, searchValue, sort, filter, onSearchChange, onSettingChange, onPageChange} = this.props; const assetsIds = sortBy(assets.ids.map((id) => assets.byId[id]), 'publication_date'); - if (this.state.sort === 'desc') { + if (sort === 'desc') { assetsIds.reverse(); } @@ -116,7 +48,7 @@ class Stories extends Component { type='text' value={searchValue} className={styles.searchBoxInput} - onChange={this.onSearchChange} + onChange={onSearchChange} placeholder={t('streams.search')}/>
    {t('streams.filter_streams')}
    @@ -125,7 +57,7 @@ class Stories extends Component { name='status filter' value={filter} childContainer='div' - onChange={this.onSettingChange('filter')} + onChange={onSettingChange('filter')} className={styles.radioGroup} > {t('streams.all')} @@ -137,7 +69,7 @@ class Stories extends Component { name='sort by' value={sort} childContainer='div' - onChange={this.onSettingChange('sort')} + onChange={onSettingChange('sort')} className={styles.radioGroup} > {t('streams.newest')} @@ -159,7 +91,7 @@ class Stories extends Component { + onPageChange={onPageChange} /> : {t('streams.empty_result')} } @@ -170,9 +102,13 @@ class Stories extends Component { Stories.propTypes = { assets: PropTypes.object, - setPage: PropTypes.func, - fetchAssets: PropTypes.func, - updateAssetState: PropTypes.func, + searchValue: PropTypes.string, + sort: PropTypes.string, + filter: PropTypes.string, + onStatusChange: PropTypes.func.isRequired, + onSearchChange: PropTypes.func.isRequired, + onPageChange: PropTypes.func.isRequired, + onSettingChange: PropTypes.func.isRequired, }; export default Stories; diff --git a/client/coral-admin/src/routes/Stories/containers/Stories.js b/client/coral-admin/src/routes/Stories/containers/Stories.js index 28048f39d..806bfc395 100644 --- a/client/coral-admin/src/routes/Stories/containers/Stories.js +++ b/client/coral-admin/src/routes/Stories/containers/Stories.js @@ -1,18 +1,106 @@ +import React, {Component} from 'react'; import {connect} from 'react-redux'; +import PropTypes from 'prop-types'; import {bindActionCreators} from 'redux'; -import {fetchAssets, updateAssetState, setPage} from 'coral-admin/src/actions/stories'; +import {fetchAssets, updateAssetState, setPage, setSearchValue, setCriteria} from 'coral-admin/src/actions/stories'; import Stories from '../components/Stories'; +class StoriesContainer extends Component { + timer=null; + + componentDidMount () { + this.fetchAssets(); + } + + onSearchChange = (e) => { + const {value} = e.target; + + this.props.setSearchValue(value); + clearTimeout(this.timer); + this.timer = setTimeout(() => { + this.fetchAssets(); + }, 350); + } + + onSettingChange = (setting) => (e) => { + const criteria = {[setting]: e.target.value}; + this.props.setCriteria(criteria); + this.fetchAssets(criteria); + } + + fetchAssets = (query) => { + const {searchValue, sort, filter, limit} = this.props; + + this.props.fetchAssets({ + value: searchValue, + sort, + filter, + limit, + ...query + }); + }; + + onStatusChange = async (closeStream, id) => { + const {updateAssetState} = this.props; + + try { + updateAssetState(id, closeStream ? Date.now() : null); + this.fetchAssets(); + } catch(err) { + console.error(err); + } + } + + onPageChange = ({selected}) => { + const page = selected + 1; + this.props.setPage(page); + this.fetchAssets({page}); + } + + render () { + return ; + } +} + const mapStateToProps = ({stories}) => ({ - assets: stories, + assets: stories.assets, + searchValue: stories.searchValue, + sort: stories.criteria.sort, + filter: stories.criteria.filter, + limit: stories.criteria.limit, }); const mapDispatchToProps = (dispatch) => bindActionCreators({ setPage, + setCriteria, + setSearchValue, fetchAssets, updateAssetState, }, dispatch); -export default connect(mapStateToProps, mapDispatchToProps)(Stories); +StoriesContainer.propTypes = { + assets: PropTypes.object, + searchValue: PropTypes.string, + sort: PropTypes.string, + filter: PropTypes.string, + limit: PropTypes.number, + setPage: PropTypes.func.isRequired, + setCriteria: PropTypes.func.isRequired, + setSearchValue: PropTypes.func.isRequired, + fetchAssets: PropTypes.func.isRequired, + updateAssetState: PropTypes.func.isRequired, +}; + +export default connect(mapStateToProps, mapDispatchToProps)(StoriesContainer); From e41c224e3d303cfc865d8fa9d59afeb50485d44a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 19:14:25 +0200 Subject: [PATCH 060/344] Remove unused --- client/coral-admin/src/routes/Stories/components/Stories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index cc99416bc..f8f553cab 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -79,7 +79,7 @@ class Stories extends Component { { assetsIds.length ?
    - + {t('streams.article')} {t('streams.pubdate')} From b4daf8fc98da50520e2114d29f81f3256f8a99b9 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 19:19:59 +0200 Subject: [PATCH 061/344] Move searchValue to Redux --- client/coral-admin/src/actions/community.js | 6 +++++ client/coral-admin/src/constants/community.js | 2 ++ client/coral-admin/src/reducers/community.js | 7 +++++ .../src/routes/Community/containers/People.js | 26 ++++++++----------- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 89427f7bb..6a091d214 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -6,6 +6,7 @@ import { FETCH_USERS_FAILURE, SORT_UPDATE, SET_PAGE, + SET_SEARCH_VALUE, SET_ROLE, SET_COMMENTER_STATUS, SHOW_BANUSER_DIALOG, @@ -50,6 +51,11 @@ export const setPage = (page) => ({ page, }); +export const setSearchValue = (value) => ({ + type: SET_SEARCH_VALUE, + value, +}); + export const setRole = (id, role) => (dispatch, _, {rest}) => { return rest(`/users/${id}/role`, {method: 'POST', body: {role}}) .then(() => { diff --git a/client/coral-admin/src/constants/community.js b/client/coral-admin/src/constants/community.js index 6e8a82bfe..eea946efb 100644 --- a/client/coral-admin/src/constants/community.js +++ b/client/coral-admin/src/constants/community.js @@ -18,3 +18,5 @@ export const HIDE_BANUSER_DIALOG = `${prefix}_HIDE_BANUSER_DIALOG`; export const SHOW_REJECT_USERNAME_DIALOG = `${prefix}_SHOW_REJECT_USERNAME_DIALOG`; export const HIDE_REJECT_USERNAME_DIALOG = `${prefix}_HIDE_REJECT_USERNAME_DIALOG`; + +export const SET_SEARCH_VALUE = `${prefix}_SET_SEARCH_VALUE`; diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js index cb71634cc..170804b60 100644 --- a/client/coral-admin/src/reducers/community.js +++ b/client/coral-admin/src/reducers/community.js @@ -4,6 +4,7 @@ import { FETCH_USERS_FAILURE, SORT_UPDATE, SET_PAGE, + SET_SEARCH_VALUE, SET_ROLE, SET_COMMENTER_STATUS, SHOW_BANUSER_DIALOG, @@ -17,6 +18,7 @@ const initialState = { isFetchingPeople: false, errorPeople: '', users: [], + searchValue: '', fieldPeople: 'created_at', ascPeople: false, totalPagesPeople: 0, @@ -107,6 +109,11 @@ export default function community (state = initialState, action) { user: action.user, rejectUsernameDialog: true }; + case SET_SEARCH_VALUE: + return { + ...state, + searchValue: action.value, + }; default : return state; } diff --git a/client/coral-admin/src/routes/Community/containers/People.js b/client/coral-admin/src/routes/Community/containers/People.js index 1f52846c9..063eaef2f 100644 --- a/client/coral-admin/src/routes/Community/containers/People.js +++ b/client/coral-admin/src/routes/Community/containers/People.js @@ -13,19 +13,17 @@ import { hideRejectUsernameDialog, setCommenterStatus, setRole, + setSearchValue, } from '../../../actions/community'; class PeopleContainer extends React.Component { - state = { - searchValue: '', - timer: null - }; + timer=null; fetchUsers = (query = {}) => { const {community} = this.props; this.props.fetchUsers({ - value: this.state.searchValue, + value: community.searchValue, field: community.fieldPeople, asc: community.ascPeople, ...query @@ -46,15 +44,11 @@ class PeopleContainer extends React.Component { onSearchChange = (e) => { const value = e.target.value; - this.setState((prevState) => { - prevState.searchValue = value; - clearTimeout(prevState.timer); - - prevState.timer = setTimeout(() => { - this.fetchUsers({value}); - }, 350); - return prevState; - }); + this.props.setSearchValue(value); + clearTimeout(this.timer); + this.timer = setTimeout(() => { + this.fetchUsers({value}); + }, 350); } onHeaderClickHandler = (sort) => { @@ -71,7 +65,7 @@ class PeopleContainer extends React.Component { render() { return setCommenterStatus, setRole, viewUserDetail, + setSearchValue, }, dispatch); export default connect(null, mapDispatchToProps)(PeopleContainer); From d472223ee3ed9084ae2d01412350c14f3caf95de Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 25 Oct 2017 19:32:47 +0200 Subject: [PATCH 062/344] Fix/Implement sorting for stories --- client/coral-admin/src/reducers/stories.js | 2 +- .../src/routes/Stories/components/Stories.js | 24 +++++++------------ .../src/routes/Stories/containers/Stories.js | 10 ++++---- routes/api/assets/index.js | 2 +- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/client/coral-admin/src/reducers/stories.js b/client/coral-admin/src/reducers/stories.js index 0d640e6c2..271ec8cfe 100644 --- a/client/coral-admin/src/reducers/stories.js +++ b/client/coral-admin/src/reducers/stories.js @@ -9,7 +9,7 @@ const initialState = { }, searchValue: '', criteria: { - sort: 'desc', + asc: 'false', filter: 'all', }, }; diff --git a/client/coral-admin/src/routes/Stories/components/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js index f8f553cab..866defde4 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -2,7 +2,6 @@ import React, {Component} from 'react'; import cn from 'classnames'; import {Link} from 'react-router'; import PropTypes from 'prop-types'; -import sortBy from 'lodash/sortBy'; import {Dropdown, Option, Paginate, Icon} from 'coral-ui'; import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; import t from 'coral-framework/services/i18n'; @@ -31,13 +30,8 @@ class Stories extends Component { } render () { - const {assets, searchValue, sort, filter, onSearchChange, onSettingChange, onPageChange} = this.props; - - const assetsIds = sortBy(assets.ids.map((id) => assets.byId[id]), 'publication_date'); - - if (sort === 'desc') { - assetsIds.reverse(); - } + const {assets, searchValue, filter, onSearchChange, onSettingChange, onPageChange, asc} = this.props; + const rows = assets.ids.map((id) => assets.byId[id]); return (
    @@ -67,19 +61,19 @@ class Stories extends Component {
    {t('streams.sort_by')}
    - {t('streams.newest')} - {t('streams.oldest')} + {t('streams.newest')} + {t('streams.oldest')}
    { - assetsIds.length + rows.length ?
    - + {t('streams.article')} {t('streams.pubdate')} @@ -103,7 +97,7 @@ class Stories extends Component { Stories.propTypes = { assets: PropTypes.object, searchValue: PropTypes.string, - sort: PropTypes.string, + asc: PropTypes.string, filter: PropTypes.string, onStatusChange: PropTypes.func.isRequired, onSearchChange: PropTypes.func.isRequired, diff --git a/client/coral-admin/src/routes/Stories/containers/Stories.js b/client/coral-admin/src/routes/Stories/containers/Stories.js index 806bfc395..d91bed968 100644 --- a/client/coral-admin/src/routes/Stories/containers/Stories.js +++ b/client/coral-admin/src/routes/Stories/containers/Stories.js @@ -29,11 +29,11 @@ class StoriesContainer extends Component { } fetchAssets = (query) => { - const {searchValue, sort, filter, limit} = this.props; + const {searchValue, asc, filter, limit} = this.props; this.props.fetchAssets({ value: searchValue, - sort, + asc, filter, limit, ...query @@ -61,7 +61,7 @@ class StoriesContainer extends Component { return ({ assets: stories.assets, searchValue: stories.searchValue, - sort: stories.criteria.sort, + asc: stories.criteria.asc, filter: stories.criteria.filter, limit: stories.criteria.limit, }); @@ -92,7 +92,7 @@ const mapDispatchToProps = (dispatch) => StoriesContainer.propTypes = { assets: PropTypes.object, searchValue: PropTypes.string, - sort: PropTypes.string, + asc: PropTypes.string, filter: PropTypes.string, limit: PropTypes.number, setPage: PropTypes.func.isRequired, diff --git a/routes/api/assets/index.js b/routes/api/assets/index.js index 4007517de..165b2cd99 100644 --- a/routes/api/assets/index.js +++ b/routes/api/assets/index.js @@ -39,7 +39,7 @@ router.get('/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, nex const { value = '', field = 'created_at', - page = 1, + page = 1, asc = 'false', filter = 'all', limit = 20, From dbd7f26311e325382b4f3e55decda4a2656dfb0f Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 25 Oct 2017 15:57:57 -0300 Subject: [PATCH 063/344] Adding pause after login --- test/e2e/specs/02_admin.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/e2e/specs/02_admin.js b/test/e2e/specs/02_admin.js index 463249eeb..03f460c1b 100644 --- a/test/e2e/specs/02_admin.js +++ b/test/e2e/specs/02_admin.js @@ -16,7 +16,11 @@ module.exports = { .setValue('@emailInput', admin.email) .setValue('@passwordInput', admin.password) .waitForElementVisible('@signInButton') - .click('@signInButton') + .click('@signInButton'); + + client.pause(3000); + + adminPage .waitForElementVisible('@moderationContainer'); }, From b06ae9001ea89297ad6f3b403c390b9f2d98113e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 25 Oct 2017 16:31:46 -0300 Subject: [PATCH 064/344] Adding missing classes --- client/coral-admin/src/components/Drawer.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/components/Drawer.js b/client/coral-admin/src/components/Drawer.js index 0385b1773..3aee31c95 100644 --- a/client/coral-admin/src/components/Drawer.js +++ b/client/coral-admin/src/components/Drawer.js @@ -5,6 +5,7 @@ import {IndexLink, Link} from 'react-router'; import styles from './Drawer.css'; import t from 'coral-framework/services/i18n'; import {can} from 'coral-framework/services/perms'; +import cn from 'classnames'; const CoralDrawer = ({handleLogout, auth = {}}) => ( @@ -12,7 +13,7 @@ const CoralDrawer = ({handleLogout, auth = {}}) => (
    {t('configure.dashboard')} @@ -20,19 +21,21 @@ const CoralDrawer = ({handleLogout, auth = {}}) => ( { can(auth.user, 'MODERATE_COMMENTS') && ( {t('configure.moderate')} ) } - {t('configure.stories')} - {t('configure.community')} @@ -41,7 +44,7 @@ const CoralDrawer = ({handleLogout, auth = {}}) => ( can(auth.user, 'UPDATE_CONFIG') && ( {t('configure.configure')} From 59acdc5c02c6645bea49a868fa4acc88f7457bd5 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 25 Oct 2017 16:53:18 -0300 Subject: [PATCH 065/344] new e2e test --- client/coral-admin/src/components/Drawer.js | 2 +- test/e2e/page_objects/admin.js | 6 ++++- test/e2e/specs/02_admin.js | 28 +++++++++++++-------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/client/coral-admin/src/components/Drawer.js b/client/coral-admin/src/components/Drawer.js index 3aee31c95..70da8ad5e 100644 --- a/client/coral-admin/src/components/Drawer.js +++ b/client/coral-admin/src/components/Drawer.js @@ -8,7 +8,7 @@ import {can} from 'coral-framework/services/perms'; import cn from 'classnames'; const CoralDrawer = ({handleLogout, auth = {}}) => ( - + { auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
    diff --git a/test/e2e/page_objects/admin.js b/test/e2e/page_objects/admin.js index ffdcf8a29..92f2ac0e1 100644 --- a/test/e2e/page_objects/admin.js +++ b/test/e2e/page_objects/admin.js @@ -15,9 +15,13 @@ module.exports = { 'passwordInput': '.talk-admin-login-sign-in #password', 'signInButton': '.talk-admin-login-sign-in-button', 'storiesNav': '.talk-admin-nav-stories', + 'storiesDrawerNav': '.talk-admin-drawer-nav .talk-admin-nav-stories', 'storiesSection': '.talk-admin-stories', 'communityNav': '.talk-admin-nav-community', + 'communityDrawerNav': '.talk-admin-drawer-nav .talk-admin-nav-community', 'communitySection': '.talk-admin-community', - 'moderationContainer': '.talk-admin-moderation-container' + 'moderationContainer': '.talk-admin-moderation-container', + 'drawerButton': '.mdl-layout__drawer-button', + 'drawerOverlay': 'div.mdl-layout__obfuscator.is-visible', } }; diff --git a/test/e2e/specs/02_admin.js b/test/e2e/specs/02_admin.js index 03f460c1b..4a60780bb 100644 --- a/test/e2e/specs/02_admin.js +++ b/test/e2e/specs/02_admin.js @@ -1,10 +1,9 @@ module.exports = { '@tags': ['admin', 'login'], - beforeEach: (client) => { - - // Testing Desktop - client.resizeWindow(1280, 800); - }, + beforeEach: (client) => { + + client.resizeWindow(1024, 800); + }, 'Admin logs in': (client) => { const adminPage = client.page.admin(); const {testData: {admin}} = client.globals; @@ -29,9 +28,14 @@ module.exports = { adminPage .navigate() - .waitForElementVisible('@storiesNav') - .click('@storiesNav') + .waitForElementVisible('@drawerButton') + .click('@drawerButton') + .waitForElementVisible('@storiesDrawerNav') + .click('@storiesDrawerNav') + .waitForElementVisible('@drawerOverlay') + .click('@drawerOverlay') .waitForElementVisible('@storiesSection'); + }, 'Admin goes to Community': (client) => { @@ -39,9 +43,13 @@ module.exports = { adminPage .navigate() - .waitForElementVisible('@communityNav') - .click('@communityNav') - .waitForElementVisible('@communitySection'); + .waitForElementVisible('@drawerButton') + .click('@drawerButton') + .waitForElementVisible('@communityDrawerNav') + .click('@communityDrawerNav') + .waitForElementVisible('@drawerOverlay') + .click('@drawerOverlay') + .waitForElementVisible('@communitySection'); }, after: (client) => { client.end(); From 6647e22d1cf894e8863f486da292f085920a107f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 25 Oct 2017 14:05:04 -0600 Subject: [PATCH 066/344] Update circle.yml --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 9f35736c6..932643ff6 100644 --- a/circle.yml +++ b/circle.yml @@ -50,7 +50,7 @@ test: - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test # Check dependancies using nsp. - nsp check - - yarn e2e-ci + # - yarn e2e-ci deployment: release: From 4089d09681475c3c59da7226c1296e977d4b0962 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 26 Oct 2017 09:19:29 -0600 Subject: [PATCH 067/344] fix regression missing yarn cache clean --- Dockerfile.onbuild | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile.onbuild b/Dockerfile.onbuild index a4bff7095..20d34ffb9 100644 --- a/Dockerfile.onbuild +++ b/Dockerfile.onbuild @@ -9,9 +9,10 @@ ONBUILD ARG TALK_PLUGINS_JSON # Bundle app source ONBUILD COPY . /usr/src/app -# At this stage, we need to install the development dependancies again because -# we need to have webpack available. We then build the new dependancies and -# clear out the development dependancies again. After this we of course need to +# At this stage, we need to install the development dependencies again because +# we need to have webpack available. We then build the new dependencies and +# clear out the development dependencies again. After this we of course need to # clear out the yarn cache, this saves quite a lot of size. ONBUILD RUN cli plugins reconcile && \ - yarn build \ No newline at end of file + yarn build && \ + yarn cache clean \ No newline at end of file From b84daf0422cf967f616265b7db82c8ae70f0a8b4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 26 Oct 2017 17:30:55 +0200 Subject: [PATCH 068/344] Set resolution to 1600x1200 bs --- nightwatch-browserstack.conf.js | 1 + 1 file changed, 1 insertion(+) diff --git a/nightwatch-browserstack.conf.js b/nightwatch-browserstack.conf.js index 9e8e334c5..ac8f90d27 100644 --- a/nightwatch-browserstack.conf.js +++ b/nightwatch-browserstack.conf.js @@ -26,6 +26,7 @@ const nightwatch_config = { // Disable this, as it makes bs slow and brittle. 'browserstack.networkLogs': false, + 'browserstack.resolution': '1600x1200', } }, chrome: { From ad4a5595f9e8b184c5f798421c8688c3a0f52c05 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 27 Oct 2017 13:19:00 +0200 Subject: [PATCH 069/344] Enable screenshot on failure for bs --- nightwatch-browserstack.conf.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nightwatch-browserstack.conf.js b/nightwatch-browserstack.conf.js index ac8f90d27..bf0ca2284 100644 --- a/nightwatch-browserstack.conf.js +++ b/nightwatch-browserstack.conf.js @@ -27,7 +27,13 @@ const nightwatch_config = { // Disable this, as it makes bs slow and brittle. 'browserstack.networkLogs': false, 'browserstack.resolution': '1600x1200', - } + }, + screenshots : { + enabled: true, + on_failure: true, + on_error: true, + path: process.env.REPORTS_FOLDER || './test/e2e/reports', + }, }, chrome: { desiredCapabilities: { From 5f5c64870ab9ee43dda0dbdaae875caeccae3b58 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 27 Oct 2017 19:00:48 +0200 Subject: [PATCH 070/344] Stabilize e2e --- scripts/e2e-ci.sh | 3 ++ test/e2e/specs/02_admin.js | 6 ++-- test/e2e/specs/03_embedStream.js | 52 +++++++++++++++++++++++++++----- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/scripts/e2e-ci.sh b/scripts/e2e-ci.sh index e0e94b4bf..f64223f32 100755 --- a/scripts/e2e-ci.sh +++ b/scripts/e2e-ci.sh @@ -1,5 +1,8 @@ #!/bin/bash +CIRCLE_TEST_REPORTS=${CIRCLE_TEST_REPORTS:-./test/e2e/reports} +CIRCLE_BRANCH=${CIRCLE_BRANCH:-master} + if [[ "${CIRCLE_BRANCH}" == "master" ]]; then exitCode=0 diff --git a/test/e2e/specs/02_admin.js b/test/e2e/specs/02_admin.js index 4a60780bb..4744d2116 100644 --- a/test/e2e/specs/02_admin.js +++ b/test/e2e/specs/02_admin.js @@ -1,9 +1,9 @@ module.exports = { '@tags': ['admin', 'login'], beforeEach: (client) => { - + client.resizeWindow(1024, 800); - }, + }, 'Admin logs in': (client) => { const adminPage = client.page.admin(); const {testData: {admin}} = client.globals; @@ -17,8 +17,6 @@ module.exports = { .waitForElementVisible('@signInButton') .click('@signInButton'); - client.pause(3000); - adminPage .waitForElementVisible('@moderationContainer'); }, diff --git a/test/e2e/specs/03_embedStream.js b/test/e2e/specs/03_embedStream.js index 57f9a8fd2..5381a5558 100644 --- a/test/e2e/specs/03_embedStream.js +++ b/test/e2e/specs/03_embedStream.js @@ -1,3 +1,31 @@ +class SortedWindowHandler { + + constructor(client) { + this.client = client; + this.client.windowHandles((result) => { + this.handles = result.value; + if (this.handles.length > 2) { + throw new Error('SortedWindowHandler must be created before new windows were created.'); + } + }); + } + + windowHandles(callback) { + this.client.windowHandles((result) => { + console.log(result); + this.handles = this.handles.filter((handle) => result.value.includes(handle)); + const remaining = result.value.filter((handle) => !this.handles.includes(handle)); + if (remaining.length === 1) { + this.handles.push(remaining[0]); + } + if (remaining.length > 1) { + throw new Error('Cannot detect new window handle, because more than one windows was created.'); + } + callback(this.handles); + }); + } +} + module.exports = { '@tags': ['embedStream', 'login'], 'creates a new asset': (client) => { @@ -18,16 +46,15 @@ module.exports = { .navigate() .getEmbedSection(); + const windowHandler = new SortedWindowHandler(client); + embed .waitForElementVisible('@signInButton') .click('@signInButton'); - client.pause(3000); - // Focusing on the Login PopUp - client.windowHandles((result) => { - const handle = result.value[1]; - client.switchWindow(handle); + windowHandler.windowHandles((handles) => { + client.switchWindow(handles[1]); }); const login = client.page.login(); @@ -45,10 +72,19 @@ module.exports = { .waitForElementVisible('@loginButton') .click('@loginButton'); + // Give a tiny bit of time to let popup close. + client.pause(50); + + if (client.capabilities.browserName === 'MicrosoftEdge') { + + // More time for edge. + // https://www.browserstack.com/automate/builds/1ceccf4efb4683b7feb890f45a32b5922b40ed3f/sessions/7393dbfda8387e43b6d5851f359b0c07db414973 + client.pause(1000); + } + // Focusing on the Embed Window - client.windowHandles((result) => { - const handle = result.value[0]; - client.switchWindow(handle); + windowHandler.windowHandles((handles) => { + client.switchWindow(handles[0]); }); }, 'user posts a comment': (client) => { From bfe82218ed44b21ffa041f7480875359c3996e22 Mon Sep 17 00:00:00 2001 From: blackcathacker Date: Sun, 29 Oct 2017 18:09:18 -0700 Subject: [PATCH 071/344] Send user an email when they are banned #1050 --- services/email/banned.html.ejs | 1 + services/email/banned.txt.ejs | 1 + services/users.js | 21 ++++++++++++++++++++- test/server/services/users.js | 21 ++++++++++++++++++++- 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 services/email/banned.html.ejs create mode 100644 services/email/banned.txt.ejs diff --git a/services/email/banned.html.ejs b/services/email/banned.html.ejs new file mode 100644 index 000000000..4fd8f191c --- /dev/null +++ b/services/email/banned.html.ejs @@ -0,0 +1 @@ +<%= body.replace(/\n/g, '
    ') %> diff --git a/services/email/banned.txt.ejs b/services/email/banned.txt.ejs new file mode 100644 index 000000000..b36560ec5 --- /dev/null +++ b/services/email/banned.txt.ejs @@ -0,0 +1 @@ +<%= body %> diff --git a/services/users.js b/services/users.js index 9906ef6c0..2e5c66d40 100644 --- a/services/users.js +++ b/services/users.js @@ -398,7 +398,7 @@ module.exports = class UsersService { // TODO: current updating status behavior is weird. // once a user has been `APPROVED` its status cannot be // changed anymore. - return UserModel.findOneAndUpdate({ + const user = await UserModel.findOneAndUpdate({ id, status: { $ne: 'APPROVED' @@ -410,6 +410,25 @@ module.exports = class UsersService { }, { new: true, }); + + if (status === 'BANNED') { + let localProfile = user.profiles.find((profile) => profile.provider === 'local'); + if (localProfile) { + const options = + { + template: 'banned', // needed to know which template to render! + locals: { // specifies the template locals. + body: 'In accordance with The Coral Project’s community guidelines, your account has been banned. You are now longer allowed to comment, flag or engage with our community.' + }, + subject: '[Talk] Your account has been banned', + to: localProfile.id // This only works if the user has registered via e-mail. + // We may want a standard way to access a user's e-mail address in the future + }; + await MailerService.sendSimple(options); + } + + } + return user; } /** diff --git a/test/server/services/users.js b/test/server/services/users.js index dddd5eda8..b4a15e3ba 100644 --- a/test/server/services/users.js +++ b/test/server/services/users.js @@ -1,8 +1,11 @@ const UsersService = require('../../../services/users'); const SettingsService = require('../../../services/settings'); +const MailerService = require('../../../services/mailer'); const chai = require('chai'); chai.use(require('chai-as-promised')); +const sinon = require('sinon'); +chai.use(require('sinon-chai')); const expect = chai.expect; describe('services.UsersService', () => { @@ -15,7 +18,7 @@ describe('services.UsersService', () => { mockUsers = await UsersService.createLocalUsers([{ email: 'stampi@gmail.com', username: 'Stampi', - password: '1Coral!-' + password: '1Coral!-', }, { email: 'sockmonster@gmail.com', username: 'Sockmonster', @@ -25,6 +28,12 @@ describe('services.UsersService', () => { username: 'Marvel', password: '3Coral!3' }]); + + sinon.spy(MailerService, 'sendSimple'); + }); + + afterEach(() => { + MailerService.sendSimple.restore(); }); describe('#findById()', () => { @@ -149,7 +158,11 @@ describe('services.UsersService', () => { .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { expect(user).to.have.property('status', 'ACTIVE'); + }) + .then(() => { + expect(MailerService.sendSimple).to.not.have.been.called; }); + }); }); @@ -188,6 +201,12 @@ describe('services.UsersService', () => { .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { expect(user).to.have.property('status', 'BANNED'); + }) + .then(() => { + expect(MailerService.sendSimple).to.have.been.calledWithMatch({ + template: 'banned', + to: mockUsers[0].profiles[0].id + }); }); }); From 82a332c787f49c624f6d6da9f3453c0d8885ddd9 Mon Sep 17 00:00:00 2001 From: blackcathacker Date: Sun, 29 Oct 2017 19:48:42 -0700 Subject: [PATCH 072/344] disable report button when comment already reported --- client/talk-plugin-flags/components/FlagButton.js | 9 ++++++++- client/talk-plugin-flags/components/styles.css | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/client/talk-plugin-flags/components/FlagButton.js b/client/talk-plugin-flags/components/FlagButton.js index 24c50416a..18db85c03 100644 --- a/client/talk-plugin-flags/components/FlagButton.js +++ b/client/talk-plugin-flags/components/FlagButton.js @@ -158,9 +158,16 @@ export default class FlagButton extends Component {
    - -
    +
    flag @@ -127,9 +121,11 @@ class User extends React.Component {
    @@ -141,4 +137,16 @@ class User extends React.Component { } } +User.propTypes = { + showSuspendUserDialog: PropTypes.func, + showBanUserDialog: PropTypes.func, + viewUserDetail: PropTypes.object, + showRejectUsernameDialog: PropTypes.func, + approveUser: PropTypes.func, + user: PropTypes.object, + className: PropTypes.string, + selected: PropTypes.bool, + me: PropTypes.object, +}; + export default User; From 302d30cde1fe39412756686018b444370eec2c68 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 30 Oct 2017 08:54:32 -0300 Subject: [PATCH 077/344] Adding missing classnames and proptypes, plus e2e testing covering userstatus --- .../coral-admin/src/components/ui/Header.js | 6 +- .../Community/components/FlaggedUser.js | 8 +- .../components/RejectUsernameDialog.js | 27 +++-- .../src/components/SuspendedAccount.js | 10 +- .../components/RestrictedContent.js | 15 ++- .../components/RestrictedMessageBox.js | 11 +- test/e2e/page_objects/admin.js | 2 + test/e2e/page_objects/adminCommunity.js | 8 ++ test/e2e/page_objects/embedStream.js | 9 +- test/e2e/specs/02_admin.js | 1 + test/e2e/specs/04_userStatus.js | 103 +++++++++++++++++- 11 files changed, 168 insertions(+), 32 deletions(-) diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index c5af63786..bd7fbcf0c 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -76,9 +76,9 @@ const CoralHeader = ({ }
      -
    • +
    • - + showShortcuts(true)}>{t('configure.shortcuts')} @@ -91,7 +91,7 @@ const CoralHeader = ({ Report a bug or give feedback - + {t('configure.sign_out')} diff --git a/client/coral-admin/src/routes/Community/components/FlaggedUser.js b/client/coral-admin/src/routes/Community/components/FlaggedUser.js index 69f94179d..b2156f479 100644 --- a/client/coral-admin/src/routes/Community/components/FlaggedUser.js +++ b/client/coral-admin/src/routes/Community/components/FlaggedUser.js @@ -45,10 +45,8 @@ class User extends React.Component { } = this.props; return ( -
    • +
    • @@ -140,7 +138,7 @@ class User extends React.Component { User.propTypes = { showSuspendUserDialog: PropTypes.func, showBanUserDialog: PropTypes.func, - viewUserDetail: PropTypes.object, + viewUserDetail: PropTypes.func, showRejectUsernameDialog: PropTypes.func, approveUser: PropTypes.func, user: PropTypes.object, diff --git a/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js b/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js index 20c411e52..18e9394a5 100644 --- a/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js +++ b/client/coral-admin/src/routes/Community/components/RejectUsernameDialog.js @@ -1,6 +1,6 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; - +import cn from 'classnames'; import {Dialog, Button} from 'coral-ui'; import styles from './RejectUsernameDialog.css'; @@ -29,12 +29,6 @@ class RejectUsernameDialog extends Component { state = {email: '', stage: 0} - static propTypes = { - stage: PropTypes.number, - handleClose: PropTypes.func.isRequired, - rejectUsername: PropTypes.func.isRequired - } - componentDidMount() { this.setState({email: t('reject_username.email_message_reject'), about: t('reject_username.username')}); } @@ -76,7 +70,7 @@ class RejectUsernameDialog extends Component { const {stage} = this.state; return