Merge pull request #68 from coralproject/comment-box

Comment box
This commit is contained in:
David Erwin
2016-11-16 10:48:18 -05:00
committed by GitHub
17 changed files with 246 additions and 94 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ import {Button, Icon} from 'react-mdl';
import timeago from 'timeago.js';
import styles from './CommentList.css';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../translations';
import translations from '../translations.json';
// Render a single comment for the list
export default props => (
+8 -4
View File
@@ -2,22 +2,26 @@ import React from 'react';
import {Layout, Navigation, Drawer, Header} from 'react-mdl';
import {Link} from 'react-router';
import styles from './Header.css';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../translations.json';
// App header. If we add a navbar it should be here
export default (props) => (
<Layout fixedDrawer>
<Header title='Talk'>
<Navigation>
<Link className={styles.navLink} to={'/admin/'}>Moderate</Link>
<Link className={styles.navLink} to={'/admin/configure'}>Configure</Link>
<Link className={styles.navLink} to={'/admin/'}>{lang.t('configure.moderate')}</Link>
<Link className={styles.navLink} to={'/admin/configure'}>{lang.t('Configure')}</Link>
</Navigation>
</Header>
<Drawer>
<Navigation>
<Link className={styles.navLink} to={'/admin/'}>Moderate</Link>
<Link className={styles.navLink} to={'/admin/configure'}>Configure</Link>
<Link className={styles.navLink} to={'/admin/'}>{lang.t('configure.moderate')}</Link>
<Link className={styles.navLink} to={'/admin/configure'}>{lang.t('configure.Configure')}</Link>
</Navigation>
</Drawer>
{props.children}
</Layout>
);
const lang = new I18n(translations);
@@ -1,5 +1,5 @@
import I18n from 'coral-framework/i18n/i18n';
import translations from '../translations';
import translations from '../translations.json';
import React from 'react';
import Modal from 'components/Modal';
import styles from './ModerationKeysModal.css';
@@ -2,13 +2,17 @@ import React from 'react';
import {Navigation, Drawer} from 'react-mdl';
import {Link} from 'react-router';
import styles from './Header.css';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../../translations.json';
export default () => (
<Drawer>
<Navigation>
<Link className={styles.navLink} to="/admin">Moderate</Link>
<Link className={styles.navLink} to="/admin/community">Community</Link>
<Link className={styles.navLink} to="/admin/configure">Configure</Link>
<Link className={styles.navLink} to="/admin">{lang.t('configure.moderate')}</Link>
<Link className={styles.navLink} to="/admin/community">{lang.t('configure.community')}</Link>
<Link className={styles.navLink} to="/admin/configure">{lang.t('configure.configure')}</Link>
</Navigation>
</Drawer>
);
const lang = new I18n(translations);
@@ -2,16 +2,20 @@ import React from 'react';
import {Navigation, Header} from 'react-mdl';
import {Link} from 'react-router';
import styles from './Header.css';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../../translations.json';
export default () => (
<Header title='Talk'>
<Navigation>
<Link className={styles.navLink} to="/admin">Moderate</Link>
<Link className={styles.navLink} to="/admin/community">Community</Link>
<Link className={styles.navLink} to="/admin/configure">Configure</Link>
<Link className={styles.navLink} to="/admin">{lang.t('configure.moderate')}</Link>
<Link className={styles.navLink} to="/admin/community">{lang.t('configure.community')}</Link>
<Link className={styles.navLink} to="/admin/configure">{lang.t('configure.configure')}</Link>
<span>
{`v${process.env.VERSION}`}
</span>
</Navigation>
</Header>
);
const lang = new I18n(translations);
@@ -1,6 +1,6 @@
import React from 'react';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../../translations';
import translations from '../../translations.json';
import {Grid, Cell} from 'react-mdl';
import styles from './Community.css';
@@ -23,6 +23,21 @@
cursor: pointer;
}
.configSettingInfoBox {
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
cursor: pointer;
width: auto;
height: auto;
text-align: left;
}
.configSettingInfoBox p {
font-size: 12px;
bottom: 0;
}
.configSettingEmbed {
border: 1px solid #ccc;
border-radius: 4px;
@@ -53,3 +68,7 @@
font-size: 14px;
letter-spacing: 0.03em;
}
.hidden {
display: none;
}
+41 -21
View File
@@ -7,14 +7,14 @@ import {
ListItem,
ListItemContent,
ListItemAction,
//Textfield,
Textfield,
Checkbox,
Button,
Icon
} from 'react-mdl';
import styles from './Configure.css';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../translations';
import translations from '../translations.json';
class Configure extends React.Component {
constructor (props) {
@@ -24,6 +24,8 @@ class Configure extends React.Component {
this.copyToClipBoard = this.copyToClipBoard.bind(this);
this.updateModeration = this.updateModeration.bind(this);
this.updateInfoBoxEnable = this.updateInfoBoxEnable.bind(this);
this.updateInfoBoxContent = this.updateInfoBoxContent.bind(this);
this.saveSettings = this.saveSettings.bind(this);
}
@@ -36,6 +38,16 @@ class Configure extends React.Component {
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}));
}
saveSettings () {
this.props.dispatch(saveSettingsToServer());
}
@@ -48,22 +60,30 @@ class Configure extends React.Component {
onClick={this.updateModeration}
checked={this.props.settings.moderation === 'pre'} />
</ListItemAction>
Enable pre-moderation
{lang.t('configure.enable-pre-moderation')}
</ListItem>
{/*
<ListItem className={styles.configSetting}>
<ListItemAction><Checkbox /></ListItemAction>
Include Comment Stream Description for Readers
<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.configSetting}>
<ListItemAction><Checkbox /></ListItemAction>
Limit Comment Length
<Textfield
pattern='-?[0-9]*(\.[0-9]+)?'
error='Input is not a number!'
label='Maximum Characters' />
<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>;
}
@@ -84,7 +104,7 @@ class Configure extends React.Component {
return <List>
<ListItem className={styles.configSettingEmbed}>
<p>Copy and paste code below into your CMS to embed your comment box in your articles</p>
<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')}
@@ -100,8 +120,8 @@ class Configure extends React.Component {
render () {
let pageTitle = this.state.activeSection === 'comments'
? 'Comment Settings'
: 'Embed Comment Stream';
? lang.t('configure.comment-settings')
: lang.t('configure.embed-comment-stream');
if (this.props.fetchingSettings) {
pageTitle += ' - Loading...';
@@ -114,16 +134,16 @@ class Configure extends React.Component {
<ListItem className={styles.settingOption}>
<ListItemContent
onClick={this.changeSection.bind(this, 'comments')}
icon='settings'>Comment Settings</ListItemContent>
icon='settings'>{lang.t('configure.comment-settings')}</ListItemContent>
</ListItem>
<ListItem className={styles.settingOption}>
<ListItemContent
onClick={this.changeSection.bind(this, 'embed')}
icon='code'>Embed Comment Stream</ListItemContent>
icon='code'>{lang.t('configure.embed-comment-stream')}</ListItemContent>
</ListItem>
</List>
<Button raised colored onClick={this.saveSettings}>
<Icon name='save' /> Save Changes
<Icon name='save' /> {lang.t('configure.save-changes')}
</Button>
</div>
<div className={styles.mainContent}>
@@ -6,7 +6,7 @@ import {updateStatus} from 'actions/comments';
import styles from './ModerationQueue.css';
import key from 'keymaster';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../translations';
import translations from '../translations.json';
/*
* Renders the moderation queue as a tabbed layout with 3 moderation
@@ -91,7 +91,7 @@ class ModerationQueue extends React.Component {
commentIds={
comments
.get('ids')
.filter(id =>
.filter(id =>
comments
.get('byId')
.get(id)
-55
View File
@@ -1,55 +0,0 @@
export default {
en: {
'community': {
username_and_email: 'Username and Email',
account_creation_date: 'Account Creation Date',
newsroom_role: 'Newsroom Role',
admin: 'Administrator',
moderator: 'Moderator',
role: 'Select role...'
},
'modqueue': {
'pending': 'pending',
'rejected': 'rejected',
'flagged': 'flagged',
'shortcuts': 'Shortcuts',
'close': 'Close',
'actions': 'Actions',
'navigation': 'Navigation',
'approve': 'Approve comment',
'reject': 'Reject comment',
'nextcomment': 'Go to the next comment',
'prevcomment': 'Go to the previous comment',
'singleview': 'Toggle single comment edit view',
'thismenu': 'Open this menu'
},
'comment': {
'flagged': 'flagged',
'anon': 'Anonymous'
},
'embedlink': {
'copy': 'Copy to Clipboard'
}
},
es: {
'community': {
username_and_email: 'Usuario y E-mail',
account_creation_date: 'Fecha de creación de la cuenta',
newsroom_role: 'Rol en la redacción',
admin: 'Administrador',
moderator: 'Moderador',
role: 'Select role...'
},
'modqueue': {
'pending': 'pendiente',
'rejected': 'rechazado',
'flagged': 'marcado',
'shortcuts': 'Atajos de teclado',
'close': 'Cerrar'
},
'comment': {
'flagged': 'marcado',
'anon': 'Anónimo'
}
}
};
+81
View File
@@ -0,0 +1,81 @@
{
"en": {
"community": {
"username_and_email": "Username and Email",
"account_creation_date": "Account Creation Date",
"newsroom_role": "Newsroom Role",
"admin": "Administrator",
"moderator": "Moderator",
"role": "Select role..."
},
"modqueue": {
"pending": "pending",
"rejected": "rejected",
"flagged": "flagged",
"shortcuts": "Shortcuts",
"close": "Close",
"actions": "Actions",
"navigation": "Navigation",
"approve": "Approve comment",
"reject": "Reject comment",
"nextcomment": "Go to the next comment",
"prevcomment": "Go to the previous comment",
"singleview": "Toggle single comment edit view",
"thismenu": "Open this menu"
},
"comment": {
"flagged": "flagged",
"anon": "Anonymous"
},
"embedlink": {
"copy": "Copy to Clipboard"
},
"configure": {
"enable-pre-moderation": "Enable pre-moderation",
"include-comment-stream": "Include Comment Stream Description for Readers.",
"include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.",
"include-text": "Include your text here.",
"comment-settings": "Comment Settings",
"embed-comment-stream": "Embed Comment Stream",
"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",
"configure": "Configure",
"community": "Community"
}
},
"es": {
"community": {
"username_and_email": "Usuario y E-mail",
"account_creation_date": "Fecha de creación de la cuenta",
"newsroom_role": "Rol en la redacción",
"admin": "Administrador",
"moderator": "Moderador",
"role": "Select role..."
},
"modqueue": {
"pending": "pendiente",
"rejected": "rechazado",
"flagged": "marcado",
"shortcuts": "Atajos de teclado",
"close": "Cerrar"
},
"comment": {
"flagged": "marcado",
"anon": "Anónimo"
},
"configure": {
"enable-pre-moderation": "Habilitar pre-moderación",
"include-comment-stream": "Incluir la Descripción a un Hilo de Comentario para los y las Lectoras.",
"include-comment-stream-desc": "Escribir un mensaje que será agregado a la parte de arriba del tu hilo de comentarios. Por ejemplo, un tema, guias de comunidad, etc.",
"include-text": "Incluir tu texto aqui.",
"comment-settings": "Configuración de Comentarios",
"embed-comment-stream": "Colocar Hilo de Comentarios",
"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",
"configure": "Configurar",
"community": "Comunidad"
}
}
}
@@ -7,6 +7,7 @@ import {
} from '../../coral-framework';
import {connect} from 'react-redux';
import CommentBox from '../../coral-plugin-commentbox/CommentBox';
import InfoBox from '../../coral-plugin-infobox/InfoBox';
import Content from '../../coral-plugin-commentcontent/CommentContent';
import PubDate from '../../coral-plugin-pubdate/PubDate';
import Count from '../../coral-plugin-comment-count/CommentCount';
@@ -104,6 +105,9 @@ class CommentStream extends Component {
rootItem
? <div>
<div id="commentBox">
<InfoBox
content={this.props.config.infoBoxContent}
enable={this.props.config.infoBoxEnable}/>
<Count
id={rootItemId}
items={this.props.items}/>
@@ -49,6 +49,23 @@ hr {
font-weight: bold;
}
/* Info Box Styles */
.coral-plugin-infobox-info {
position: fixed;
top: 0;
border: 0;
background: rgb(105,105,105);
color: white;
border-radius: 2px;
font-weight: bold;
display: block;
}
.hidden {
visibility: hidden;
display: none;
}
/* Comment Box Styles */
.coral-plugin-commentbox-container {
display: flex;
+10
View File
@@ -0,0 +1,10 @@
import React from 'react';
const packagename = 'coral-plugin-infobox';
const InfoBox = ({enable, content}) =>
<div
className={`${packagename}-info ${enable ? null : ', hidden'}` }>
{content}
</div>;
export default InfoBox;
@@ -0,0 +1,26 @@
import React from 'react';
import {shallow} from 'enzyme';
import {expect} from 'chai';
import InfoBox from '../InfoBox';
describe('InfoBox', () => {
let comment;
let render;
beforeEach(() => {
comment = {};
const postItem = (item) => {
comment.posted = item;
return Promise.resolve(4);
};
render = shallow(<InfoBox
postItem={postItem}
updateItem={(e) => comment.text = e.target.value}
item_id={'1'}
comments={['1', '2', '3']}/>);
});
it('should render the InfoBox appropriately', () => {
expect(render.contains('<div class="InfoBox"')).to.be.truthy;
expect(render.contains('<button class="postCommentButton"')).to.be.truthy;
});
});
+11 -1
View File
@@ -3,7 +3,9 @@ const Schema = mongoose.Schema;
const SettingSchema = new Schema({
id: {type: String, default: '1'},
moderation: {type: String, enum: ['pre', 'post'], default: 'pre'}
moderation: {type: String, enum: ['pre', 'post'], default: 'pre'},
infoBoxEnable: {type: Boolean, default: false},
infoBoxContent: {type: String, default: ''}
}, {
timestamps: {
createdAt: 'created_at',
@@ -35,6 +37,14 @@ SettingSchema.statics.getModerationSetting = function () {
return this.findOne({id: '1'}).select('moderation');
};
/**
* Gets the info box settings and sends it back
* @return {Promise} content the content of the info Box
*/
SettingSchema.statics.getInfoBoxSetting = function () {
return this.findOne({id: '1'}).select('infoBoxEnable', 'infoBoxContent');
};
/**
* This will update the settings object with whatever you pass in
* @param {object} setting a hash of whatever settings you want to update
+10 -2
View File
@@ -8,7 +8,7 @@ const expect = require('chai').expect;
describe('Setting: model', () => {
beforeEach(() => {
const defaults = {id: 1, moderation: 'pre'};
const defaults = {id: 1};
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
});
@@ -18,13 +18,21 @@ describe('Setting: model', () => {
expect(settings).to.have.property('moderation').and.to.equal('pre');
});
});
it('should have two infoBox fields defined', () => {
return Setting.getSettings().then(settings => {
expect(settings).to.have.property('infoBoxEnable').and.to.equal(false);
expect(settings).to.have.property('infoBoxContent').and.to.equal('');
});
});
});
describe('#updateSettings()', () => {
it('should update the settings with a passed object', () => {
const mockSettings = {moderation: 'post'};
const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'};
return Setting.updateSettings(mockSettings).then(updatedSettings => {
expect(updatedSettings).to.have.property('moderation').and.to.equal('post');
expect(updatedSettings).to.have.property('infoBoxEnable', true);
expect(updatedSettings).to.have.property('infoBoxContent', 'yeah');
});
});
});