Compare commits

...
27 Commits
Author SHA1 Message Date
David Erwin 8b4c2876c7 Merge pull request #538 from coralproject/ios-safari-full-width
Safari IOS ignores width=100% in iframes
2017-04-26 09:44:01 -04:00
Chi Vinh Le 9ff2ccf1ed Safari IOS ignores width=100% in iframes 2017-04-26 18:52:30 +07:00
Kim Gardner 2c2c2dc604 Merge pull request #502 from coralproject/all-comments-queue
All comments queue
2017-04-25 18:20:36 -04:00
Kim Gardner 94e429e431 Merge branch 'master' into all-comments-queue 2017-04-25 18:16:01 -04:00
David Erwin 7d0aac5d4a Merge pull request #532 from coralproject/fix-view-more
Fix: View More working with asset_id
2017-04-25 16:01:38 -04:00
David Erwin d4c4b5a3e9 Merge branch 'master' into fix-view-more 2017-04-25 15:26:20 -04:00
Riley Davis b88cbe30de have all the comments show up in All Queue with proper statuses 2017-04-25 13:23:39 -06:00
Belen Curcio 9c448d5143 View More working with asset_id 2017-04-25 16:17:10 -03:00
riley 67301e6e85 Merge branch 'comment-status-display' into all-comments-queue 2017-04-25 12:52:32 -06:00
riley ad4386b235 Merge branch 'all-comments-queue' into comment-status-display 2017-04-25 12:51:20 -06:00
Riley Davis 5666c4ac90 condense some var definitions 2017-04-25 11:57:36 -06:00
Riley Davis 37ac5157dd Merge branch 'master' into comment-status-display 2017-04-25 11:45:27 -06:00
Riley Davis 5db9ea6520 Merge pull request #520 from coralproject/shortcut-hint
add a little keyboard shortcut hint widget
2017-04-25 11:44:18 -06:00
Belén Curcio a4b9f86e29 Merge branch 'master' into shortcut-hint 2017-04-25 14:38:47 -03:00
Riley Davis cee1c5bc1c have active state on action buttons depending on comment status 2017-04-25 11:21:28 -06:00
riley 19a83a902a Merge branch 'master' into all-comments-queue 2017-04-24 15:15:10 -06:00
Riley Davis b71b7cc883 move the shortcuts note logic into redux state 2017-04-24 14:04:57 -06:00
Riley Davis aaa5d17ca3 Merge branch 'master' into shortcut-hint 2017-04-24 12:34:44 -06:00
Kim Gardner 860f434335 Merge branch 'master' into all-comments-queue 2017-04-24 13:45:02 -04:00
Riley Davis 2743f6faf6 return all the known types 2017-04-21 16:44:42 -06:00
Gabriela Rodríguez Berón a41108a53c Merge branch 'master' into shortcut-hint 2017-04-21 15:10:42 -07:00
Gabriela Rodríguez Berón e9c81f0639 Merge branch 'master' into all-comments-queue 2017-04-21 14:31:16 -07:00
Riley Davis d1ab279f74 add a little keyboard shortcut widget 2017-04-21 13:26:32 -06:00
Riley Davis f20f0c5d10 Merge branch 'master' into all-comments-queue 2017-04-21 09:05:38 -06:00
Riley Davis 673bbb61b1 Merge branch 'master' into all-comments-queue 2017-04-17 15:41:30 -06:00
riley 449a6f67f1 add some tab icons 2017-04-17 15:37:00 -06:00
Riley Davis df859dc782 add all tab 2017-04-17 14:12:45 -06:00
21 changed files with 210 additions and 54 deletions
+3
View File
@@ -39,6 +39,9 @@ const routes = (
{/* Moderation Routes */}
<Route path='moderate' component={ModerationLayout}>
<Route path='all' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
<Route path='premod' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
@@ -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};
};
@@ -1,17 +1,24 @@
import React from 'react';
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap';
const ActionButton = ({type = '', ...props}) => {
const ActionButton = ({type = '', status, ...props}) => {
const typeName = type.toLowerCase();
const active = ((type === 'REJECT' && status === 'REJECTED') || (type === 'APPROVE' && status === 'ACCEPTED'));
return (
<Button
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
className={`${typeName} ${styles.actionButton} ${active ? styles[`${typeName}__active`] : ''}`}
cStyle={typeName}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
>{menuActionsMap[type].text}</Button>
);
};
ActionButton.propTypes = {
status: PropTypes.string
};
export default ActionButton;
@@ -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>
);
}
}
@@ -188,3 +188,15 @@
margin: 0;
width: 140px;
}
.approve__active {
box-shadow: none;
color: white;
background-color: #519954;
}
.reject__active, .rejected__active {
color: white;
background-color: #D03235;
box-shadow: none;
}
@@ -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';
@@ -135,6 +135,9 @@ class ModerationContainer extends Component {
const comments = data[activeTab];
let activeTabCount;
switch(activeTab) {
case 'all':
activeTabCount = data.allCount;
break;
case 'premod':
activeTabCount = data.premodCount;
break;
@@ -151,6 +154,7 @@ class ModerationContainer extends Component {
<ModerationHeader asset={asset} />
<ModerationMenu
asset={asset}
allCount={data.allCount}
premodCount={data.premodCount}
rejectedCount={data.rejectedCount}
flaggedCount={data.flaggedCount}
@@ -183,6 +187,8 @@ class ModerationContainer extends Component {
rejectComment={props.rejectComment}
/>
<ModerationKeysModal
hideShortcutsNote={props.hideShortcutsNote}
shortcutsNoteVisible={moderation.shortcutsNoteVisible}
open={moderation.modalOpen}
onClose={onClose}/>
</div>
@@ -204,6 +210,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(
@@ -21,7 +21,6 @@ const ModerationQueue = ({comments, selectedIndex, commentCount, singleView, loa
key={i}
index={i}
comment={comment}
commentType={activeTab}
selected={i === selectedIndex}
suspectWords={props.suspectWords}
bannedWords={props.bannedWords}
@@ -23,6 +23,12 @@ const Comment = ({actions = [], comment, ...props}) => {
const linkText = links ? links.map(link => link.raw) : [];
const flagActionSummaries = getActionSummary('FlagActionSummary', comment);
const flagActions = comment.actions && comment.actions.filter(a => a.__typename === 'FlagAction');
let commentType = '';
if (comment.status === 'PREMOD') {
commentType = 'premod';
} else if (flagActions && flagActions.length) {
commentType = 'flagged';
}
return (
<li tabIndex={props.index} className={`mdl-card ${props.selected ? 'mdl-shadow--8dp' : 'mdl-shadow--2dp'} ${styles.Comment} ${styles.listItem}`}>
@@ -36,7 +42,7 @@ const Comment = ({actions = [], comment, ...props}) => {
{timeago().format(comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
</span>
<BanUserButton user={comment.user} onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status !== 'REJECTED')} />
<CommentType type={props.commentType} />
<CommentType type={commentType} />
</div>
{comment.user.status === 'banned' ?
<span className={styles.banned}>
@@ -64,6 +70,7 @@ const Comment = ({actions = [], comment, ...props}) => {
<ActionButton key={i}
type={action}
user={comment.user}
status={comment.status}
acceptComment={() => props.acceptComment({commentId: comment.id})}
rejectComment={() => props.rejectComment({commentId: comment.id})}
/>
@@ -23,7 +23,7 @@ LoadMore.propTypes = {
comments: PropTypes.array.isRequired,
loadMore: PropTypes.func.isRequired,
sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged', 'all']).isRequired,
assetId: PropTypes.string,
showLoadMore: PropTypes.bool.isRequired
};
@@ -4,22 +4,18 @@ import styles from './styles.css';
import {SelectField, Option} from 'react-mdl-selectfield';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
import {Icon} from 'coral-ui';
import {Link} from 'react-router';
const lang = new I18n(translations);
const ModerationMenu = (
{asset, premodCount, rejectedCount, flaggedCount, selectSort, sort}
{asset, allCount, premodCount, rejectedCount, flaggedCount, selectSort, sort}
) => {
const premodPath = asset
? `/admin/moderate/premod/${asset.id}`
: '/admin/moderate/premod';
const rejectPath = asset
? `/admin/moderate/rejected/${asset.id}`
: '/admin/moderate/rejected';
const flagPath = asset
? `/admin/moderate/flagged/${asset.id}`
: '/admin/moderate/flagged';
function getPath (type) {
return asset ? `/admin/moderate/${type}/${asset.id}` : `/admin/moderate/${type}`;
}
return (
<div className="mdl-tabs">
@@ -27,22 +23,28 @@ const ModerationMenu = (
<div className={styles.tabBarPadding} />
<div>
<Link
to={premodPath}
to={getPath('all')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.premod')} <CommentCount count={premodCount} />
<Icon name='question_answer' className={styles.tabIcon} /> {lang.t('modqueue.all')} <CommentCount count={allCount} />
</Link>
<Link
to={flagPath}
to={getPath('premod')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
<Icon name='access_time' className={styles.tabIcon} /> {lang.t('modqueue.premod')} <CommentCount count={premodCount} />
</Link>
<Link
to={rejectPath}
to={getPath('flagged')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
<Icon name='flag' className={styles.tabIcon} /> {lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
</Link>
<Link
to={getPath('rejected')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
<Icon name='close' className={styles.tabIcon} /> {lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
</Link>
</div>
<SelectField
@@ -59,6 +61,7 @@ const ModerationMenu = (
};
ModerationMenu.propTypes = {
allCount: PropTypes.number.isRequired,
premodCount: PropTypes.number.isRequired,
rejectedCount: PropTypes.number.isRequired,
flaggedCount: PropTypes.number.isRequired,
@@ -418,3 +418,8 @@ span {
.loadMore:hover {
background-color: #4399FF;
}
.tabIcon {
position: relative;
top: 7px;
}
@@ -36,6 +36,9 @@ export const getMetrics = graphql(METRICS, {
export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) => {
let statuses;
switch(tab) {
case 'all':
statuses = null;
break;
case 'premod':
statuses = ['PREMOD'];
break;
@@ -1,6 +1,13 @@
#import "../fragments/commentView.graphql"
query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
all: comments(query: {
statuses: [NONE, PREMOD, ACCEPTED, REJECTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
premod: comments(query: {
statuses: [PREMOD],
asset_id: $asset_id,
@@ -28,6 +35,9 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
title
url
}
allCount: commentCount(query: {
asset_id: $asset_id
})
premodCount: commentCount(query: {
statuses: [PREMOD],
asset_id: $asset_id
@@ -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;
}
+8
View File
@@ -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",
+1 -1
View File
@@ -7,9 +7,9 @@ const onLoadMoreClick = ({loadMore, commentCount, firstCommentDate, assetId, upd
e.preventDefault();
updateCountCache(assetId, commentCount);
loadMore({
asset_id: assetId,
limit: 500,
cursor: firstCommentDate,
assetId,
sort: 'CHRONOLOGICAL'
}, true);
};
+4
View File
@@ -57,6 +57,10 @@ function configurePymParent(pymParent) {
window.document.body.appendChild(snackbar);
// Workaround: IOS Safari ignores `width` but respects `min-width` value.
pymParent.el.firstChild.style.width = '1px';
pymParent.el.firstChild.style.minWidth = '100%';
// Resize parent iframe height when child height changes
pymParent.onMessage('height', function(height) {
if (height !== cachedHeight) {
+1 -1
View File
@@ -149,7 +149,7 @@
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.03), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.09);
width: 128px;
&:hover {
&:hover {
color: white;
background-color: #D03235;
box-shadow: none;
+5 -1
View File
@@ -1,8 +1,12 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Icon as IconMDL} from 'react-mdl';
const Icon = ({className = '', name}) => (
<IconMDL className={className} name={name} />
);
Icon.propTypes = {
name: PropTypes.string.isRequired
};
export default Icon;