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-stream/style/default.css b/client/coral-embed-stream/style/default.css index 009246963..68bac3110 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -1,10 +1,16 @@ +* { + font-weight: inherit; + font-family: inherit; + font-style: inherit; + font-size: 100%; +} + html, body { width:auto; height:auto; } body { - font-family: 'Open Sans', sans-serif; font-family: 'Lato', sans-serif; width: 100%; font-size: 14px; @@ -81,6 +87,15 @@ hr { margin-bottom: 10px; display: block; box-sizing: border-box; + border-radius: 2px; +} + + +.commentStream .material-icons { + vertical-align: middle; + width: 1em; + font-size: 1em; + overflow: hidden; } /* Question Box Styles */ @@ -96,15 +111,41 @@ hr { font-weight: bold; font-size: 14px; display: block; + overflow: hidden; + height: 50px; } -.coral-plugin-questionbox-icon { +.coral-plugin-questionbox-icon.bubble{ + position: absolute; + top: 11px; + left: 15px; + color: #949393; + font-size: 20px; + z-index: 0; +} + +.coral-plugin-questionbox-icon.person{ + z-index: 2; + top: 20px; + left: 20px; + position: absolute; + font-size: 24px; + color: white; +} + +.coral-plugin-questionbox-box { + position: relative; border: 0; background: black; color: white; padding: 20px; margin-left: 0px !important; margin-right: 10px; + display: inline-block; + width: 15px; + height: 100%; + padding: 3px 20px; + vertical-align: middle; } .hidden { @@ -128,6 +169,8 @@ hr { flex: 1; padding: 5px; min-height: 100px; + margin-top: 10px; + font-size: 14px; } .coral-plugin-commentbox-button-container { @@ -238,13 +281,6 @@ button.comment__action-button[disabled], white-space: nowrap; } -.commentStream .material-icons { - vertical-align: middle; - width: 1em; - font-size: 1em; - overflow: hidden; -} - .likedButton { color: rgb(0,134,227); } @@ -324,14 +360,14 @@ button.comment__action-button[disabled], } .coral-plugin-flags-popup-counter { - float: left; - margin-top: 21px; - color: #999; + float: left; + margin-top: 21px; + color: #999; } .coral-plugin-flags-popup-button { - float: right; - margin-top: 10px; + float: right; + margin-top: 10px; } .coral-plugin-flags-reason-text { @@ -387,6 +423,8 @@ button.coral-load-more { color: #FFF; background-color: #2376D8; cursor: pointer; + padding: 10px; + border-radius: 2px; } button.coral-load-more:hover { 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-commentbox/translations.json b/client/coral-plugin-commentbox/translations.json index 83ed974e4..0f77fc334 100644 --- a/client/coral-plugin-commentbox/translations.json +++ b/client/coral-plugin-commentbox/translations.json @@ -3,7 +3,7 @@ "post": "Post", "cancel": "Cancel", "reply": "Reply", - "comment": "Comment", + "comment": "Post a 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.", @@ -14,7 +14,7 @@ "post": "Publicar", "cancel": "Cancelar", "reply": "Respuesta", - "comment": "Comentario", + "comment": "Escribe un Comentario", "name": "Nombre", "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.", diff --git a/client/coral-plugin-questionbox/QuestionBox.js b/client/coral-plugin-questionbox/QuestionBox.js index 542cb8790..3c2410d44 100644 --- a/client/coral-plugin-questionbox/QuestionBox.js +++ b/client/coral-plugin-questionbox/QuestionBox.js @@ -2,9 +2,11 @@ import React from 'react'; const packagename = 'coral-plugin-questionbox'; const QuestionBox = ({enable, content}) => -
    - chat_bubble person +
    +
    + chat_bubble + person +
    {content}
    ; 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/client/coral-ui/components/Button.css b/client/coral-ui/components/Button.css index 9a2611630..14ed64aa2 100644 --- a/client/coral-ui/components/Button.css +++ b/client/coral-ui/components/Button.css @@ -9,7 +9,7 @@ min-width: 64px; padding: 0 8px; display: inline-block; - font-family: 'Roboto','Helvetica','Arial',sans-serif; + font-family: inherit; font-size: 14px; overflow: hidden; will-change: box-shadow,transform; 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(''); - }); - }); -});