diff --git a/client/coral-framework/components/CloseCommentsInfo.js b/client/coral-configure/components/CloseCommentsInfo.js similarity index 100% rename from client/coral-framework/components/CloseCommentsInfo.js rename to client/coral-configure/components/CloseCommentsInfo.js diff --git a/client/coral-configure/components/ConfigureCommentStream.css b/client/coral-configure/components/ConfigureCommentStream.css new file mode 100644 index 000000000..8526edc09 --- /dev/null +++ b/client/coral-configure/components/ConfigureCommentStream.css @@ -0,0 +1,37 @@ +.container { + position: relative; +} + +.apply { + position: absolute; + top: 38%; + transform: translateX(-50%); + right: 0; +} + +ul { + list-style: none; + padding: 0; +} + +ul ul { + padding-left: 20px +} + +.checkbox { + vertical-align: top; + margin: 12px 12px 12px 0; +} + +h4 { + font-size: 14px; + margin-bottom: 5px; +} + +p { + max-width: 380px; +} + +.wrapper { + margin-bottom: 20px; +} diff --git a/client/coral-configure/components/ConfigureCommentStream.js b/client/coral-configure/components/ConfigureCommentStream.js new file mode 100644 index 000000000..507c764d7 --- /dev/null +++ b/client/coral-configure/components/ConfigureCommentStream.js @@ -0,0 +1,53 @@ +import React from 'react'; +import {Button, Checkbox} from 'coral-ui'; +import styles from './ConfigureCommentStream.css'; + +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../translations.json'; +const lang = new I18n(translations); + +export default ({handleChange, handleApply, changed, ...props}) => ( +
+
+

{lang.t('configureCommentStream.title')}

+

{lang.t('configureCommentStream.description')}

