diff --git a/client/coral-admin/src/containers/Configure/StreamSettings.js b/client/coral-admin/src/containers/Configure/StreamSettings.js
index 51250e340..5530bc6cd 100644
--- a/client/coral-admin/src/containers/Configure/StreamSettings.js
+++ b/client/coral-admin/src/containers/Configure/StreamSettings.js
@@ -32,6 +32,11 @@ const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
updateSettings({infoBoxEnable});
};
+const updatePremodLinksEnable = (updateSettings, premodLinks) => () => {
+ const premodLinksEnable = !premodLinks;
+ updateSettings({premodLinksEnable});
+};
+
const updateInfoBoxContent = (updateSettings) => (event) => {
const infoBoxContent = event.target.value;
updateSettings({infoBoxContent});
@@ -94,6 +99,19 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
+
+
+
+
+
+
{lang.t('configure.enable-premod-links')}
+
+ {lang.t('configure.enable-premod-links-text')}
+
+
+
+
+
+
@@ -88,11 +88,11 @@ class ConfigureStreamContainer extends Component {
handleChange={this.handleChange}
handleApply={this.handleApply}
changed={this.state.changed}
- premodLinks={false}
+ premodLinks={settings.premodLinks}
premod={premod}
updateQuestionBoxContent={this.updateQuestionBoxContent}
- questionBoxEnable={questionBoxEnable}
- questionBoxContent={questionBoxContent}
+ questionBoxEnable={settings.questionBoxEnable}
+ questionBoxContent={settings.questionBoxContent}
/>
{status === 'open' ? 'Close' : 'Open'} Comment Stream
diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js
index 5fc1673a1..1c52fb68c 100644
--- a/client/coral-embed-stream/src/Embed.js
+++ b/client/coral-embed-stream/src/Embed.js
@@ -16,7 +16,7 @@ import {queryStream} from 'coral-framework/graphql/queries';
import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag} from 'coral-framework/graphql/mutations';
import {editName} from 'coral-framework/actions/user';
import {updateCountCache} from 'coral-framework/actions/asset';
-import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework';
+import {notificationActions, authActions, assetActions, pym} from 'coral-framework';
import Stream from './Stream';
import InfoBox from 'coral-plugin-infobox/InfoBox';
@@ -176,7 +176,7 @@ class Embed extends Component {
refetch={refetch}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.state.activeReplyBox}
- addNotification={addNotification}
+ addNotification={this.props.addNotification}
depth={0}
postItem={this.props.postItem}
asset={asset}
@@ -220,11 +220,6 @@ class Embed extends Component {
showSignInDialog={this.props.showSignInDialog}
comments={asset.comments} />
-
-
);
@@ -258,7 +248,6 @@ class Embed extends Component {
}
const mapStateToProps = state => ({
- notification: state.notification.toJS(),
auth: state.auth.toJS(),
userData: state.user.toJS(),
asset: state.asset.toJS()
@@ -267,13 +256,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
requestConfirmEmail: () => dispatch(requestConfirmEmail()),
loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)),
- addNotification: (type, text) => {
- pym.sendMessage('getPosition');
-
- pym.onMessage('position', position => {
- dispatch(addNotification(type, text, position));
- });
- },
+ addNotification: (type, text) => dispatch(addNotification(type, text)),
clearNotification: () => dispatch(clearNotification()),
editName: (username) => dispatch(editName(username)),
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js
index 8d8855aa7..db4cbf37e 100644
--- a/client/coral-embed/src/index.js
+++ b/client/coral-embed/src/index.js
@@ -1,5 +1,26 @@
import pym from 'pym.js';
+const snackbarStyles = {
+ position: 'fixed',
+ cursor: 'default',
+ userSelect: 'none',
+ backgroundColor: '#323232',
+ zIndex: 3,
+ willChange: 'transform, opacity',
+ transition: 'transform .35s cubic-bezier(.55,0,.1,1), opacity .35s',
+ pointerEvents: 'none',
+ padding: '12px 18px',
+ color: '#fff',
+ borderRadius: '3px 3px 0 0',
+ textAlign: 'center',
+ maxWidth: '300px',
+ left: '50%',
+ opacity: 0,
+ transform: 'translate(-50%, 20px)',
+ bottom: 0,
+ boxSizing: 'border-box'
+};
+
// This function should return value of window.Coral
const Coral = {};
const Talk = Coral.Talk = {};
@@ -32,6 +53,14 @@ function configurePymParent(pymParent, asset_url) {
let notificationOffset = 200;
let ready = false;
let cachedHeight;
+ const snackbar = document.createElement('div');
+ snackbar.id = 'coral-notif';
+
+ for (let key in snackbarStyles) {
+ snackbar.style[key] = snackbarStyles[key];
+ }
+
+ window.document.body.appendChild(snackbar);
// Resize parent iframe height when child height changes
pymParent.onMessage('height', function(height) {
@@ -41,6 +70,27 @@ function configurePymParent(pymParent, asset_url) {
}
});
+ pymParent.onMessage('coral-clear-notification', function () {
+ snackbar.style.opacity = 0;
+ });
+
+ pymParent.onMessage('coral-alert', function (message) {
+ const [type, text] = message.split('|');
+ snackbar.style.transform = 'translate(-50%, 20px)';
+ snackbar.style.opacity = 0;
+ snackbar.className = `coral-notif-${type}`;
+ snackbar.textContent = text;
+
+ setTimeout(() => {
+ snackbar.style.transform = 'translate(-50%, 0)';
+ snackbar.style.opacity = 1;
+ }, 0);
+
+ setTimeout(() => {
+ snackbar.style.opacity = 0;
+ }, 5000);
+ });
+
// Helps child show notifications at the right scrollTop
pymParent.onMessage('getPosition', function() {
let position = viewport().height + document.body.scrollTop;
diff --git a/client/coral-framework/actions/notification.js b/client/coral-framework/actions/notification.js
index f0ac4d951..f2cc053ec 100644
--- a/client/coral-framework/actions/notification.js
+++ b/client/coral-framework/actions/notification.js
@@ -1,17 +1,9 @@
-export const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
-export const CLEAR_NOTIFICATION = 'CLEAR_NOTIFICATION';
+import {pym} from 'coral-framework';
-export const addNotification = (notifType, text, position) => {
- return {
- type: ADD_NOTIFICATION,
- notifType,
- text,
- position
- };
+export const addNotification = (notifType, text) => {
+ pym.sendMessage('coral-alert', `${notifType}|${text}`);
};
export const clearNotification = () => {
- return {
- type: CLEAR_NOTIFICATION
- };
+ pym.sendMessage('coral-clear-notification');
};
diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js
index a874f8f0c..7dd8bf12b 100644
--- a/client/coral-framework/graphql/mutations/index.js
+++ b/client/coral-framework/graphql/mutations/index.js
@@ -42,7 +42,7 @@ export const postComment = graphql(POST_COMMENT, {
updateQueries: {
AssetQuery: (oldData, {mutationResult:{data:{createComment:{comment}}}}) => {
- if (oldData.asset.settings.moderation === 'PRE') {
+ if (oldData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') {
return oldData;
}
diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js
index 56a2e522c..2900466ee 100644
--- a/client/coral-framework/index.js
+++ b/client/coral-framework/index.js
@@ -4,7 +4,6 @@ import I18n from './modules/i18n/i18n';
import * as authActions from './actions/auth';
import * as assetActions from './actions/asset';
import * as notificationActions from './actions/notification';
-import Notification from './modules/notification/Notification';
export {
pym,
@@ -12,6 +11,5 @@ export {
store,
authActions,
assetActions,
- Notification,
notificationActions
};
diff --git a/client/coral-framework/modules/notification/Notification.js b/client/coral-framework/modules/notification/Notification.js
deleted file mode 100644
index 709403976..000000000
--- a/client/coral-framework/modules/notification/Notification.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import {SnackBar} from 'coral-ui';
-
-const Notification = (props) => {
- if (props.notification.text) {
- setTimeout(() => {
- props.clearNotification();
- }, props.notifLength);
- }
- return (
-
- {
- props.notification.text &&
-
- {props.notification.text}
-
- }
-
- );
-};
-
-export default Notification;
diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js
index 720f15232..9e98c901a 100644
--- a/client/coral-framework/reducers/index.js
+++ b/client/coral-framework/reducers/index.js
@@ -1,11 +1,9 @@
import auth from './auth';
import user from './user';
import asset from './asset';
-import notification from './notification';
export default {
auth,
user,
asset,
- notification
};
diff --git a/client/coral-framework/reducers/notification.js b/client/coral-framework/reducers/notification.js
deleted file mode 100644
index fbc58230f..000000000
--- a/client/coral-framework/reducers/notification.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as actions from '../actions/notification';
-import {Map} from 'immutable';
-
-const initialState = Map({
- text: '',
- type: '',
- position: 400
-});
-
-export default (state = initialState, action) => {
- switch (action.type) {
- case actions.ADD_NOTIFICATION:
- return state
- .merge({
- type: action.notifType,
- text: action.text,
- position: action.position
- });
- case actions.CLEAR_NOTIFICATION:
- return initialState;
- default:
- return state;
- }
-};
diff --git a/client/coral-plugin-replies/ReplyBox.js b/client/coral-plugin-replies/ReplyBox.js
index c50bca124..807ad6699 100644
--- a/client/coral-plugin-replies/ReplyBox.js
+++ b/client/coral-plugin-replies/ReplyBox.js
@@ -1,21 +1,29 @@
-import React, {PropTypes} from 'react';
+import React, {Component, PropTypes} from 'react';
import CommentBox from '../coral-plugin-commentbox/CommentBox';
const name = 'coral-plugin-replies';
-const ReplyBox = ({styles, postItem, assetId, authorId, addNotification, parentId, commentPostedHandler, setActiveReplyBox}) => (
-
-
-
-);
+class ReplyBox extends Component {
+
+ componentDidMount() {
+ document.getElementById('replyText').focus();
+ }
+
+ render() {
+ const {styles, postItem, assetId, authorId, addNotification, parentId, commentPostedHandler, setActiveReplyBox} = this.props;
+ return
+
+
;
+ }
+}
ReplyBox.propTypes = {
setActiveReplyBox: PropTypes.func.isRequired,
diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js
index 553c3f44c..bcab347d7 100644
--- a/graph/mutators/comment.js
+++ b/graph/mutators/comment.js
@@ -3,6 +3,7 @@ const errors = require('../../errors');
const AssetsService = require('../../services/assets');
const ActionsService = require('../../services/actions');
const CommentsService = require('../../services/comments');
+const linkify = require('linkify-it')();
const Wordlist = require('../../services/wordlist');
@@ -54,13 +55,16 @@ const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id =
* @param {String} body body of a comment
* @return {Object} resolves to the wordlist results
*/
-const filterNewComment = (context, {body}) => {
+const filterNewComment = (context, {body, asset_id}) => {
// Create a new instance of the Wordlist.
const wl = new Wordlist();
// Load the wordlist and filter the comment content.
- return wl.load().then(() => wl.scan('body', body));
+ return Promise.all([
+ wl.load().then(() => wl.scan('body', body)),
+ AssetsService.rectifySettings(AssetsService.findById(asset_id))
+ ]);
};
/**
@@ -72,7 +76,7 @@ const filterNewComment = (context, {body}) => {
* @param {Object} [wordlist={}] the results of the wordlist scan
* @return {Promise} resolves to the comment's status
*/
-const resolveNewCommentStatus = (context, {asset_id, body}, wordlist = {}) => {
+const resolveNewCommentStatus = (context, {asset_id, body}, wordlist = {}, settings) => {
// Decide the status based on whether or not the current asset/settings
// has pre-mod enabled or not. If the comment was rejected based on the
@@ -82,6 +86,8 @@ const resolveNewCommentStatus = (context, {asset_id, body}, wordlist = {}) => {
if (wordlist.banned) {
status = Promise.resolve('REJECTED');
+ } else if (settings.premodLinksEnable && linkify.test(body)) {
+ status = Promise.resolve('PREMOD');
} else {
status = AssetsService
.rectifySettings(AssetsService.findById(asset_id).then((asset) => {
@@ -131,13 +137,13 @@ const createPublicComment = (context, commentInput) => {
// We then take the wordlist and the comment into consideration when
// considering what status to assign the new comment, and resolve the new
// status to set the comment to.
- .then((wordlist) => resolveNewCommentStatus(context, commentInput, wordlist)
+ .then(([wordlist, settings]) => resolveNewCommentStatus(context, commentInput, wordlist, settings)
// Then we actually create the comment with the new status.
.then((status) => createComment(context, commentInput, status))
.then((comment) => {
- // If the comment was flagged as being suspect, we need to add a
+ // If the comment has a suspect word or a link, we need to add a
// flag to it to indicate that it needs to be looked at.
// Otherwise just return the new comment.
diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql
index daf9edafa..6cb4cb046 100644
--- a/graph/typeDefs.graphql
+++ b/graph/typeDefs.graphql
@@ -370,6 +370,7 @@ type Settings {
infoBoxEnable: Boolean
infoBoxContent: String
+ premodLinksEnable: Boolean
questionBoxEnable: Boolean
questionBoxContent: String
closeTimeout: Int
diff --git a/models/setting.js b/models/setting.js
index 993384ff1..ac54584a4 100644
--- a/models/setting.js
+++ b/models/setting.js
@@ -40,6 +40,10 @@ const SettingSchema = new Schema({
type: String,
default: ''
},
+ premodLinksEnable: {
+ type: Boolean,
+ default: false
+ },
organizationName: {
type: String
},
diff --git a/package.json b/package.json
index f67a36908..92442efd2 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"inquirer": "^3.0.1",
"jsonwebtoken": "^7.1.9",
"kue": "^0.11.5",
+ "linkify-it": "^2.0.3",
"lodash": "^4.16.6",
"metascraper": "^1.0.6",
"minimist": "^1.2.0",
diff --git a/test/client/coral-framework/reducers/notificationReducer.spec.js b/test/client/coral-framework/reducers/notificationReducer.spec.js
deleted file mode 100644
index 40c50d11a..000000000
--- a/test/client/coral-framework/reducers/notificationReducer.spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import {Map} from 'immutable';
-import {expect} from 'chai';
-import notificationReducer from '../../../../client/coral-framework/reducers/notification';
-import * as actions from '../../../../client/coral-framework/actions/notification';
-
-describe ('notificationsReducer', () => {
- describe('ADD_NOTIFICATION', () => {
- it('should add a notification', () => {
- const action = {
- type: actions.ADD_NOTIFICATION,
- text: 'Test notification',
- notifType: 'test'
- };
- const store = new Map({});
- const result = notificationReducer(store, action);
- expect(result.get('text')).to.equal(action.text);
- expect(result.get('type')).to.equal(action.notifType);
- });
- });
-
- describe('CLEAR_NOTIFICATION', () => {
- it('should clear a notification', () => {
- const action = {
- type: actions.CLEAR_NOTIFICATION
- };
- const store = new Map({
- text: 'Test notification',
- type: 'test'
- });
- const result = notificationReducer(store, action);
- expect(result.get('text')).to.equal('');
- expect(result.get('type')).to.equal('');
- });
- });
-});
diff --git a/test/client/coral-framework/store/notificationReducer.spec.js b/test/client/coral-framework/store/notificationReducer.spec.js
deleted file mode 100644
index 40c50d11a..000000000
--- a/test/client/coral-framework/store/notificationReducer.spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import {Map} from 'immutable';
-import {expect} from 'chai';
-import notificationReducer from '../../../../client/coral-framework/reducers/notification';
-import * as actions from '../../../../client/coral-framework/actions/notification';
-
-describe ('notificationsReducer', () => {
- describe('ADD_NOTIFICATION', () => {
- it('should add a notification', () => {
- const action = {
- type: actions.ADD_NOTIFICATION,
- text: 'Test notification',
- notifType: 'test'
- };
- const store = new Map({});
- const result = notificationReducer(store, action);
- expect(result.get('text')).to.equal(action.text);
- expect(result.get('type')).to.equal(action.notifType);
- });
- });
-
- describe('CLEAR_NOTIFICATION', () => {
- it('should clear a notification', () => {
- const action = {
- type: actions.CLEAR_NOTIFICATION
- };
- const store = new Map({
- text: 'Test notification',
- type: 'test'
- });
- const result = notificationReducer(store, action);
- expect(result.get('text')).to.equal('');
- expect(result.get('type')).to.equal('');
- });
- });
-});