Merge branch 'master' of github.com:coralproject/talk into flag-username

This commit is contained in:
David Jay
2016-12-09 15:51:13 -05:00
29 changed files with 638 additions and 192 deletions
@@ -0,0 +1,79 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import styles from './Configure.css';
import {
List,
ListItem,
ListItemContent,
ListItemAction,
Textfield,
Checkbox
} from 'react-mdl';
const updateModeration = (updateSettings, mod) => () => {
const moderation = mod === 'pre' ? 'post' : 'pre';
updateSettings({moderation});
};
const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
const infoBoxEnable = !infoBox;
updateSettings({infoBoxEnable});
};
const updateInfoBoxContent = (updateSettings) => (event) => {
const infoBoxContent = event.target.value;
updateSettings({infoBoxContent});
};
const updateClosedMessage = (updateSettings) => (event) => {
const closedMessage = event.target.value;
updateSettings({closedMessage});
};
const CommentSettings = (props) => <List>
<ListItem className={styles.configSetting}>
<ListItemAction>
<Checkbox
onClick={updateModeration(props.updateSettings, props.settings.moderation)}
checked={props.settings.moderation === 'pre'} />
</ListItemAction>
{lang.t('configure.enable-pre-moderation')}
</ListItem>
<ListItem threeLine className={styles.configSettingInfoBox}>
<ListItemAction>
<Checkbox
onClick={updateInfoBoxEnable(props.updateSettings, props.settings.infoBoxEnable)}
checked={props.settings.infoBoxEnable} />
</ListItemAction>
<ListItemContent>
{lang.t('configure.include-comment-stream')}
<p>
{lang.t('configure.include-comment-stream-desc')}
</p>
</ListItemContent>
</ListItem>
<ListItem className={`${styles.configSettingInfoBox} ${props.settings.infoBoxEnable ? null : styles.hidden}`} >
<ListItemContent>
<Textfield
onChange={updateInfoBoxContent(props.updateSettings)}
value={props.settings.infoBoxContent}
label={lang.t('configure.include-text')}
rows={3}/>
</ListItemContent>
</ListItem>
<ListItem className={styles.configSettingInfoBox}>
<ListItemContent>
{lang.t('configure.closed-comments-desc')}
<Textfield
onChange={updateClosedMessage(props.updateSettings)}
value={props.settings.closedMessage}
label={lang.t('configure.closed-comments-label')}
rows={3}/>
</ListItemContent>
</ListItem>
</List>;
export default CommentSettings;
const lang = new I18n(translations);
@@ -45,6 +45,10 @@
display: block;
}
.changedSave {
background-color:#4caf50;
}
.copiedText {
color: #008000;
float: right;
@@ -69,6 +73,19 @@
letter-spacing: 0.03em;
}
#bannedWordlist {
width: 100%;
padding: 10px;
}
.bannedWordHeader {
font-weight: bold;
font-size:18px;
margin-bottom:3px;
}
.hidden {
display: none;
}
@@ -5,142 +5,94 @@ import {
List,
ListItem,
ListItemContent,
ListItemAction,
Textfield,
Checkbox,
Button,
Icon
} from 'react-mdl';
import styles from './Configure.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import EmbedLink from './EmbedLink';
import CommentSettings from './CommentSettings';
import Wordlist from './Wordlist';
class Configure extends React.Component {
constructor (props) {
super(props);
this.state = {activeSection: 'comments', copied: false};
this.copyToClipBoard = this.copyToClipBoard.bind(this);
// Update settings
this.updateModeration = this.updateModeration.bind(this);
// InfoBox has two settings. Enable or not and the content of it if it is enable.
this.updateInfoBoxEnable = this.updateInfoBoxEnable.bind(this);
this.updateInfoBoxContent = this.updateInfoBoxContent.bind(this);
this.updateClosedMessage = this.updateClosedMessage.bind(this);
this.saveSettings = this.saveSettings.bind(this);
this.state = {
activeSection: 'comments',
wordlist: [],
changed: false
};
}
componentWillMount () {
componentWillMount = () => {
this.props.dispatch(fetchSettings());
}
updateModeration () {
const moderation = this.props.settings.moderation === 'pre' ? 'post' : 'pre';
this.props.dispatch(updateSettings({moderation}));
}
updateInfoBoxEnable () {
const infoBoxEnable = !this.props.settings.infoBoxEnable;
this.props.dispatch(updateSettings({infoBoxEnable}));
}
updateInfoBoxContent (event) {
const infoBoxContent = event.target.value;
this.props.dispatch(updateSettings({infoBoxContent}));
}
updateClosedMessage (event) {
const closedMessage = event.target.value;
this.props.dispatch(updateSettings({closedMessage}));
}
saveSettings () {
this.props.dispatch(saveSettingsToServer());
}
getCommentSettings () {
return <List>
<ListItem className={styles.configSetting}>
<ListItemAction>
<Checkbox
onClick={this.updateModeration}
checked={this.props.settings.moderation === 'pre'} />
</ListItemAction>
{lang.t('configure.enable-pre-moderation')}
</ListItem>
<ListItem threeLine className={styles.configSettingInfoBox}>
<ListItemAction>
<Checkbox
onClick={this.updateInfoBoxEnable}
checked={this.props.settings.infoBoxEnable} />
</ListItemAction>
<ListItemContent>
{lang.t('configure.include-comment-stream')}
<p>
{lang.t('configure.include-comment-stream-desc')}
</p>
</ListItemContent>
</ListItem>
<ListItem className={styles.configSettingInfoBox}>
<ListItemContent>
{lang.t('configure.closed-comments-desc')}
<Textfield
onChange={this.updateClosedMessage}
value={this.props.settings.closedMessage}
label={lang.t('configure.closed-comments-label')}
rows={3}/>
</ListItemContent>
</ListItem>
<ListItem className={`${styles.configSettingInfoBox} ${this.props.settings.infoBoxEnable ? null : styles.hidden}`} >
<ListItemContent>
<Textfield
onChange={this.updateInfoBoxContent}
value={this.props.settings.infoBoxContent}
label={lang.t('configure.include-text')}
rows={3}/>
</ListItemContent>
</ListItem>
</List>;
}
copyToClipBoard () {
const copyTextarea = document.querySelector(`.${ styles.embedInput}`);
copyTextarea.select();
try {
document.execCommand('copy');
this.setState({copied: true});
} catch (err) {
console.error('Unable to copy', err);
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(', ')});
}
}
getEmbed () {
const embedText = `<div id='coralStreamEmbed'></div><script type='text/javascript' src='${window.location.protocol}//pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/embed/stream', {title: 'Comments'});</script>`;
return <List>
<ListItem className={styles.configSettingEmbed}>
<p>{lang.t('configure.copy-and-paste')}</p>
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
<Button raised colored className={styles.copyButton} onClick={this.copyToClipBoard}>
{lang.t('embedlink.copy')}
</Button>
<div className={styles.copiedText}>{this.state.copied && 'Copied!'}</div>
</ListItem>
</List>;
saveSettings = () => {
this.props.dispatch(saveSettingsToServer());
this.setState({changed: false});
}
changeSection (activeSection) {
changeSection = (activeSection) => () => {
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())
}));
}
onSettingUpdate = (setting) => {
this.setState({changed: true});
this.props.dispatch(updateSettings(setting));
}
getSection = (section) => {
switch(section){
case 'comments':
return <CommentSettings
settings={this.props.settings}
updateSettings={this.onSettingUpdate}/>;
case 'embed':
return <EmbedLink/>;
case 'wordlist':
return <Wordlist
wordlist={this.state.wordlist}
onChangeWordlist={this.onChangeWordlist}/>;
}
}
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');
}
}
render () {
let pageTitle = this.state.activeSection === 'comments'
? lang.t('configure.comment-settings')
: lang.t('configure.embed-comment-stream');
let pageTitle = this.getPageTitle(this.state.activeSection);
const section = this.getSection(this.state.activeSection);
if (this.props.fetchingSettings) {
pageTitle += ' - Loading...';
@@ -152,28 +104,41 @@ class Configure extends React.Component {
<List>
<ListItem className={styles.settingOption}>
<ListItemContent
onClick={this.changeSection.bind(this, 'comments')}
onClick={this.changeSection('comments')}
icon='settings'>{lang.t('configure.comment-settings')}</ListItemContent>
</ListItem>
<ListItem className={styles.settingOption}>
<ListItemContent
onClick={this.changeSection.bind(this, 'embed')}
onClick={this.changeSection('embed')}
icon='code'>{lang.t('configure.embed-comment-stream')}</ListItemContent>
</ListItem>
<ListItem className={styles.settingOption}>
<ListItemContent
onClick={this.changeSection('wordlist')}
icon='settings'>{lang.t('configure.wordlist')}</ListItemContent>
</ListItem>
</List>
<Button raised colored onClick={this.saveSettings}>
<Icon name='save' /> {lang.t('configure.save-changes')}
{
this.state.changed ?
<Button
raised
onClick={this.saveSettings}
className={styles.changedSave}>
<Icon name='check' /> {lang.t('configure.save-changes')}
</Button>
: <Button
raised
disabled>
{lang.t('configure.save-changes')}
</Button>
}
</div>
<div className={styles.mainContent}>
<h1>{pageTitle}</h1>
{ this.props.saveFetchingError }
{ this.props.fetchSettingsError }
{
this.state.activeSection === 'comments'
? this.getCommentSettings()
: this.getEmbed()
}
{ section }
</div>
</div>
);
@@ -0,0 +1,49 @@
import React, {Component} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import styles from './Configure.css';
import {
List,
ListItem,
Button
} from 'react-mdl';
class EmbedLink extends Component {
constructor (props) {
super(props);
this.state = {copied: false};
}
copyToClipBoard = () => {
const copyTextarea = document.querySelector(`.${styles.embedInput}`);
copyTextarea.select();
try {
document.execCommand('copy');
this.setState({copied: true});
} catch (err) {
console.error('Unable to copy', err);
}
}
render () {
const embedText = `<div id='coralStreamEmbed'></div><script type='text/javascript' src='${window.location.protocol}//pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/embed/stream', {title: 'Comments'});</script>`;
return <List>
<ListItem className={styles.configSettingEmbed}>
<p>{lang.t('configure.copy-and-paste')}</p>
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
<Button raised colored className={styles.copyButton} onClick={this.copyToClipBoard}>
{lang.t('embedlink.copy')}
</Button>
<div className={styles.copiedText}>{this.state.copied && 'Copied!'}</div>
</ListItem>
</List>;
}
}
export default EmbedLink;
const lang = new I18n(translations);
@@ -0,0 +1,22 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import styles from './Configure.css';
import {
Card
} from 'react-mdl';
const Wordlist = ({wordlist, onChangeWordlist}) => <Card id={styles.bannedWordlist} shadow={2}>
<p className={styles.bannedWordHeader}>{lang.t('configure.banned-word-header')}</p>
<p className={styles.bannedWordText}>{lang.t('configure.banned-word-text')}</p>
<textarea
rows={5}
type='text'
className={styles.bannedWordInput}
onChange={onChangeWordlist}
value={wordlist}/>
</Card>;
export default Wordlist;
const lang = new I18n(translations);
+6
View File
@@ -37,6 +37,9 @@
"include-text": "Include your text here.",
"comment-settings": "Comment Settings",
"embed-comment-stream": "Embed Comment Stream",
"banned-word-header": "Write the bannned words list",
"banned-word-text": "Comments which contain these words or phrases, not seperated by commas and not case sensitive, will be automatically removed from the comment stream.",
"wordlist": "Banned words list",
"save-changes": "Save Changes",
"copy-and-paste": "Copy and paste code below into your CMS to embed your comment box in your articles",
"moderate": "Moderate",
@@ -73,6 +76,9 @@
"include-text": "Incluir tu texto aqui.",
"comment-settings": "Configuración de Comentarios",
"embed-comment-stream": "Colocar Hilo de Comentarios",
"wordlist": "Lista de palabras no permitidas",
"banned-word-header": "Escribir las palabras no permitidas",
"banned-word-text": "Comentarios que contengan estas palabras o frases, no separadas por comas y en mayusculas o minusuculas, serán automaticamente separadas de los comentarios publicados.",
"save-changes": "Guardar Cambios",
"copy-and-paste": "Copiar y pegar el código de más abajo en tu CMS para colocar la caja de comentarios en tus articulos",
"moderate": "Moderar",
@@ -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;
}
@@ -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}) => (
<div className={styles.wrapper}>
<div className={styles.container}>
<h3>{lang.t('configureCommentStream.title')}</h3>
<p>{lang.t('configureCommentStream.description')}</p>
<Button
className={styles.apply}
cStyle={changed ? 'green' : 'darkGrey'}
onClick={handleApply}
>
{lang.t('configureCommentStream.apply')}
</Button>
</div>
<ul>
<li>
<Checkbox
className={styles.checkbox}
cStyle={changed ? 'green' : 'darkGrey'}
name="premod"
onChange={handleChange}
checked={props.premod}
info={{
title: lang.t('configureCommentStream.enablePremod'),
description: lang.t('configureCommentStream.enablePremodDescription')
}}
/>
<ul>
<li>
<Checkbox
className={styles.checkbox}
cStyle={changed ? 'green' : 'darkGrey'}
name="premodLinks"
onChange={handleChange}
checked={props.premodLinks}
info={{
title: lang.t('configureCommentStream.enablePremodLinks'),
description: lang.t('configureCommentStream.enablePremodDescription')
}}
/>
</li>
</ul>
</li>
</ul>
</div>
);
@@ -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 (
<div>
<ConfigureCommentStream
handleChange={this.handleChange}
handleApply={this.handleApply}
changed={this.state.changed}
{...this.state}
/>
<hr />
<h3>{status === 'open' ? 'Close' : 'Open'} Comment Stream</h3>
<CloseCommentsInfo
onClick={this.toggleStatus}
status={status}
/>
</div>
);
}
}
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);
+24
View File
@@ -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."
}
}
}
+8 -13
View File
@@ -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,
@@ -259,9 +253,11 @@ class CommentStream extends Component {
/>
</TabContent>
<TabContent show={activeTab === 2}>
<h3>{status === 'open' ? 'Close' : 'Open'} Comment Stream</h3>
<RestrictedContent restricted={!loggedIn}>
<CloseCommentsInfo onClick={this.toggleStatus} status={status} />
<ConfigureStreamContainer
status={status}
onClick={this.toggleStatus}
/>
</RestrictedContent>
</TabContent>
</RestrictedContent>
@@ -298,8 +294,7 @@ const mapDispatchToProps = (dispatch) => ({
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);
+24 -10
View File
@@ -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)));
};
+3 -8
View File
@@ -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));
@@ -197,7 +192,7 @@ export function postItem (item, type, id) {
return coralApi(`/${type}`, {method: 'POST', body: item})
.then((json) => {
dispatch(addItem({...item, id:json.id}, type));
return json.id;
return json;
});
};
}
@@ -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';
+13 -16
View File
@@ -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 state.merge(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;
}
+2
View File
@@ -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",
+15 -9
View File
@@ -39,16 +39,22 @@ class CommentBox extends Component {
related = 'comments';
parent_type = 'assets';
}
updateItem(child_id || parent_id, 'showReply', false, 'comments');
if (child_id || parent_id) {
updateItem(child_id || parent_id, 'showReply', false, 'comments');
}
postItem(comment, 'comments')
.then((comment_id) => {
if (premod === 'pre') {
addNotification('success', lang.t('comment-post-notif-premod'));
} else {
appendItemArray(parent_id || id, related, comment_id, !parent_id, parent_type);
addNotification('success', 'Your comment has been posted.');
}
})
.then((postedComment) => {
const commentId = postedComment.id;
const status = postedComment.status;
if (status[0] && status[0].type === 'rejected') {
addNotification('error', lang.t('comment-post-banned-word'));
} else if (premod === 'pre') {
addNotification('success', lang.t('comment-post-notif-premod'));
} else {
appendItemArray(parent_id || id, related, commentId, !parent_id, parent_type);
addNotification('success', 'Your comment has been posted.');
}
})
.catch((err) => console.error(err));
this.setState({body: ''});
}
@@ -5,14 +5,16 @@
"comment": "Comment",
"name": "Name",
"comment-post-notif": "Your comment has been posted.",
"comment-post-notif-premod": "Thank you for posting. Our moderation team will review your comment shortly."
"comment-post-notif-premod": "Thank you for posting. Our moderation team will review your comment shortly.",
"comment-post-banned-word": "Your comment contains one or more words that are not permitted, so it will not be published. If you think this message is incorrect, please contact our moderation team."
},
"es": {
"post": "Publicar",
"reply": "Respuesta",
"comment": "Comentario",
"name": "Nombre",
"comment-post-notif": "¡traduceme!",
"comment-post-notif-premod": "¡traduceme!"
"comment-post-notif": "Tu comentario ha sido publicado.",
"comment-post-notif-premod": "Gracias por comentar. Nuestro equipo de moderación va a revisarlo muy pronto.",
"comment-post-banned-word": "Tu comentario contiene una o más palabras que no estan permitidasen nuestro espacio, por lo que no será publicado. Si crees que es un error, por favor contacta a nuestro equipo de moderación."
}
}
+1 -1
View File
@@ -25,7 +25,7 @@
"report": "Informe",
"reported": "Informado",
"report-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar.",
"report-notif-remove": "¡traduceme!",
"report-notif-remove": "Tu marca ha sido eliminada.",
"step-1-header": "¡traduceme!",
"step-2-header": "¡traduceme!",
"step-3-header": "¡traduceme!",
View File
+10
View File
@@ -77,6 +77,16 @@
background: #696969;
}
.type--green {
color: white;
background: #00897B;
}
.type--green:hover {
color: white;
background: #00a291;
}
.full {
width: 100%;
margin: 0;
+70
View File
@@ -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;
}
+16
View File
@@ -0,0 +1,16 @@
import React from 'react';
import styles from './Checkbox.css';
export default ({name, cStyle = 'base', onChange, label, className, info, checked = 'false'}) => (
<label className={`${styles.label} ${styles[`type--${cStyle}`]} ${className}`} htmlFor={name}>
<input type="checkbox" id={name} name={name} onChange={onChange} checked={checked} />
<span className={styles.checkbox}></span>
{label && <span>{label}</span>}
{info && (
<div className={styles.checkboxInfo}>
<h4>{info.title}</h4>
<span>{info.description}</span>
</div>
)}
</label>
);
+1
View File
@@ -8,3 +8,4 @@ export {default as Button} from './components/Button';
export {default as Spinner} from './components/Spinner';
export {default as Tooltip} from './components/Tooltip';
export {default as PopupMenu} from './components/PopupMenu';
export {default as Checkbox} from './components/Checkbox';
+3 -1
View File
@@ -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
});
/**
+2 -9
View File
@@ -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) => {
-1
View File
@@ -108,7 +108,6 @@ router.post('/', wordlist.filter('body'), (req, res, next) => {
author_id: req.user.id
}))
.then((comment) => {
// The comment was created! Send back the created comment.
res.status(201).json(comment);
})
@@ -131,7 +131,7 @@ describe('itemActions', () => {
body: JSON.stringify(item.data)
}
);
expect(id).to.equal('123');
expect(id).to.deep.equal({id: '123'});
expect(store.getActions()[0]).to.deep.equal({
type: actions.ADD_ITEM,
item: {