+ +
+ +
+); diff --git a/client/coral-configure/containers/ConfigureStreamContainer.js b/client/coral-configure/containers/ConfigureStreamContainer.js new file mode 100644 index 000000000..d4b71f4e5 --- /dev/null +++ b/client/coral-configure/containers/ConfigureStreamContainer.js @@ -0,0 +1,83 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; + +import {updateOpenStatus, updateConfiguration} from '../../coral-framework/actions/config'; + +import CloseCommentsInfo from '../components/CloseCommentsInfo'; +import ConfigureCommentStream from '../components/ConfigureCommentStream'; + +class ConfigureStreamContainer extends Component { + constructor (props) { + super(props); + + this.state = { + premod: props.config.moderation === 'pre', + premodLinks: false + }; + + this.toggleStatus = this.toggleStatus.bind(this); + this.handleChange = this.handleChange.bind(this); + this.handleApply = this.handleApply.bind(this); + } + + handleApply () { + const {premod, changed} = this.state; + const newConfig = { + moderation: premod ? 'pre' : 'post' + }; + if (changed) { + this.props.updateConfiguration(newConfig); + setTimeout(() => { + this.setState({ + changed: false + }); + }, 300); + } + } + + handleChange (e) { + const {name, checked} = e.target; + this.setState({ + [name]: checked, + changed: true + }); + } + + toggleStatus () { + this.props.updateStatus(this.props.config.status === 'open' ? 'closed' : 'open'); + } + + render () { + const {status} = this.props; + return ( +
+ +
+

{status === 'open' ? 'Close' : 'Open'} Comment Stream

+ +
+ ); + } +} + +const mapStateToProps = (state) => ({ + config: state.config.toJS() +}); + +const mapDispatchToProps = dispatch => ({ + updateStatus: status => dispatch(updateOpenStatus(status)), + updateConfiguration: newConfig => dispatch(updateConfiguration(newConfig)) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(ConfigureStreamContainer); diff --git a/client/coral-configure/translations.json b/client/coral-configure/translations.json new file mode 100644 index 000000000..e597c9c9a --- /dev/null +++ b/client/coral-configure/translations.json @@ -0,0 +1,24 @@ +{ + "en": { + "configureCommentStream": { + "apply": "Apply", + "title": "Configure Comment Stream", + "description": "As an admin you may customize the settings for the comment stream for this article", + "enablePremod": "Enable Premoderation", + "enablePremodDescription": "Moderators must approve any comment before its published.", + "enablePremodLinks": "Pre-Moderate Comments Containing Links", + "enablePremodLinksDescription": "Moderators must approve any comment containing a link before its published." + } + }, + "es": { + "configureCommentStream": { + "apply": "Aplicar", + "title": "Configurar los comentarios", + "description": "Como Administrador puedes modificar las opciones de los comentarios en este artículo", + "enablePremod": "Activar Pre Moderación", + "enablePremodDescription": "Los Moderadores deben aprobar cualquier comentario antes de su publicación", + "enablePremodLinks": "Pre-Moderar Commentarios que contienen Links", + "enablePremodLinksDescription": "Los Moderadores deben probar cualquier comentario que contengan links antes de su publicación." + } + } +} diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index fb8a1962f..bb59ffa8e 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -6,8 +6,7 @@ import { itemActions, Notification, notificationActions, - authActions, - configActions + authActions } from '../../coral-framework'; import CommentBox from '../../coral-plugin-commentbox/CommentBox'; @@ -27,12 +26,12 @@ import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui'; import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; import RestrictedContent from '../../coral-framework/components/RestrictedContent'; import SuspendedAccount from '../../coral-framework/components/SuspendedAccount'; -import CloseCommentsInfo from '../../coral-framework/components/CloseCommentsInfo'; + +import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer'; const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; const {logout, showSignInDialog} = authActions; -const {updateOpenStatus} = configActions; class CommentStream extends Component { @@ -44,7 +43,6 @@ class CommentStream extends Component { }; this.changeTab = this.changeTab.bind(this); - this.toggleStatus = this.toggleStatus.bind(this); } changeTab (tab) { @@ -53,10 +51,6 @@ class CommentStream extends Component { }); } - toggleStatus () { - this.props.updateStatus(this.props.config.status === 'open' ? 'closed' : 'open'); - } - static propTypes = { items: PropTypes.object.isRequired, addItem: PropTypes.func.isRequired, @@ -272,9 +266,11 @@ class CommentStream extends Component { /> -

{status === 'open' ? 'Close' : 'Open'} Comment Stream

- +
({ deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), handleSignInDialog: () => dispatch(authActions.showSignInDialog()), - logout: () => dispatch(logout()), - updateStatus: status => dispatch(updateOpenStatus(status)) + logout: () => dispatch(logout()) }); export default connect(mapStateToProps, mapDispatchToProps)(CommentStream); diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index e004fa1eb..f85e609eb 100644 --- a/client/coral-framework/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -1,19 +1,33 @@ import coralApi from '../helpers/response'; -/* Config Actions */ +import * as actions from '../constants/config'; +import {addNotification} from '../actions/notification'; -/** - * Action name constants - */ - -export const UPDATE_SETTINGS = 'UPDATE_SETTINGS'; -export const OPEN_COMMENTS = 'OPEN_COMMENTS'; -export const CLOSE_COMMENTS = 'CLOSE_COMMENTS'; -export const ADD_ITEM = 'ADD_ITEM'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from './../translations'; +const lang = new I18n(translations); export const updateOpenStatus = status => (dispatch, getState) => { const assetId = getState().items.get('assets') .keySeq() .toArray()[0]; return coralApi(`/asset/${assetId}/status?status=${status}`, {method: 'PUT'}) - .then(() => dispatch({type: status === 'open' ? OPEN_COMMENTS : CLOSE_COMMENTS})); + .then(() => dispatch({type: status === 'open' ? actions.OPEN_COMMENTS : actions.CLOSE_COMMENTS})); +}; + +const updateConfigRequest = () => ({type: actions.UPDATE_CONFIG_REQUEST}); +const updateConfigSuccess = config => ({type: actions.UPDATE_CONFIG_SUCCESS, config}); +const updateConfigFailure = () => ({type: actions.UPDATE_CONFIG_FAILURE}); + +export const updateConfiguration = newConfig => (dispatch, getState) => { + const assetId = getState().items.get('assets') + .keySeq() + .toArray()[0]; + + dispatch(updateConfigRequest()); + coralApi(`/asset/${assetId}/settings`, {method: 'PUT', body: newConfig}) + .then(() => { + dispatch(addNotification('success', lang.t('successUpdateSettings'))); + dispatch(updateConfigSuccess(newConfig)); + }) + .catch(error => dispatch(updateConfigFailure(error))); }; diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index 2029714c7..cdaa7c1a6 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -1,14 +1,9 @@ import coralApi from '../helpers/response'; import {fromJS} from 'immutable'; -/* Item Actions */ - -/** - * Action name constants - */ +import {UPDATE_CONFIG} from '../constants/config'; export const ADD_ITEM = 'ADD_ITEM'; export const UPDATE_ITEM = 'UPDATE_ITEM'; -export const UPDATE_SETTINGS = 'UPDATE_SETTINGS'; export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY'; /** @@ -106,7 +101,7 @@ export function getStream (assetUrl) { dispatch(addItem(action, 'actions')); }); } else if (type === 'settings') { - dispatch({type: UPDATE_SETTINGS, config: fromJS(json[type])}); + dispatch({type: UPDATE_CONFIG, config: fromJS(json[type])}); } else { json[type].forEach(item => { dispatch(addItem(item, type)); diff --git a/client/coral-framework/constants/config.js b/client/coral-framework/constants/config.js new file mode 100644 index 000000000..5dca44ba9 --- /dev/null +++ b/client/coral-framework/constants/config.js @@ -0,0 +1,9 @@ +export const UPDATE_CONFIG_REQUEST = 'UPDATE_CONFIG_REQUEST'; +export const UPDATE_CONFIG_SUCCESS = 'UPDATE_CONFIG_SUCCESS'; +export const UPDATE_CONFIG_FAILURE = 'UPDATE_CONFIG_FAILURE'; + +export const UPDATE_CONFIG = 'UPDATE_CONFIG'; + +export const OPEN_COMMENTS = 'OPEN_COMMENTS'; +export const CLOSE_COMMENTS = 'CLOSE_COMMENTS'; +export const ADD_ITEM = 'ADD_ITEM'; diff --git a/client/coral-framework/reducers/config.js b/client/coral-framework/reducers/config.js index 7fbccaa49..8266fbfa1 100644 --- a/client/coral-framework/reducers/config.js +++ b/client/coral-framework/reducers/config.js @@ -1,31 +1,28 @@ -/* @flow */ - import {Map} from 'immutable'; -import * as actions from '../actions/config'; +import * as actions from '../constants/config'; const initialState = Map({ features: Map({}), status: 'open', - closedMessage: '' + moderation: null }); export default (state = initialState, action) => { switch(action.type) { - // Override config if worked - case actions.UPDATE_SETTINGS: - return action.config; - + case actions.UPDATE_CONFIG: + return state + .merge(Map(action.config)); + case actions.UPDATE_CONFIG_SUCCESS: + return state + .merge(Map(action.config)); case actions.OPEN_COMMENTS: - return state.set('status', 'open'); - + return state + .set('status', 'open'); case actions.CLOSE_COMMENTS: - return state.set('status', 'closed'); - + return state + .set('status', 'closed'); case actions.ADD_ITEM: - return action.item_type === 'assets' ? - state.set('status', action.item.status) - : state; - + return action.item_type === 'assets' ? state.set('status', action.item.status) : state; default: return state; } diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index ab65ba565..06a39944a 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -1,5 +1,6 @@ { "en": { + "successUpdateSettings": "The changes you have made have been applied to the comment stream on this article", "successBioUpdate": "Your Bio has been updated", "contentNotAvailable": "This content is not available", "suspendedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", @@ -13,6 +14,7 @@ } }, "es": { + "successUpdateSettings": "La configuración de este articulo fue actualizada", "successBioUpdate": "Tu bio fue actualizada", "contentNotAvailable": "El contenido no se encuentra disponible", "suspendedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information", diff --git a/client/coral-settings/index.js b/client/coral-settings/index.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/coral-ui/components/Button.css b/client/coral-ui/components/Button.css index 3a3a4221e..e36f55886 100644 --- a/client/coral-ui/components/Button.css +++ b/client/coral-ui/components/Button.css @@ -77,6 +77,16 @@ background: #696969; } +.type--green { + color: white; + background: #00897B; +} + +.type--green:hover { + color: white; + background: #00a291; +} + .full { width: 100%; margin: 0; diff --git a/client/coral-ui/components/Checkbox.css b/client/coral-ui/components/Checkbox.css new file mode 100644 index 000000000..d20886689 --- /dev/null +++ b/client/coral-ui/components/Checkbox.css @@ -0,0 +1,70 @@ +.label { + position: relative; + display: inline-block; +} + +.label input { + visibility: hidden; + position: absolute; + left: 7px; + bottom: 7px; + margin: 0; + padding: 0; + outline: none; + cursor: pointer; + opacity: 0; +} + +.checkbox { + cursor: pointer; +} + +.label input[type="checkbox"]:checked + .checkbox:before { + content: "\e834"; +} + +.label input[type="checkbox"] + .checkbox:before { + content: "\e835"; + color: #717171; +} + +.label.type--green input[type="checkbox"] + .checkbox:before { + color: #00a291; +} + +.label input[type="checkbox"] + .checkbox:before { + position: absolute; + left: 4px; + top: 0px; + width: 18px; + height: 18px; + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + vertical-align: -6px; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + -webkit-font-feature-settings: 'liga'; + font-feature-settings: 'liga'; + -webkit-transition: all .2s ease; + transition: all .2s ease; + z-index: 1; +} + +.checkboxInfo { + display: inline-block; + max-width: 360px; + margin-left: 50px; +} + +.checkboxInfo h4 { + margin: 0 0 5px; +} diff --git a/client/coral-ui/components/Checkbox.js b/client/coral-ui/components/Checkbox.js new file mode 100644 index 000000000..94626295f --- /dev/null +++ b/client/coral-ui/components/Checkbox.js @@ -0,0 +1,16 @@ +import React from 'react'; +import styles from './Checkbox.css'; + +export default ({name, cStyle = 'base', onChange, label, className, info, checked = 'false'}) => ( + +); diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index ffdb3bbd2..afcce7354 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -7,3 +7,4 @@ export {default as TabContent} from './components/TabContent'; export {default as Button} from './components/Button'; export {default as Spinner} from './components/Spinner'; export {default as Tooltip} from './components/Tooltip'; +export {default as Checkbox} from './components/Checkbox'; diff --git a/models/asset.js b/models/asset.js index 77b8ebff3..3c68b7c13 100644 --- a/models/asset.js +++ b/models/asset.js @@ -132,10 +132,12 @@ AssetSchema.statics.findOrCreateByUrl = (url) => Asset.findOneAndUpdate({url}, { * @param {[type]} settings [description] * @return {[type]} [description] */ -AssetSchema.statics.overrideSettings = (id, settings) => Asset.update({id}, { +AssetSchema.statics.overrideSettings = (id, settings) => Asset.findOneAndUpdate({id}, { $set: { settings } +}, { + new: true }); /** diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 05926bc15..2ddc3ea23 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -82,18 +82,11 @@ router.post('/:asset_id/scrape', (req, res, next) => { }); router.put('/:asset_id/settings', (req, res, next) => { - // Override the settings for the asset. Asset .overrideSettings(req.params.asset_id, req.body) - .then(() => { - - res.status(204).end(); - }) - .catch((err) => { - next(err); - }); - + .then(() => res.status(204).end()) + .catch((err) => next(err)); }); router.put('/:asset_id/status', (req, res, next) => {