mirror of
https://github.com/wassname/talk.git
synced 2026-06-29 05:18:32 +08:00
Merge branch 'master' into query-composition
Conflicts: client/coral-embed-stream/src/Embed.js client/coral-embed-stream/src/Stream.js
This commit is contained in:
@@ -6,3 +6,15 @@ export const singleView = () => ({type: actions.SINGLE_VIEW});
|
||||
// Ban User Dialog
|
||||
export const showBanUserDialog = (user, commentId, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, showRejectedNote});
|
||||
export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog});
|
||||
|
||||
// hide shortcuts note
|
||||
export const hideShortcutsNote = () => {
|
||||
try {
|
||||
window.localStorage.setItem('coral:shortcutsNote', 'hide');
|
||||
} catch (e) {
|
||||
|
||||
// above will fail in Safari private mode
|
||||
}
|
||||
|
||||
return {type: actions.HIDE_SHORTCUTS_NOTE};
|
||||
};
|
||||
|
||||
@@ -18,3 +18,47 @@
|
||||
border-radius: 4px;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
.callToAction {
|
||||
position: fixed;
|
||||
left: 10px;
|
||||
bottom: 10px;
|
||||
width: 280px;
|
||||
height: 200px;
|
||||
background: white;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.15);
|
||||
|
||||
.ctaHeader {
|
||||
font-size: 16px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 12px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
li span:first-child, p:last-child span:first-child {
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
float: right;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.smallKey {
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../translations.json';
|
||||
import React from 'react';
|
||||
import React, {PropTypes} from 'react';
|
||||
import Modal from 'components/Modal';
|
||||
import styles from './ModerationKeysModal.css';
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const shortcuts = [
|
||||
{
|
||||
title: 'modqueue.navigation',
|
||||
@@ -23,29 +25,50 @@ const shortcuts = [
|
||||
}
|
||||
];
|
||||
|
||||
export default ({open, onClose}) => (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<h3>{lang.t('modqueue.shortcuts')}</h3>
|
||||
<div className={styles.container}>
|
||||
{shortcuts.map((shortcut, i) => (
|
||||
<table className={styles.table} key={i}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{lang.t(shortcut.title)}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(shortcut.shortcuts).map(key => (
|
||||
<tr key={`${key }tr`}>
|
||||
<td className={styles.shortcut}><span className={styles.key}>{key}</span></td>
|
||||
<td>{lang.t(shortcut.shortcuts[key])}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
export default class ModerationKeysModal extends React.Component {
|
||||
|
||||
const lang = new I18n(translations);
|
||||
static propTypes = {
|
||||
hideShortcutsNote: PropTypes.func.isRequired,
|
||||
shortcutsNoteVisible: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const {open, onClose, hideShortcutsNote, shortcutsNoteVisible} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.callToAction} style={{display: shortcutsNoteVisible === 'show' ? 'block' : 'none'}}>
|
||||
<div onClick={hideShortcutsNote} className={styles.closeButton}>×</div>
|
||||
<p className={styles.ctaHeader}>{lang.t('modqueue.mod-faster')}</p>
|
||||
<p><strong>{lang.t('modqueue.try-these')}:</strong></p>
|
||||
<ul>
|
||||
<li><span>{lang.t('modqueue.approve')}</span> <span className={styles.smallKey}>t</span></li>
|
||||
<li><span>{lang.t('modqueue.reject')}</span> <span className={styles.smallKey}>r</span></li>
|
||||
</ul>
|
||||
<p><span>{lang.t('modqueue.view-more-shortcuts')}</span> <span className={styles.smallKey}>{lang.t('modqueue.shift-key')}</span> + <span className={styles.smallKey}>/</span></p>
|
||||
</div>
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<h3>{lang.t('modqueue.shortcuts')}</h3>
|
||||
<div className={styles.container}>
|
||||
{shortcuts.map((shortcut, i) => (
|
||||
<table className={styles.table} key={i}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{lang.t(shortcut.title)}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(shortcut.shortcuts).map(key => (
|
||||
<tr key={`${key }tr`}>
|
||||
<td className={styles.shortcut}><span className={styles.key}>{key}</span></td>
|
||||
<td>{lang.t(shortcut.shortcuts[key])}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ export const TOGGLE_MODAL = 'TOGGLE_MODAL';
|
||||
export const SINGLE_VIEW = 'SINGLE_VIEW';
|
||||
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
|
||||
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
|
||||
export const HIDE_SHORTCUTS_NOTE = 'HIDE_SHORTCUTS_NOTE';
|
||||
|
||||
@@ -10,7 +10,7 @@ import {banUser, setCommentStatus} from '../../graphql/mutations';
|
||||
|
||||
import {fetchSettings} from 'actions/settings';
|
||||
import {updateAssets} from 'actions/assets';
|
||||
import {toggleModal, singleView, showBanUserDialog, hideBanUserDialog} from 'actions/moderation';
|
||||
import {toggleModal, singleView, showBanUserDialog, hideBanUserDialog, hideShortcutsNote} from 'actions/moderation';
|
||||
|
||||
import {Spinner} from 'coral-ui';
|
||||
import BanUserDialog from '../../components/BanUserDialog';
|
||||
@@ -183,6 +183,8 @@ class ModerationContainer extends Component {
|
||||
rejectComment={props.rejectComment}
|
||||
/>
|
||||
<ModerationKeysModal
|
||||
hideShortcutsNote={props.hideShortcutsNote}
|
||||
shortcutsNoteVisible={moderation.shortcutsNoteVisible}
|
||||
open={moderation.modalOpen}
|
||||
onClose={onClose}/>
|
||||
</div>
|
||||
@@ -204,6 +206,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
fetchSettings: () => dispatch(fetchSettings()),
|
||||
showBanUserDialog: (user, commentId, showRejectedNote) => dispatch(showBanUserDialog(user, commentId, showRejectedNote)),
|
||||
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
|
||||
hideShortcutsNote: () => dispatch(hideShortcutsNote()),
|
||||
});
|
||||
|
||||
export default compose(
|
||||
|
||||
@@ -6,7 +6,8 @@ const initialState = Map({
|
||||
modalOpen: false,
|
||||
user: Map({}),
|
||||
commentId: null,
|
||||
banDialog: false
|
||||
banDialog: false,
|
||||
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show'
|
||||
});
|
||||
|
||||
export default function moderation (state = initialState, action) {
|
||||
@@ -31,6 +32,9 @@ export default function moderation (state = initialState, action) {
|
||||
case actions.SINGLE_VIEW:
|
||||
return state
|
||||
.set('singleView', !state.get('singleView'));
|
||||
case actions.HIDE_SHORTCUTS_NOTE:
|
||||
return state
|
||||
.set('shortcutsNoteVisible', 'hide');
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@
|
||||
"close": "Close",
|
||||
"actions": "Actions",
|
||||
"navigation": "Navigation",
|
||||
"mod-faster": "Moderate faster with keyboard shortcuts",
|
||||
"try-these": "Try these",
|
||||
"view-more-shortcuts": "View more shortcuts",
|
||||
"shift-key": "⇧",
|
||||
"approve": "Approve comment",
|
||||
"reject": "Reject comment",
|
||||
"nextcomment": "Go to the next comment",
|
||||
@@ -226,6 +230,10 @@
|
||||
"rejected": "rechazado",
|
||||
"flagged": "marcado",
|
||||
"shortcuts": "Atajos de teclado",
|
||||
"mod-faster": "Moderar más rápido con atajos del teclado",
|
||||
"try-these": "Intenta estos",
|
||||
"view-more-shortcuts": "Ver más atajos",
|
||||
"shift-key": "⇧",
|
||||
"close": "Cerrar",
|
||||
"emptyqueue": "No se encontro ningún usuario. Están escondidos.",
|
||||
"showshortcuts": "Mostrar atajos",
|
||||
|
||||
@@ -64,6 +64,8 @@ class Comment extends React.Component {
|
||||
currentUser: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired
|
||||
}),
|
||||
charCountEnable: PropTypes.bool.isRequired,
|
||||
maxCharCount: PropTypes.number,
|
||||
comment: PropTypes.shape({
|
||||
depth: PropTypes.number,
|
||||
action_summaries: PropTypes.array.isRequired,
|
||||
@@ -121,6 +123,8 @@ class Comment extends React.Component {
|
||||
ignoreUser,
|
||||
disableReply,
|
||||
commentIsIgnored,
|
||||
maxCharCount,
|
||||
charCountEnable,
|
||||
} = this.props;
|
||||
|
||||
const likeSummary = getActionSummary('LikeActionSummary', comment);
|
||||
@@ -256,6 +260,8 @@ class Comment extends React.Component {
|
||||
commentPostedHandler={() => {
|
||||
setActiveReplyBox('');
|
||||
}}
|
||||
charCountEnable={charCountEnable}
|
||||
maxCharCount={maxCharCount}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
parentId={parentId || comment.id}
|
||||
addNotification={addNotification}
|
||||
@@ -288,6 +294,8 @@ class Comment extends React.Component {
|
||||
addCommentTag={addCommentTag}
|
||||
removeCommentTag={removeCommentTag}
|
||||
ignoreUser={ignoreUser}
|
||||
charCountEnable={charCountEnable}
|
||||
maxCharCount={maxCharCount}
|
||||
showSignInDialog={showSignInDialog}
|
||||
reactKey={reply.id}
|
||||
key={reply.id}
|
||||
|
||||
@@ -91,7 +91,8 @@ class Stream extends React.Component {
|
||||
premod={asset.settings.moderation}
|
||||
isReply={false}
|
||||
authorId={user.id}
|
||||
charCount={asset.settings.charCountEnable && asset.settings.charCount} />
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount} />
|
||||
: null
|
||||
}
|
||||
</RestrictedContent>
|
||||
@@ -125,7 +126,10 @@ class Stream extends React.Component {
|
||||
key={highlightedComment.id}
|
||||
commentIsIgnored={commentIsIgnored}
|
||||
reactKey={highlightedComment.id}
|
||||
comment={highlightedComment} />
|
||||
comment={highlightedComment}
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount}
|
||||
/>
|
||||
: <div>
|
||||
<NewCount
|
||||
commentCount={asset.commentCount}
|
||||
@@ -167,6 +171,8 @@ class Stream extends React.Component {
|
||||
reactKey={comment.id}
|
||||
comment={comment}
|
||||
pluginProps={pluginProps}
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -195,7 +195,8 @@ hr {
|
||||
padding: 5px;
|
||||
min-height: 100px;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.coral-plugin-commentbox-button-container {
|
||||
|
||||
@@ -123,11 +123,11 @@ class CommentBox extends Component {
|
||||
handleChange = e => this.setState({body: e.target.value});
|
||||
|
||||
render () {
|
||||
const {styles, isReply, authorId, charCount} = this.props;
|
||||
const {styles, isReply, authorId, maxCharCount} = this.props;
|
||||
let {cancelButtonClicked} = this.props;
|
||||
|
||||
const length = this.state.body.length;
|
||||
const enablePostComment = !length || (charCount && length > charCount);
|
||||
const enablePostComment = !length || (maxCharCount && length > maxCharCount);
|
||||
|
||||
if (isReply && typeof cancelButtonClicked !== 'function') {
|
||||
console.warn('the CommentBox component should have a cancelButtonClicked callback defined if it lives in a Reply');
|
||||
@@ -152,8 +152,8 @@ class CommentBox extends Component {
|
||||
onChange={this.handleChange}
|
||||
rows={3}/>
|
||||
</div>
|
||||
<div className={`${name}-char-count ${length > charCount ? `${name}-char-max` : ''}`}>
|
||||
{charCount && `${charCount - length} ${lang.t('characters-remaining')}`}
|
||||
<div className={`${name}-char-count ${length > maxCharCount ? `${name}-char-max` : ''}`}>
|
||||
{maxCharCount && `${maxCharCount - length} ${lang.t('characters-remaining')}`}
|
||||
</div>
|
||||
<div className={`${name}-button-container`}>
|
||||
<Slot
|
||||
@@ -188,6 +188,8 @@ class CommentBox extends Component {
|
||||
}
|
||||
|
||||
CommentBox.propTypes = {
|
||||
charCountEnable: PropTypes.bool.isRequired,
|
||||
maxCharCount: PropTypes.number,
|
||||
commentPostedHandler: PropTypes.func,
|
||||
postItem: PropTypes.func.isRequired,
|
||||
cancelButtonClicked: PropTypes.func,
|
||||
|
||||
@@ -10,9 +10,22 @@ class ReplyBox extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {styles, postItem, assetId, authorId, addNotification, parentId, commentPostedHandler, setActiveReplyBox} = this.props;
|
||||
const {
|
||||
styles,
|
||||
postItem,
|
||||
assetId,
|
||||
authorId,
|
||||
addNotification,
|
||||
parentId,
|
||||
commentPostedHandler,
|
||||
setActiveReplyBox,
|
||||
maxCharCount,
|
||||
charCountEnable
|
||||
} = this.props;
|
||||
return <div className={`${name}-textarea`} style={styles && styles.container}>
|
||||
<CommentBox
|
||||
maxCharCount={maxCharCount}
|
||||
charCountEnable={charCountEnable}
|
||||
commentPostedHandler={commentPostedHandler}
|
||||
parentId={parentId}
|
||||
cancelButtonClicked={setActiveReplyBox}
|
||||
@@ -26,6 +39,8 @@ class ReplyBox extends Component {
|
||||
}
|
||||
|
||||
ReplyBox.propTypes = {
|
||||
charCountEnable: PropTypes.bool.isRequired,
|
||||
maxCharCount: PropTypes.number,
|
||||
setActiveReplyBox: PropTypes.func.isRequired,
|
||||
commentPostedHandler: PropTypes.func,
|
||||
parentId: PropTypes.string,
|
||||
|
||||
@@ -180,9 +180,9 @@ const createPublicComment = (context, commentInput) => {
|
||||
* @param {String} status the new status of the comment
|
||||
*/
|
||||
|
||||
const setCommentStatus = ({loaders: {Comments}}, {id, status}) => {
|
||||
const setCommentStatus = ({user, loaders: {Comments}}, {id, status}) => {
|
||||
return CommentsService
|
||||
.setStatus(id, status)
|
||||
.pushStatus(id, status, user ? user.id : null)
|
||||
.then((comment) => {
|
||||
|
||||
// If the loaders are present, clear the caches for these values because we
|
||||
|
||||
+9
-22
@@ -213,7 +213,15 @@ module.exports = class CommentsService {
|
||||
* @return {Promise}
|
||||
*/
|
||||
static pushStatus(id, status, assigned_by = null) {
|
||||
return CommentModel.update({id}, {
|
||||
|
||||
// Check to see if the comment status is in the allowable set of statuses.
|
||||
if (STATUSES.indexOf(status) === -1) {
|
||||
|
||||
// Comment status is not supported! Error out here.
|
||||
return Promise.reject(new Error(`status ${status} is not supported`));
|
||||
}
|
||||
|
||||
return CommentModel.findOneAndUpdate({id}, {
|
||||
$push: {
|
||||
status_history: {
|
||||
type: status,
|
||||
@@ -292,25 +300,4 @@ module.exports = class CommentsService {
|
||||
|
||||
return CommentModel.find(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Comment Status
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} status the new status of the comment
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
static setStatus(id, status) {
|
||||
|
||||
// Check to see if the comment status is in the allowable set of statuses.
|
||||
if (STATUSES.indexOf(status) === -1) {
|
||||
|
||||
// Comment status is not supported! Error out here.
|
||||
return Promise.reject(new Error(`status ${status} is not supported`));
|
||||
}
|
||||
|
||||
return CommentModel.findOneAndUpdate({id}, {
|
||||
$set: {status}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user