Merge branch 'master'

Conflicts:
	routes/api/comments/index.js
This commit is contained in:
Riley Davis
2016-12-19 10:28:54 -07:00
25 changed files with 74 additions and 22 deletions
+2
View File
@@ -21,6 +21,8 @@
"no-eval": [2],
"no-global-assign": [2],
"no-implied-eval": [2],
"lines-around-comment": ["warn", {"beforeLineComment": true}],
"spaced-comment": ["warn", "always", { "line": { "exceptions": ["-", "="] } }],
"no-script-url": [2],
"no-throw-literal": [2],
"yoda": [1],
+1
View File
@@ -22,6 +22,7 @@ if (app.get('env') !== 'test') {
//==============================================================================
app.set('trust proxy', 1);
// We disable frameward on helmet to allow crossdomain injection of the embed
app.use(helmet({
frameguard: false
+2
View File
@@ -60,11 +60,13 @@ function normalizePort(val) {
let port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
@@ -31,6 +31,7 @@ export default class CommentList extends React.Component {
// add key handlers and gestures
componentDidMount () {
this.bindKeyHandlers();
// this.bindGestures() // need to check whether we're on a mobile device or this throws an Error
}
@@ -80,6 +81,7 @@ export default class CommentList extends React.Component {
const {commentIds} = this.props;
const {active} = this.state;
// check boundaries
if (active === null || !commentIds.length) {
this.setState({active: commentIds[0]});
@@ -102,6 +104,7 @@ export default class CommentList extends React.Component {
// TODO: In the future this can be improved and look at the actual state to
// resolve since the content of the list could change externally. For now it works as expected
onClickAction (action, id, author_id) {
// activate the next comment
if (id === this.state.active) {
const {commentIds} = this.props;
@@ -69,11 +69,17 @@ const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
}
};
const CommentSettings = ({updateSettings, settingsError, settings, errors}) => <List>
const CommentSettings = ({fetchingSettings, updateSettings, settingsError, settings, errors}) => {
if (fetchingSettings) {
/* maybe a spinner here at some point */
return <p>Loading settings...</p>;
}
return <List>
<ListItem className={`${styles.configSetting} ${settings.moderation === 'pre' ? styles.enabledSetting : styles.disabledSetting}`}>
<ListItemAction>
<Checkbox
onClick={updateModeration(updateSettings, settings.moderation)}
onChange={updateModeration(updateSettings, settings.moderation)}
checked={settings.moderation === 'pre'} />
</ListItemAction>
<ListItemContent>
@@ -86,7 +92,7 @@ const CommentSettings = ({updateSettings, settingsError, settings, errors}) => <
<ListItem className={`${styles.configSetting} ${settings.charCountEnable ? styles.enabledSetting : styles.disabledSetting}`}>
<ListItemAction>
<Checkbox
onClick={updateCharCountEnable(updateSettings, settings.charCountEnable)}
onChange={updateCharCountEnable(updateSettings, settings.charCountEnable)}
checked={settings.charCountEnable} />
</ListItemAction>
<ListItemContent>
@@ -113,7 +119,7 @@ const CommentSettings = ({updateSettings, settingsError, settings, errors}) => <
<ListItem threeLine className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? styles.enabledSetting : styles.disabledSetting}`}>
<ListItemAction>
<Checkbox
onClick={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
onChange={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
checked={settings.infoBoxEnable} />
</ListItemAction>
<ListItemContent>
@@ -144,7 +150,9 @@ const CommentSettings = ({updateSettings, settingsError, settings, errors}) => <
value={getTimeoutAmount(settings.closedTimeout)}
label={lang.t('configure.closed-comments-label')} />
<div className={styles.configTimeoutSelect}>
<SelectField value={getTimeoutMeasure(settings.closedTimeout)}
<SelectField
label="comments closed time window"
value={getTimeoutMeasure(settings.closedTimeout)}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout, true)}>
<Option value={'hours'}>{lang.t('configure.hours')}</Option>
<Option value={'days'}>{lang.t('configure.days')}</Option>
@@ -164,6 +172,7 @@ const CommentSettings = ({updateSettings, settingsError, settings, errors}) => <
</ListItemContent>
</ListItem>
</List>;
};
export default CommentSettings;
@@ -78,6 +78,7 @@ class Configure extends React.Component {
switch(section){
case 'comments':
return <CommentSettings
fetchingSettings={this.props.fetchingSettings}
settings={this.props.settings}
updateSettings={this.onSettingUpdate}
errors={this.state.errors}
@@ -48,6 +48,7 @@ class ModerationQueue extends React.Component {
// Hack for dynamic mdl tabs
componentDidMount () {
if (typeof componentHandler !== 'undefined') {
// FIXME: fix this hack
componentHandler.upgradeAllRegistered(); // eslint-disable-line no-undef
}
@@ -55,6 +56,7 @@ class ModerationQueue extends React.Component {
// Dispatch the update status action
onCommentAction (action, id) {
// If not banning then change the status to approved or flagged as action = status
this.props.dispatch(updateStatus(action, id));
}
@@ -98,7 +100,7 @@ class ModerationQueue extends React.Component {
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.rejected')}</a>
<a href='#flagged' onClick={() => this.onTabClick('flagged')}
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.flagged')}</a>
<a href='#' onClick={this.showShortcuts}
<a href='#shortcuts' onClick={this.showShortcuts}
className={`mdl-tabs__tab ${styles.tab} ${styles.showShortcuts}`}>
<Icon name='keyboard' />
<span>{lang.t('modqueue.showshortcuts')}</span>
@@ -120,7 +122,7 @@ class ModerationQueue extends React.Component {
handleClose={() => this.hideBanUserDialog()}
onClickBanUser={(userId, commentId) => this.banUser(userId, commentId)}
user={comments.banUser}/>
</div>
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
<CommentList
isActive={activeTab === 'rejected'}
@@ -134,7 +136,7 @@ class ModerationQueue extends React.Component {
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='flagged'>
<CommentList
isActive={activeTab === 'rejected'}
isActive={activeTab === 'flagged'}
singleView={singleView}
commentIds={flaggedIds}
comments={comments.byId}
@@ -143,11 +145,14 @@ class ModerationQueue extends React.Component {
actions={['reject', 'approve']}
loading={comments.loading} />
</div>
<ModerationKeysModal open={modalOpen}
onClose={() => this.setState({modalOpen: false})} />
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='shortcuts'>
<ModerationKeysModal open={modalOpen}
onClose={() => this.setState({modalOpen: false})} />
</div>
</div>
</div>
);
}
}
@@ -38,6 +38,7 @@ Promise.all([
coralApi('/comments?action_type=flag')
])
.then(([pending, rejected, flagged]) => {
/* Combine seperate calls into a single object */
let all = {};
all.comments = pending.comments
@@ -55,6 +56,7 @@ Promise.all([
return all;
})
.then(all => {
/* Post comments and users to redux store. Actions will be posted when they are needed. */
store.dispatch({type: 'USERS_MODERATION_QUEUE_FETCH_SUCCESS',
users: all.users});
@@ -62,6 +64,7 @@ Promise.all([
comments: all.comments});
});
// .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
@@ -58,6 +58,7 @@ class CommentStream extends Component {
}
componentDidMount () {
// Set up messaging between embedded Iframe an parent component
this.pym = new Pym.Child({polling: 100});
+1
View File
@@ -118,6 +118,7 @@ export function getStream (assetUrl) {
/* Sort comments by date*/
json.comments.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
const rels = json.comments.reduce((h, item) => {
/* Check for root and child comments. */
if (
item.asset_id === assetId &&
@@ -9,6 +9,7 @@ import get from 'lodash/get';
class i18n {
constructor (translations) {
/**
* Register locales
*/
@@ -16,6 +17,7 @@ class i18n {
this.locales = {'en': 'en', 'es': 'es'};
timeago.register('es_ES', esTA);
this.timeagoInstance = new timeago();
/**
* Load translations
*/
@@ -55,6 +57,7 @@ class i18n {
this.t = (key, ...replacements) => {
if (has(this.translations, key)) {
let translation = get(this.translations, key);
// replace any {n} with the arguments passed to this method
replacements.forEach((str, i) => {
translation = translation.replace(new RegExp(`\\{${i}\\}`, 'g'), str);
+2 -1
View File
@@ -32,7 +32,7 @@ class FlagButton extends Component {
const {postAction, addItem, updateItem, flag, id, author_id} = this.props;
const {itemType, field, detail, step, otherText, posted} = this.state;
//Proceed to the next step or close the menu if we've reached the end
// Proceed to the next step or close the menu if we've reached the end
if (step + 1 >= this.props.getPopupMenu.length) {
this.setState({showMenu: false});
} else {
@@ -41,6 +41,7 @@ class FlagButton extends Component {
// If itemType and detail are both set, post the action
if (itemType && detail && !posted) {
// Set the text from the "other" field if it exists.
const updatedDetail = otherText || detail;
let item_id;
@@ -21,6 +21,7 @@ class SignInContainer extends Component {
}
componentWillMount () {
// Fetch commentHistory
this.props.fetchCommentsByUserId(this.props.userData.id);
}
@@ -38,6 +39,7 @@ class SignInContainer extends Component {
const commentsMostRecentFirst = user
.myComments.map(id => items.comments[id])
.sort(({created_at:a}, {created_at:b}) => {
// descending order, created_at
// js date strings can be sorted lexigraphically.
const aLessThanB = a < b ? 1 : 0;
+1
View File
@@ -7,6 +7,7 @@ const maxRecursion = 3;
* payload response first based on user and role.
*/
module.exports = (req, res, next) => {
/**
* Updates the original document based on filtering out for roles.
* @param {Mixed} o original object to be modified
+3
View File
@@ -88,6 +88,7 @@ const UserSchema = new mongoose.Schema({
// Status provides a string that says in which state the account is.
// When the account is banned, the user login is disabled.
status: {type: String, enum: USER_STATUS, default: 'active'},
// User's settings
settings: {
bio: {
@@ -501,6 +502,7 @@ UserService.createPasswordResetToken = function (email) {
.then(user => {
if (user === null) {
// since we don't want to reveal that the email does/doesn't exist
// just go ahead and resolve the Promise with null and check in the endpoint
return Promise.resolve(null);
@@ -528,6 +530,7 @@ UserService.verifyPasswordResetToken = token => {
});
})
.then(decoded => {
/**
* TODO: check the jti from this decoded token in redis
* and make an entry if it does not exist.
+1
View File
@@ -4,6 +4,7 @@ const router = express.Router();
// Get /password-reset expects a signed token (JWT) in the hash.
// Links to this endpoint are generated by /views/password-reset-email.ejs.
router.get('/password-reset', (req, res, next) => {
// TODO: store the redirect uri in the token or something fancy.
// admins and regular users should probably be redirected to different places.
res.render('password-reset', {redirectUri: process.env.TALK_ROOT_URL});
+1
View File
@@ -82,6 +82,7 @@ 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)
+2
View File
@@ -107,6 +107,7 @@ router.post('/', wordlist.filter('body'), (req, res, next) => {
// Check to see if the asset has closed commenting...
if (asset.isClosed) {
// They have, ensure that we send back an error.
return Promise.reject(new Error(`asset has commenting closed because: ${asset.closedMessage}`));
}
@@ -117,6 +118,7 @@ router.post('/', wordlist.filter('body'), (req, res, next) => {
// Return `premod` if pre-moderation is enabled and an empty "new" status
// in the event that it is not in pre-moderation mode.
.then(({moderation, charCountEnable, charCount}) => {
// Reject if the comment is too long
if (charCountEnable && body.length > charCount) {
return 'rejected';
+2
View File
@@ -25,6 +25,7 @@ router.get('/', (req, res, next) => {
// Get the asset_id for this url (or create it if it doesn't exist)
Promise.all([
// Find or create the asset by url.
Asset.findOrCreateByUrl(asset_url)
@@ -70,6 +71,7 @@ router.get('/', (req, res, next) => {
settings
]);
})
// Get all the users and actions for those comments.
.then(([comments, asset, settings]) => {
+1
View File
@@ -123,6 +123,7 @@ router.post('/request-password-reset', (req, res, next) => {
to: email,
html: resetEmailTemplate({
token,
// probably more clear to explicitly pass this
rootURL: process.env.TALK_ROOT_URL
})
+1
View File
@@ -6,6 +6,7 @@ router.use('/:embed', (req, res, next) => {
case 'stream':
return res.render('embed/stream', {});
default:
// will return a 404.
return next();
}
+1 -1
View File
@@ -2,7 +2,7 @@ const mongoose = require('mongoose');
const debug = require('debug')('talk:db');
const enabled = require('debug').enabled;
//Append '-test' to the db if node_env === 'test'
// Append '-test' to the db if node_env === 'test'
let url = process.env.TALK_MONGO_URL || 'mongodb://localhost/coral-talk';
if (process.env.NODE_ENV === 'test') {
@@ -74,7 +74,7 @@ describe('itemActions', () => {
});
});
//Disabling tests for this function until is is used again.
// Disabling tests for this function until is is used again.
xdescribe('getItemsArray', () => {
const response = {items: [{type: 'comment', id: '123'}, {type: 'comment', id: '456'}]};
const ids = [1, 2];
+13 -9
View File
@@ -18,12 +18,13 @@ module.exports = {
client.perform((client, done) => {
mocks.settings({moderation: 'post'})
.then(() => {
//Load Page
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe')
//Register and Log In
// Register and Log In
.waitForElementVisible('#commentBox', 1000)
.waitForElementVisible('#coralSignInButton', 2000)
.click('#coralSignInButton')
@@ -44,7 +45,7 @@ module.exports = {
.click('.coral-plugin-commentbox-button')
.waitForElementVisible('.comment', 1000)
//Verify that it appears
// Verify that it appears
.assert.containsText('.comment', mockComment);
done();
})
@@ -58,7 +59,8 @@ module.exports = {
client.perform((client, done) => {
mocks.settings({moderation: 'pre'})
.then(() => {
//Load Page
// Load Page
client.url(client.globals.baseUrl)
.frame('coralStreamIframe');
@@ -68,7 +70,7 @@ module.exports = {
.click('.coral-plugin-commentbox-button')
.waitForElementVisible('#coral-notif', 1000)
//Verify that it appears
// Verify that it appears
.assert.containsText('#coral-notif', 'moderation team');
done();
})
@@ -82,7 +84,8 @@ module.exports = {
client.perform((client, done) => {
mocks.settings({moderation: 'post'})
.then(() => {
//Load Page
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe');
@@ -100,7 +103,7 @@ module.exports = {
.click('.coral-plugin-replies-textarea button')
.waitForElementVisible('.reply', 2000)
//Verify that it appears
// Verify that it appears
.assert.containsText('.reply', mockReply);
done();
})
@@ -132,7 +135,8 @@ module.exports = {
}]);
})
.then(() => {
//Load Page
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe');
@@ -145,7 +149,7 @@ module.exports = {
.click('.coral-plugin-replies-textarea button')
.waitForElementVisible('#coral-notif', 1000)
//Verify that it appears
// Verify that it appears
.assert.containsText('#coral-notif', 'moderation team');
done();
})
+2
View File
@@ -3,8 +3,10 @@ const mongoose = require('../../services/mongoose');
// Ensure the NODE_ENV is set to 'test',
// this is helpful when you would like to change behavior when testing.
function clearDB() {
// console.log('Clearing DB', mongoose.connection);
for (let i in mongoose.connection.collections) {
// console.log('Clearing', i);
mongoose.connection.collections[i].remove(function() {});
}