Merge branch 'master' into ban-user

This commit is contained in:
Gabriela Rodríguez Berón
2016-12-09 10:43:55 -10:00
committed by GitHub
13 changed files with 282 additions and 138 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
@@ -46,6 +46,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",
@@ -98,6 +101,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",
+1 -1
View File
@@ -192,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;
});
};
}
+1 -2
View File
@@ -6,7 +6,7 @@ import * as actions from '../actions/items';
const initialState = fromJS({
comments: {},
users: {},
actions: {}
actions: {}
});
export default (state = initialState, action) => {
@@ -17,7 +17,6 @@ export default (state = initialState, action) => {
return state.setIn([action.item_type, action.id, action.property], fromJS(action.value));
case actions.APPEND_ITEM_ARRAY:
return state.updateIn([action.item_type, action.id, action.property], (prop) => {
console.log(prop);
if (action.add_to_front) {
return prop ? prop.unshift(fromJS(action.value)) : fromJS([action.value]);
} else {
+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
@@ -9,6 +9,6 @@
"flag": "Marcar",
"flagged": "Marcado",
"flag-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar.",
"flag-notif-remove": "¡traduceme!"
"flag-notif-remove": "Tu marca ha sido eliminada."
}
}
-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: {