diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js new file mode 100644 index 000000000..f71730663 --- /dev/null +++ b/client/coral-admin/src/actions/settings.js @@ -0,0 +1,66 @@ +export const SETTINGS_LOADING = 'SETTINGS_LOADING'; +export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED'; +export const SETTINGS_FETCH_ERROR = 'SETTINGS_FETCH_ERROR'; + +export const SETTINGS_UPDATED = 'SETTINGS_UPDATED'; + +export const SAVE_SETTINGS_LOADING = 'SAVE_SETTINGS_LOADING'; +export const SAVE_SETTINGS_SUCCESS = 'SAVE_SETTINGS_SUCCESS'; +export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED'; + +const base = '/api/v1'; + +const getInit = (method, body) => { + const headers = new Headers({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }); + + const init = {method, headers}; + if (method.toLowerCase() !== 'get') { + init.body = JSON.stringify(body); + } + + return init; +}; + +const handleResp = res => { + if (res.status === 401) { + throw new Error('Not Authorized to make this request'); + } else if (res.status > 399) { + throw new Error('Error! Status ', res.status); + } else if (res.status === 204) { + return res.text(); + } else { + return res.json(); + } +}; + +export const fetchSettings = () => dispatch => { + dispatch({type: SETTINGS_LOADING}); + fetch(`${base}/settings`, getInit('GET')) + .then(handleResp) + .then(settings => { + dispatch({type: SETTINGS_RECEIVED, settings}); + }) + .catch(error => { + dispatch({type: SETTINGS_FETCH_ERROR, error}); + }); +}; + +export const updateSettings = settings => { + return {type: SETTINGS_UPDATED, settings}; +}; + +export const saveSettingsToServer = () => (dispatch, getState) => { + const settings = getState().settings.toJS().settings; + dispatch({type: SAVE_SETTINGS_LOADING}); + fetch(`${base}/settings`, getInit('PUT', settings)) + .then(handleResp) + .then(() => { + dispatch({type: SAVE_SETTINGS_SUCCESS, settings}); + }) + .catch(error => { + dispatch({type: SAVE_SETTINGS_FAILED, error}); + }); +}; diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index 18f12cacd..98cb0e254 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -20,6 +20,7 @@ border-radius: 4px; height: 90px; margin-bottom: 10px; + cursor: pointer; } .configSettingEmbed { diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 4c6253eaf..0d4cf58d6 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -1,11 +1,13 @@ + import React from 'react'; import {connect} from 'react-redux'; +import {fetchSettings, updateSettings, saveSettingsToServer} from '../actions/settings'; import { List, ListItem, ListItemContent, ListItemAction, - Textfield, + //Textfield, Checkbox, Button, Icon @@ -17,16 +19,38 @@ import translations from '../translations'; class Configure extends React.Component { constructor (props) { super(props); + this.state = {activeSection: 'comments', copied: false}; + this.copyToClipBoard = this.copyToClipBoard.bind(this); + this.updateModeration = this.updateModeration.bind(this); + this.saveSettings = this.saveSettings.bind(this); + } + + componentWillMount () { + this.props.dispatch(fetchSettings()); + } + + updateModeration () { + const moderation = this.props.settings.moderation === 'pre' ? 'post' : 'pre'; + this.props.dispatch(updateSettings({moderation})); + } + + saveSettings () { + this.props.dispatch(saveSettingsToServer()); } getCommentSettings () { return - + + + Enable pre-moderation + {/* Include Comment Stream Description for Readers @@ -39,6 +63,7 @@ class Configure extends React.Component { error='Input is not a number!' label='Maximum Characters' /> + */} ; } @@ -75,10 +100,14 @@ class Configure extends React.Component { } render () { - const pageTitle = this.state.activeSection === 'comments' + let pageTitle = this.state.activeSection === 'comments' ? 'Comment Settings' : 'Embed Comment Stream'; + if (this.props.fetchingSettings) { + pageTitle += ' - Loading...'; + } + return (
@@ -94,12 +123,14 @@ class Configure extends React.Component { icon='code'>Embed Comment Stream -

{pageTitle}

+ { this.props.saveFetchingError } + { this.props.fetchSettingsError } { this.state.activeSection === 'comments' ? this.getCommentSettings() @@ -111,6 +142,7 @@ class Configure extends React.Component { } } -export default connect(x => x)(Configure); +const mapStateToProps = state => state.settings.toJS(); +export default connect(mapStateToProps)(Configure); const lang = new I18n(translations); diff --git a/client/coral-admin/src/reducers/comments.js b/client/coral-admin/src/reducers/comments.js index 2842e2289..01f536f71 100644 --- a/client/coral-admin/src/reducers/comments.js +++ b/client/coral-admin/src/reducers/comments.js @@ -39,7 +39,7 @@ const updateStatus = (state, action) => { // Flag a comment const flag = (state, action) => { const byId = state.get('byId'); - const data = byId.get(action.id).get('data').set('flagged', true); + const data = byId.get(action.id).set('flagged', true); const comment = byId.get(action.id).set('data', data); return state.set('byId', byId.set(action.id, comment)); }; diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 3c0ddc924..63176ca3b 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,8 +1,10 @@ import {combineReducers} from 'redux'; import comments from 'reducers/comments'; +import settings from 'reducers/settings'; // Combine all reducers into a main one export default combineReducers({ + settings, comments }); diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js new file mode 100644 index 000000000..20cd024f5 --- /dev/null +++ b/client/coral-admin/src/reducers/settings.js @@ -0,0 +1,43 @@ +import {Map} from 'immutable'; +import * as types from '../actions/settings'; + +const initialState = Map({ + settings: Map(), + saveSettingsError: null, + fetchSettingsError: null, + fetchingSettings: false +}); + +// Handle the comment actions +export default (state = initialState, action) => { + switch (action.type) { + case types.SETTINGS_LOADING: return state.set('fetchingSettings', true).set('fetchSettingsError', null); + case types.SETTINGS_RECEIVED: return updateSettings(state, action); + case types.SETTINGS_FETCH_ERROR: return settingsFetchFailed(state, action); + case types.SETTINGS_UPDATED: return updateSettings(state, action); + case types.SAVE_SETTINGS_LOADING: return state.set('fetchingSettings', true).set('saveSettingsError', null); + case types.SAVE_SETTINGS_SUCCESS: return saveComplete(state, action); + case types.SAVE_SETTINGS_FAILED: return settingsSaveFailed(state, action); + default: return state; + } +}; + +const updateSettings = (state, action) => { + const s = state.set('fetchingSettings', false).set('fetchSettingsError', null); + const settings = s.get('settings').merge(action.settings); + return s.set('settings', settings); +}; + +const saveComplete = (state, action) => { + const s = state.set('fetchingSettings', false).set('saveSettingsError', null); + const settings = s.get('settings').merge(action.settings); + return s.set('settings', settings); +}; + +const settingsFetchFailed = (state, action) => { + return state.set('fetchingSettings', false).set('fetchSettingsError', action.error); +}; + +const settingsSaveFailed = (state, action) => { + return state.set('fetchingSettings', false).set('fetchSettingsError', action.error); +}; diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index ce2e2692a..8122bc709 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -29,15 +29,20 @@ export default store => next => action => { // Get comments to fill each of the three lists on the mod queue const fetchModerationQueueComments = store => -fetch('/api/v1/queue') -.then(res => res.json()) + +Promise.all([fetch('/api/v1/comments/status/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')]) +.then(res => Promise.all(res.map(r => r.json()))) +.then(res => { + res[2] = res[2].map(comment => { comment.flagged = true; return comment; }); + return res.reduce((prev, curr) => prev.concat(curr), []); +}) .then(res => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS', comments: res})) .catch(error => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_FAILED', error})); // Update a comment. Now to update a comment we need to send back the whole object const updateComment = (store, comment) => -fetch(`/api/v1/comments/${comment._id}/status`, { +fetch('/api/v1/comments/${comment._id}/status', { method: 'POST', body: JSON.stringify({status: comment.status}) }) diff --git a/package.json b/package.json index a3656ddba..5563c1f36 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "body-parser": "^1.15.2", "debug": "^2.2.0", "ejs": "^2.5.2", + "eslint-config-postcss": "^2.0.2", "express": "^4.14.0", "mongoose": "^4.6.5", "morgan": "^1.7.0", @@ -58,8 +59,10 @@ "babel-core": "^6.18.2", "babel-loader": "^6.2.7", "babel-plugin-transform-async-to-generator": "^6.16.0", + "babel-plugin-transform-class-properties": "^6.18.0", "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-object-assign": "^6.8.0", + "babel-plugin-transform-object-rest-spread": "^6.16.0", "babel-plugin-transform-react-jsx": "^6.8.0", "babel-polyfill": "^6.16.0", "babel-preset-es2015": "^6.18.0", diff --git a/tests/models/comment.js b/tests/models/comment.js index 0efd0c960..f81cb7609 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -113,7 +113,6 @@ describe('Comment: models', () => { }); }); }); - describe('#moderationQueue()', () => { it('should find an array of new comments to moderate when pre-moderation', () => { return Comment.moderationQueue('pre').then((result) => {