diff --git a/.eslintrc.json b/.eslintrc.json index d99b2254b..8b737cbd2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -28,7 +28,6 @@ "yoda": [1], "no-path-concat": [2], "eol-last": [1], - "no-continue": [1], "no-nested-ternary": [1], "no-tabs": [2], "no-unneeded-ternary": [1], diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 6d55d7b18..bc9747f34 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -4,6 +4,7 @@ import {Router, Route, IndexRoute, browserHistory} from 'react-router'; import ModerationQueue from 'containers/ModerationQueue/ModerationQueue'; import CommentStream from 'containers/CommentStream/CommentStream'; import Configure from 'containers/Configure/Configure'; +import Streams from 'containers/Streams/Streams'; import CommunityContainer from 'containers/Community/CommunityContainer'; import LayoutContainer from 'containers/LayoutContainer'; @@ -13,6 +14,7 @@ const routes = ( + ); diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js new file mode 100644 index 000000000..f431f1ad6 --- /dev/null +++ b/client/coral-admin/src/actions/assets.js @@ -0,0 +1,36 @@ +import { + FETCH_ASSETS_REQUEST, + FETCH_ASSETS_SUCCESS, + FETCH_ASSETS_FAILURE, + UPDATE_ASSET_STATE_REQUEST, + UPDATE_ASSET_STATE_SUCCESS, + UPDATE_ASSET_STATE_FAILURE +} from '../constants/assets'; +import coralApi from '../../../coral-framework/helpers/response'; + +/** + * Action disptacher related to assets + */ + +// 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) => { + dispatch({type: FETCH_ASSETS_REQUEST}); + return coralApi(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`) + .then(({result, count}) => + dispatch({type: FETCH_ASSETS_SUCCESS, + assets: result, + count + })) + .catch(error => dispatch({type: FETCH_ASSETS_FAILURE, error})); +}; + +// Update an asset state +// Get comments to fill each of the three lists on the mod queue +export const updateAssetState = (id, closedAt) => (dispatch) => { + dispatch({type: UPDATE_ASSET_STATE_REQUEST}); + return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}}) + .then(() => + dispatch({type: UPDATE_ASSET_STATE_SUCCESS})) + .catch(error => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error})); +}; diff --git a/client/coral-admin/src/actions/comments.js b/client/coral-admin/src/actions/comments.js index 6014979b1..141156d4f 100644 --- a/client/coral-admin/src/actions/comments.js +++ b/client/coral-admin/src/actions/comments.js @@ -1,10 +1,11 @@ import coralApi from '../../../coral-framework/helpers/response'; -import * as commentActions from '../constants/comments'; +import * as commentTypes from '../constants/comments'; +import * as actionTypes from '../constants/actions'; // Get comments to fill each of the three lists on the mod queue export const fetchModerationQueueComments = () => { return dispatch => { - dispatch({type: commentActions.COMMENTS_MODERATION_QUEUE_FETCH}); + dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST}); return Promise.all([ coralApi('/queue/comments/pending'), coralApi('/comments?status=rejected'), @@ -20,11 +21,12 @@ export const fetchModerationQueueComments = () => { actions: [...pending.actions, ...rejected.actions, ...flagged.actions] }; }) - .then(({comments, users}) => { + .then(({comments, users, actions}) => { /* Post comments and users to redux store. Actions will be posted when they are needed. */ - dispatch({type: commentActions.USERS_MODERATION_QUEUE_FETCH_SUCCESS, users}); - dispatch({type: commentActions.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS, comments}); + dispatch({type: commentTypes.USERS_MODERATION_QUEUE_FETCH_SUCCESS, users}); + dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS, comments}); + dispatch({type: actionTypes.ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS, actions}); }); }; @@ -35,8 +37,8 @@ export const createComment = (name, body, _csrf) => { return dispatch => { const comment = {body, name, _csrf}; return coralApi('/comments', {method: 'POST', comment}) - .then(res => dispatch({type: commentActions.COMMENT_CREATE_SUCCESS, comment: res})) - .catch(error => dispatch({type: commentActions.COMMENT_CREATE_FAILED, error})); + .then(res => dispatch({type: commentTypes.COMMENT_CREATE_SUCCESS, comment: res})) + .catch(error => dispatch({type: commentTypes.COMMENT_CREATE_FAILED, error})); }; }; @@ -47,23 +49,23 @@ export const createComment = (name, body, _csrf) => { // Update a comment. Now to update a comment we need to send back the whole object export const updateStatus = (status, comment) => { return dispatch => { - dispatch({type: commentActions.COMMENT_STATUS_UPDATE, id: comment.id, status}); + dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_REQUEST, id: comment.id, status}); return coralApi(`/comments/${comment.id}/status`, {method: 'PUT', body: {status}}) - .then(res => dispatch({type: commentActions.COMMENT_UPDATE_SUCCESS, res})) - .catch(error => dispatch({type: commentActions.COMMENT_UPDATE_FAILED, error})); + .then(res => dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_SUCCESS, res})) + .catch(error => dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_FAILURE, error})); }; }; export const flagComment = id => (dispatch, getState) => { - dispatch({type: commentActions.COMMENT_FLAG, id}); + dispatch({type: commentTypes.COMMENT_FLAG, id}); dispatch({type: 'COMMENT_UPDATE', comment: getState().comments.get('byId').get(id)}); }; // Dialog Actions export const showBanUserDialog = (userId, userName, commentId) => { - return {type: commentActions.SHOW_BANUSER_DIALOG, userId, userName, commentId}; + return {type: commentTypes.SHOW_BANUSER_DIALOG, userId, userName, commentId}; }; export const hideBanUserDialog = (showDialog) => { - return {type: commentActions.HIDE_BANUSER_DIALOG, showDialog}; + return {type: commentTypes.HIDE_BANUSER_DIALOG, showDialog}; }; diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index 1dfda51b9..85ad5149e 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -10,6 +10,8 @@ export const SAVE_SETTINGS_LOADING = 'SAVE_SETTINGS_LOADING'; export const SAVE_SETTINGS_SUCCESS = 'SAVE_SETTINGS_SUCCESS'; export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED'; +export const WORDLIST_UPDATED = 'WORDLIST_UPDATED'; + export const fetchSettings = () => dispatch => { dispatch({type: SETTINGS_LOADING}); coralApi('/settings') @@ -21,10 +23,16 @@ export const fetchSettings = () => dispatch => { }); }; +// for updating top-level settings export const updateSettings = settings => { return {type: SETTINGS_UPDATED, settings}; }; +// this is a nested property, so it needs a special action. +export const updateWordlist = (listName, list) => { + return {type: WORDLIST_UPDATED, listName, list}; +}; + export const saveSettingsToServer = () => (dispatch, getState) => { let settings = getState().settings.toJS().settings; if (settings.charCount) { diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 291825c71..17bd56714 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -8,6 +8,7 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations.json'; import {Icon} from 'react-mdl'; +import Highlighter from 'react-highlight-words'; import {FabButton, Button} from 'coral-ui'; const linkify = new Linkify(); @@ -31,7 +32,7 @@ export default props => { {links ? Contains Link : null}
- {props.actions.map((action, i) => getActionButton(action, i, props))} + {props.modActions.map((action, i) => getActionButton(action, i, props))}
@@ -42,7 +43,9 @@ export default props => {
- {comment.body} +
diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index 39de5121b..326d63cf1 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -1,12 +1,11 @@ - -import React from 'react'; +import React, {PropTypes} from 'react'; import styles from './CommentList.css'; import key from 'keymaster'; import Hammer from 'hammerjs'; import Comment from 'components/Comment'; // Each action has different meaning and configuration -const actions = { +const modActions = { 'reject': {status: 'rejected', icon: 'close', key: 'r'}, 'approve': {status: 'accepted', icon: 'done', key: 't'}, 'flag': {status: 'flagged', icon: 'flag', filter: 'Untouched'}, @@ -15,6 +14,23 @@ const actions = { // Renders a comment list and allow performing actions export default class CommentList extends React.Component { + static propTypes = { + isActive: PropTypes.bool, + singleView: PropTypes.bool, + commentIds: PropTypes.arrayOf(PropTypes.string).isRequired, + comments: PropTypes.object.isRequired, + users: PropTypes.object.isRequired, + onClickAction: PropTypes.func, + modActions: PropTypes.arrayOf(PropTypes.string), + loading: PropTypes.bool, + + // list of actions (flags, etc) associated with the comments + actions: PropTypes.shape({ + ids: PropTypes.arrayOf(PropTypes.string) + }), + suspectWords: PropTypes.arrayOf(PropTypes.string) + } + constructor (props) { super(props); @@ -44,22 +60,22 @@ export default class CommentList extends React.Component { // Add swipe to approve or reject bindGestures () { - const {actions} = this.props; + const {modActions} = this.props; this._hammer = new Hammer(this.base); this._hammer.get('swipe').set({direction: Hammer.DIRECTION_HORIZONTAL}); - if (actions.indexOf('reject') !== -1) { + if (modActions.indexOf('reject') !== -1) { this._hammer.on('swipeleft', () => this.props.singleView && this.actionKeyHandler('Rejected')); } - if (actions.indexOf('approve') !== -1) { + if (modActions.indexOf('approve') !== -1) { this._hammer.on('swiperight', () => this.props.singleView && this.actionKeyHandler('Approved')); } } // Add key handlers. Each action has one and added j/k for moving around bindKeyHandlers () { - this.props.actions.filter(action => actions[action].key).forEach(action => { - key(actions[action].key, 'commentList', () => this.props.isActive && this.actionKeyHandler(actions[action].status)); + this.props.modActions.filter(action => modActions[action].key).forEach(action => { + key(modActions[action].key, 'commentList', () => this.props.isActive && this.actionKeyHandler(modActions[action].status)); }); key('j', 'commentList', () => this.props.isActive && this.moveKeyHandler('down')); key('k', 'commentList', () => this.props.isActive && this.moveKeyHandler('up')); @@ -122,7 +138,7 @@ export default class CommentList extends React.Component { } render () { - const {singleView, commentIds, comments, users, hideActive, key} = this.props; + const {singleView, commentIds, comments, users, hideActive, key, suspectWords} = this.props; const {active} = this.state; return ( @@ -133,14 +149,16 @@ export default class CommentList extends React.Component { {commentIds.map((commentId, index) => { const comment = comments[commentId]; const author = users[comment.author_id]; - return ; })} diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index e4d151d30..4c76424cc 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -16,6 +16,8 @@ export default ({handleLogout}) => ( activeClassName={styles.active}>{lang.t('configure.community')} {lang.t('configure.configure')} + {lang.t('configure.streams')}
    diff --git a/client/coral-admin/src/constants/actions.js b/client/coral-admin/src/constants/actions.js new file mode 100644 index 000000000..d64c62d26 --- /dev/null +++ b/client/coral-admin/src/constants/actions.js @@ -0,0 +1 @@ +export const ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS = 'ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS'; diff --git a/client/coral-admin/src/constants/assets.js b/client/coral-admin/src/constants/assets.js new file mode 100644 index 000000000..0a2ecf73c --- /dev/null +++ b/client/coral-admin/src/constants/assets.js @@ -0,0 +1,6 @@ +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'; diff --git a/client/coral-admin/src/constants/comments.js b/client/coral-admin/src/constants/comments.js index c75911587..35a915a1b 100644 --- a/client/coral-admin/src/constants/comments.js +++ b/client/coral-admin/src/constants/comments.js @@ -1,13 +1,12 @@ export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG'; export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG'; -export const USER_BAN_SUCESS = 'USER_BAN_SUCESS'; export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS'; -export const COMMENTS_MODERATION_QUEUE_FETCH = 'COMMENTS_MODERATION_QUEUE_FETCH'; +export const COMMENTS_MODERATION_QUEUE_FETCH_REQUEST = 'COMMENTS_MODERATION_QUEUE_FETCH_REQUEST'; export const COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS = 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS'; export const COMMENT_CREATE_SUCCESS = 'COMMENT_CREATE_SUCCESS'; export const COMMENT_CREATE_FAILED = 'COMMENT_CREATE_FAILED'; export const COMMENT_STREAM_FETCH_SUCCESS = 'COMMENT_STREAM_FETCH_SUCCESS'; -export const COMMENT_UPDATE_SUCCESS = 'COMMENT_UPDATE_SUCCESS'; -export const COMMENT_UPDATE_FAILED = 'COMMENT_UPDATE_FAILED'; -export const COMMENT_STATUS_UPDATE = 'COMMENT_STATUS_UPDATE'; +export const COMMENT_STATUS_UPDATE_SUCCESS = 'COMMENT_STATUS_UPDATE_SUCCESS'; +export const COMMENT_STATUS_UPDATE_FAILURE = 'COMMENT_STATUS_UPDATE_FAILURE'; +export const COMMENT_STATUS_UPDATE_REQUEST = 'COMMENT_STATUS_UPDATE_REQUEST'; export const COMMENT_FLAG = 'COMMENT_FLAG'; diff --git a/client/coral-admin/src/containers/Community/Community.js b/client/coral-admin/src/containers/Community/Community.js index e798266f0..286a7f364 100644 --- a/client/coral-admin/src/containers/Community/Community.js +++ b/client/coral-admin/src/containers/Community/Community.js @@ -7,7 +7,7 @@ import styles from './Community.css'; import Table from './Table'; import Loading from './Loading'; import NoResults from './NoResults'; -import Pager from './Pager'; +import Pager from 'coral-ui/components/Pager'; const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 6bcf1194b..6000c1d43 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -69,110 +69,115 @@ const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => { } }; -const CommentSettings = ({fetchingSettings, updateSettings, settingsError, settings, errors}) => { +const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError, settings, errors}) => { if (fetchingSettings) { /* maybe a spinner here at some point */ return

    Loading settings...

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

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

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

    - {lang.t('configure.comment-count-text-pre')} - - {lang.t('configure.comment-count-text-post')} - { - errors.charCount && - -
    - - {lang.t('configure.comment-count-error')} -
    - } -

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

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

    -
    -
    - - - - - - - - {lang.t('configure.close-after')} -
    - -
    - - - - - -
    -
    -
    - - - {lang.t('configure.closed-comments-desc')} - - - -
    ; + return ( +
    +

    {title}

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

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

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

    + {lang.t('configure.comment-count-text-pre')} + + {lang.t('configure.comment-count-text-post')} + { + errors.charCount && + +
    + + {lang.t('configure.comment-count-error')} +
    + } +

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

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

    +
    +
    + + + + + + + + {lang.t('configure.close-after')} +
    + +
    + + + + + +
    +
    +
    + + + {lang.t('configure.closed-comments-desc')} + + + +
    +
    + ); }; export default CommentSettings; diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css index c0646c9e2..2b5c8d578 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -112,12 +112,12 @@ letter-spacing: 0.03em; } -#bannedWordlist { +#bannedWordlist, #suspectWordlist { width: 100%; padding: 10px; } -.bannedWordHeader { +.wordlistHeader { font-weight: bold; font-size:18px; margin-bottom:3px; diff --git a/client/coral-admin/src/containers/Configure/Configure.js b/client/coral-admin/src/containers/Configure/Configure.js index ed8ba9b26..9fc24ad53 100644 --- a/client/coral-admin/src/containers/Configure/Configure.js +++ b/client/coral-admin/src/containers/Configure/Configure.js @@ -1,6 +1,11 @@ import React from 'react'; import {connect} from 'react-redux'; -import {fetchSettings, updateSettings, saveSettingsToServer} from '../../actions/settings'; +import { + fetchSettings, + updateSettings, + saveSettingsToServer, + updateWordlist, +} from '../../actions/settings'; import { List, ListItem, @@ -14,6 +19,7 @@ import translations from '../../translations.json'; import EmbedLink from './EmbedLink'; import CommentSettings from './CommentSettings'; import Wordlist from './Wordlist'; +import has from 'lodash/has'; class Configure extends React.Component { constructor (props) { @@ -21,7 +27,6 @@ class Configure extends React.Component { this.state = { activeSection: 'comments', - wordlist: [], changed: false, errors: {} }; @@ -31,15 +36,6 @@ class Configure extends React.Component { this.props.dispatch(fetchSettings()); } - componentWillUpdate = (newProps) => { - if ((!this.props.settings - || !this.props.settings.wordlist) - && newProps.settings.wordlist - && newProps.settings.wordlist.length !== 0 ) { - this.setState({wordlist: newProps.settings.wordlist.join(', ')}); - } - } - saveSettings = () => { this.props.dispatch(saveSettingsToServer()); this.setState({changed: false}); @@ -49,15 +45,9 @@ class Configure extends React.Component { this.setState({activeSection}); } - onChangeWordlist = (event) => { - event.preventDefault(); - const newlist = event.target.value; - this.setState({wordlist: newlist.toLowerCase(), changed: true}); - this.props.dispatch(updateSettings({ - wordlist: newlist.toLowerCase() - .split(',') - .map((word) => word.trim()) - })); + onChangeWordlist = (listName, list) => { + this.setState({changed: true}); + this.props.dispatch(updateWordlist(listName, list)); } onSettingUpdate = (setting) => { @@ -74,46 +64,46 @@ class Configure extends React.Component { }); } - getSection = (section) => { + getSection (section) { + const pageTitle = this.getPageTitle(section); switch(section){ case 'comments': return ; case 'embed': - return ; + return ; case 'wordlist': - return ; + return has(this, 'props.settings.wordlist') + ? + :

    loading wordlists

    ; } } - getPageTitle = (section) => { + getPageTitle (section) { switch(section) { case 'comments': return lang.t('configure.comment-settings'); case 'embed': return lang.t('configure.embed-comment-stream'); - case 'wordlist': - return lang.t('configure.wordlist'); + default: + return ''; } } render () { - let pageTitle = this.getPageTitle(this.state.activeSection); const section = this.getSection(this.state.activeSection); const showSave = Object.keys(this.state.errors).reduce( (bool, error) => this.state.errors[error] ? false : bool, this.state.changed); - if (this.props.fetchingSettings) { - pageTitle += ' - Loading...'; - } - return (
    @@ -151,7 +141,6 @@ class Configure extends React.Component {
    -

    {pageTitle}

    { this.props.saveFetchingError } { this.props.fetchSettingsError } { section } diff --git a/client/coral-admin/src/containers/Configure/EmbedLink.js b/client/coral-admin/src/containers/Configure/EmbedLink.js index 6c5c3da99..374d78ea3 100644 --- a/client/coral-admin/src/containers/Configure/EmbedLink.js +++ b/client/coral-admin/src/containers/Configure/EmbedLink.js @@ -31,16 +31,21 @@ class EmbedLink extends Component { render () { const embedText = `
    `; - return - -

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

    -