Merge branch 'master' into asset-comment-metrics

This commit is contained in:
riley
2017-03-09 13:20:51 -07:00
22 changed files with 414 additions and 173 deletions
@@ -8,7 +8,14 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
const onBanClick = (userId, commentId, handleBanUser, rejectComment, handleClose) => (e) => {
e.preventDefault();
handleBanUser({userId})
.then(handleClose)
.then(() => rejectComment({commentId}));
};
const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, commentId}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
@@ -29,7 +36,7 @@ const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={() => handleBanUser({userId: user.id})} raised>
<Button cStyle="black" className={styles.ban} onClick={onBanClick(user.id, commentId, handleBanUser, rejectComment, handleClose)} raised>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
@@ -90,6 +90,11 @@
.configTimeoutSelect {
display: inline-block;
margin-left: 20px;
i { /* fix for firefox and react-mdl-selectfield@0.2.0 */
padding: 20px 0;
vertical-align: top;
}
}
.charCountTexfield {
@@ -171,6 +171,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
</div>
</div>
</Card>
{/* the above card should be the last one if at all possible because of z-index issues with the selects */}
</div>
);
};
@@ -7,3 +7,28 @@
font-size: 1.5rem;
font-weight: bold;
}
.autoUpdate {
background-color: #d5d5d5;
padding: 3px 10px 10px 10px;
margin-bottom: 0;
i {
position: relative;
top: 7px;
}
b {
float: right;
border-radius: 20px;
cursor: pointer;
background-color: #c0c0c0;
width: 30px;
height: 30px;
text-align: center;
top: 4px;
position: relative;
line-height: 1.7em;
font-size: 1.3em;
}
}
@@ -6,11 +6,41 @@ import {getMetrics} from 'coral-admin/src/graphql/queries';
import FlagWidget from './FlagWidget';
import LikeWidget from './LikeWidget';
import {showBanUserDialog, hideBanUserDialog} from 'coral-admin/src/actions/moderation';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import {Spinner, Icon} from 'coral-ui';
import {Spinner} from 'coral-ui';
const lang = new I18n(translations);
const refreshIntervalSeconds = 60 * 5;
class Dashboard extends React.Component {
state = {
noteHidden: false,
secondsUntilRefresh: refreshIntervalSeconds
}
componentWillMount () {
setInterval(() => { // the countdown timer
let nextCount = this.state.secondsUntilRefresh - 1;
if (nextCount < 0) {
nextCount = refreshIntervalSeconds;
this.props.data.refetch();
}
this.setState({secondsUntilRefresh: nextCount});
}, 1000);
}
formatTime = () => {
const minutes = Math.floor(this.state.secondsUntilRefresh / 60);
let seconds = (this.state.secondsUntilRefresh % 60).toString();
if (seconds.length < 2) {
seconds = `0${seconds}`;
}
return `${minutes}:${seconds}`;
}
render () {
if (this.props.data && this.props.data.loading) {
@@ -20,9 +50,18 @@ class Dashboard extends React.Component {
const {data: {assetsByLike, assetsByFlag}} = this.props;
return (
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<LikeWidget assets={assetsByLike} />
<div>
<p
style={{display: this.state.noteHidden ? 'none' : 'block'}}
className={styles.autoUpdate}
onClick={() => this.setState({noteHidden: true})}>
<b>×</b>
<Icon name='timer' /> <strong>{lang.t('dashboard.next-update', this.formatTime())}</strong> {lang.t('dashboard.auto-update')}
</p>
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<LikeWidget assets={assetsByLike} />
</div>
</div>
);
}
@@ -12,43 +12,29 @@ const FlagWidget = (props) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the most flags</h2>
<table className={styles.widgetTable}>
<thead className={styles.widgetHead}>
<tr>
<th>{lang.t('streams.article')}</th>
<th colSpan='2'>{lang.t('dashboard.flags')}</th>
</tr>
</thead>
<tbody>
{
assets.length
? assets.map(asset => {
const flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary');
return (
<tr className={styles.rowLinkify} key={asset.id}>
<td>
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</Link>
</td>
<td>
<p className={styles.widgetCount}>{flagSummary ? flagSummary.actionCount : 0}</p>
</td>
<td>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
</td>
</tr>
);
})
: <tr className={styles.rowLinkify}><td colSpan="3">{lang.t('dashboard.no_flags')}</td></tr>
}
{ /* rows in a table with a fixed height will expand and ignore height.
this extra row will expand to fill the extra space. */
assets.length < 10 ? <tr></tr> : null
}
</tbody>
</table>
<div className={styles.widgetHead}>
<p>{lang.t('streams.article')}</p>
<p>{lang.t('dashboard.flags')}</p>
</div>
<div className={styles.widgetTable}>
{
assets.length
? assets.map(asset => {
const flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary');
return (
<div className={styles.rowLinkify} key={asset.id}>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
<p className={styles.widgetCount}>{flagSummary ? flagSummary.actionCount : 0}</p>
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
</Link>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</div>
);
})
: <div className={styles.rowLinkify}>{lang.t('dashboard.no_flags')}</div>
}
</div>
</div>
);
};
@@ -13,43 +13,29 @@ const LikeWidget = (props) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the most likes</h2>
<table className={styles.widgetTable}>
<thead className={styles.widgetHead}>
<tr>
<th>{lang.t('streams.article')}</th>
<th colSpan='2'>{lang.t('modqueue.likes')}</th>
</tr>
</thead>
<tbody>
{
assets.length
? assets.map(asset => {
const likeSummary = asset.action_summaries.find(s => s.type === 'LikeAssetActionSummary');
return (
<tr className={styles.rowLinkify} key={asset.id}>
<td>
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</Link>
</td>
<td>
<p className={styles.widgetCount}>{likeSummary ? likeSummary.actionCount : 0}</p>
</td>
<td>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
</td>
</tr>
);
})
: <tr className={styles.rowLinkify}><td colSpan="3">{lang.t('dashboard.no_likes')}</td></tr>
}
{ /* rows in a table with a fixed height will expand and ignore height.
this extra row will expand to fill the extra space. */
assets.length < 10 ? <tr></tr> : null
}
</tbody>
</table>
<div className={styles.widgetHead}>
<p>{lang.t('streams.article')}</p>
<p>{lang.t('modqueue.likes')}</p>
</div>
<div className={styles.widgetTable}>
{
assets.length
? assets.map(asset => {
const likeSummary = asset.action_summaries.find(s => s.type === 'LikeAssetActionSummary');
return (
<div className={styles.rowLinkify} key={asset.id}>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
<p className={styles.widgetCount}>{likeSummary ? likeSummary.actionCount : 0}</p>
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
</Link>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</div>
);
})
: <div className={styles.rowLinkify}>{lang.t('dashboard.no_likes')}</div>
}
</div>
</div>
);
};
@@ -1,14 +1,17 @@
:root {
--row-height: 80px;
--row-height: 60px;
}
.widget {
box-sizing: border-box;
margin: 10px 5px 5px 5px;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
padding: 15px;
flex: 1;
background-color: white;
box-sizing: border-box;
}
.widget * {
box-sizing: border-box;
}
.heading {
@@ -16,24 +19,32 @@
padding-left: 10px;
font-size: 1.5rem;
font-weight: bold;
background-color: #e0e0e0;
}
.widgetTable {
width: 100%;
border-collapse: collapse;
user-select: none;
height: calc(var(--row-height) * 10);
}
.widgetTable thead th {
.widgetTable + div:after {
content: '';
clear: both;
display: block;
}
.widgetHead p {
color: rgb(35, 102, 223);
padding: 10px;
text-align: left;
text-transform: capitalize;
display: inline-block;
box-sizing: border-box;
margin-bottom: 0;
}
.widgetTable thead th:last-child {
width: 10%;
.widgetHead p:last-child {
float: right;
margin-right: 100px;
}
.rowLinkify {
@@ -41,6 +52,7 @@
border-bottom: 1px solid lightgrey;
color: #555;
height: var(--row-height);
padding: 10px;
}
.rowLinkify:last-child {
@@ -51,16 +63,8 @@
background-color: #f8f8f8;
}
.widgetTable tbody td {
padding: 10px;
}
.widgetTable tbody td:last-child {
padding-top: 0;
}
.linkToAsset {
display: block;
display: inline-block;
text-decoration: none;
}
@@ -69,6 +73,8 @@
padding: 10px 14px;
text-decoration: none;
color: black;
float: right;
margin-left: 15px;
}
.linkToModerate:hover {
@@ -96,4 +102,6 @@
color: #555;
font-size: 1.3em;
font-weight: 400;
float: right;
margin-top: 7px;
}
@@ -21,12 +21,13 @@ import ModerationKeysModal from '../../components/ModerationKeysModal';
class ModerationContainer extends Component {
state = {
selectedIndex: 0
selectedIndex: 0,
sort: 'REVERSE_CHRONOLOGICAL'
}
componentWillMount() {
const {toggleModal, singleView} = this.props;
this.props.fetchSettings();
key('s', () => singleView());
key('shift+/', () => toggleModal(true));
@@ -74,6 +75,11 @@ class ModerationContainer extends Component {
}
}
selectSort = (sort) => {
this.setState({sort});
this.props.modQueueResort(sort);
}
componentWillUnmount() {
key.unbind('s');
key.unbind('shift+/');
@@ -92,7 +98,7 @@ class ModerationContainer extends Component {
}
render () {
const {data, moderation, settings, assets, modQueueResort, onClose, ...props} = this.props;
const {data, moderation, settings, assets, onClose, ...props} = this.props;
const providedAssetId = this.props.params.id;
const activeTab = this.props.route.path === ':id' ? 'premod' : this.props.route.path;
@@ -115,6 +121,18 @@ class ModerationContainer extends Component {
}
const comments = data[activeTab];
let activeTabCount;
switch(activeTab) {
case 'premod':
activeTabCount = data.premodCount;
break;
case 'flagged':
activeTabCount = data.flaggedCount;
break;
case 'rejected':
activeTabCount = data.rejectedCount;
break;
}
return (
<div>
@@ -124,7 +142,8 @@ class ModerationContainer extends Component {
premodCount={data.premodCount}
rejectedCount={data.rejectedCount}
flaggedCount={data.flaggedCount}
modQueueResort={modQueueResort}
selectSort={this.selectSort}
sort={this.state.sort}
/>
<ModerationQueue
currentAsset={asset}
@@ -136,12 +155,18 @@ class ModerationContainer extends Component {
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
loadMore={props.loadMore}
assetId={providedAssetId}
sort={this.state.sort}
commentCount={activeTabCount}
/>
<BanUserDialog
open={moderation.banDialog}
user={moderation.user}
commentId={moderation.commentId}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
rejectComment={props.rejectComment}
/>
<ModerationKeysModal
open={moderation.modalOpen}
@@ -6,9 +6,10 @@ import EmptyCard from '../../components/EmptyCard';
import {actionsMap} from './helpers/moderationQueueActionsMap';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import LoadMore from './components/LoadMore';
const lang = new I18n(translations);
const ModerationQueue = ({comments, selectedIndex, singleView, ...props}) => {
const ModerationQueue = ({comments, selectedIndex, commentCount, singleView, loadMore, activeTab, sort, ...props}) => {
return (
<div id="moderationList" className={`${styles.list} ${singleView ? styles.singleView : ''}`}>
<ul style={{paddingLeft: 0}}>
@@ -20,7 +21,7 @@ const ModerationQueue = ({comments, selectedIndex, singleView, ...props}) => {
key={i}
index={i}
comment={comment}
commentType={props.activeTab}
commentType={activeTab}
selected={i === selectedIndex}
suspectWords={props.suspectWords}
actions={actionsMap[status]}
@@ -33,6 +34,14 @@ const ModerationQueue = ({comments, selectedIndex, singleView, ...props}) => {
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
}
</ul>
<LoadMore
comments={comments}
loadMore={loadMore}
sort={sort}
tab={activeTab}
showLoadMore={comments.length < commentCount}
assetId={props.assetId}
/>
</div>
);
};
@@ -0,0 +1,31 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
import styles from './styles.css';
const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) =>
<div className={styles.loadMoreContainer}>
{
showLoadMore && <Button
className={styles.loadMore}
onClick={() =>
loadMore({
cursor: comments[comments.length - 1].created_at,
sort,
tab,
asset_id: assetId
})}>
Load More
</Button>
}
</div>;
LoadMore.propTypes = {
comments: PropTypes.array.isRequired,
loadMore: PropTypes.func.isRequired,
sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged']).isRequired,
assetId: PropTypes.string,
showLoadMore: PropTypes.bool.isRequired
};
export default LoadMore;
@@ -10,9 +10,9 @@ const ModerationHeader = props => (
props.asset ?
<div className={`mdl-tabs__tab-bar ${styles.moderateAsset}`}>
<Link className="mdl-tabs__tab" to="/admin/moderate">All Streams</Link>
<a className="mdl-tabs__tab">
{props.asset.title}
<a href={props.asset.url} className={styles.settingsButton}><Icon name="settings"/></a>
<a className="mdl-tabs__tab" href={props.asset.url}>
<span>{props.asset.title}</span>
<Icon className={styles.settingsButton} name="open_in_new"/>
</a>
<Link className="mdl-tabs__tab" to="/admin/streams">Select Stream</Link>
</div>
@@ -1,4 +1,4 @@
import React, {PropTypes, Component} from 'react';
import React, {PropTypes} from 'react';
import CommentCount from './CommentCount';
import styles from './styles.css';
import {SelectField, Option} from 'react-mdl-selectfield';
@@ -8,57 +8,45 @@ import {Link} from 'react-router';
const lang = new I18n(translations);
class ModerationMenu extends Component {
state = {
sort: 'REVERSE_CHRONOLOGICAL',
}
static propTypes = {
premodCount: PropTypes.number.isRequired,
rejectedCount: PropTypes.number.isRequired,
flaggedCount: PropTypes.number.isRequired,
asset: PropTypes.shape({
id: PropTypes.string
})
}
selectSort = (sort) => {
this.setState({sort});
this.props.modQueueResort(sort);
}
render() {
const {asset, premodCount, rejectedCount, flaggedCount} = this.props;
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';
return (
<div className='mdl-tabs'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div className={styles.tabBarPadding}/>
<div>
<Link to={premodPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.premod')} <CommentCount count={premodCount} />
</Link>
<Link to={rejectPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
</Link>
<Link to={flagPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
</Link>
</div>
<SelectField
className={styles.selectField}
label='Sort'
value={this.state.sort}
onChange={sort => this.selectSort(sort)}>
<Option value={'REVERSE_CHRONOLOGICAL'}>Newest First</Option>
<Option value={'CHRONOLOGICAL'}>Oldest First</Option>
</SelectField>
const ModerationMenu = ({asset, 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';
return (
<div className='mdl-tabs'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div className={styles.tabBarPadding}/>
<div>
<Link to={premodPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.premod')} <CommentCount count={premodCount} />
</Link>
<Link to={rejectPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
</Link>
<Link to={flagPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
</Link>
</div>
<SelectField
className={styles.selectField}
label='Sort'
value={sort}
onChange={sort => selectSort(sort)}>
<Option value={'REVERSE_CHRONOLOGICAL'}>Newest First</Option>
<Option value={'CHRONOLOGICAL'}>Oldest First</Option>
</SelectField>
</div>
);
}
}
</div>
);
};
ModerationMenu.propTypes = {
premodCount: PropTypes.number.isRequired,
rejectedCount: PropTypes.number.isRequired,
flaggedCount: PropTypes.number.isRequired,
asset: PropTypes.shape({
id: PropTypes.string
})
};
export default ModerationMenu;
@@ -84,11 +84,10 @@ span {
margin-bottom: -1px;
.settingsButton {
i {
vertical-align: middle;
margin-left: 10px;
margin-top: -4px;
}
vertical-align: middle;
margin-left: 10px;
margin-top: -4px;
font-size: 16px;
}
.moderateAsset {
@@ -114,7 +113,15 @@ span {
}
&:nth-child(2) {
text-align: center;
span {
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 344px;
display: inline-block;
vertical-align: top;
}
}
&:last-child {
@@ -387,3 +394,23 @@ span {
cursor: pointer;
}
}
.loadMoreContainer {
display: flex;
justify-content: center;
width: 100%;
};
.loadMore {
width: 100%;
text-align: center;
color: #FFF;
max-width: 660px;
margin-bottom: 30px;
background-color: #2376D8;
cursor: pointer;
}
.loadMore:hover {
background-color: #4399FF;
}
@@ -80,8 +80,8 @@ class Streams extends Component {
<div
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
{closed ? lang.t('streams.closed') : lang.t('streams.open')}
{!statusMenuOpen && <Icon className={styles.statusMenuIcon} name='keyboard_arrow_down'/>}
{closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
{
statusMenuOpen &&
@@ -22,7 +22,26 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
commentId,
status: 'ACCEPTED'
},
refetchQueries: ['ModQueue']
updateQueries: {
ModQueue: (oldData) => {
const premod = oldData.premod.filter(c => c.id !== commentId);
const flagged = oldData.flagged.filter(c => c.id !== commentId);
const rejected = oldData.rejected.filter(c => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount;
return {
...oldData,
premodCount,
flaggedCount,
rejectedCount,
premod,
flagged,
rejected,
};
}
}
});
},
rejectComment: ({commentId}) => {
@@ -31,7 +50,27 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
commentId,
status: 'REJECTED'
},
refetchQueries: ['ModQueue']
updateQueries: {
ModQueue: (oldData) => {
const comment = oldData.premod.concat(oldData.flagged).filter(c => c.id === commentId)[0];
const rejected = [comment].concat(oldData.rejected);
const premod = oldData.premod.filter(c => c.id !== commentId);
const flagged = oldData.flagged.filter(c => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = oldData.rejectedCount + 1;
return {
...oldData,
premodCount,
flaggedCount,
rejectedCount,
premod,
flagged,
rejected
};
}
}
});
}
})
@@ -1,6 +1,7 @@
import {graphql} from 'react-apollo';
import MOD_QUEUE_QUERY from './modQueueQuery.graphql';
import MOD_QUEUE_LOAD_MORE from './loadMore.graphql';
import METRICS from './metricsQuery.graphql';
export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
@@ -14,7 +15,8 @@ export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
},
props: ({ownProps: {params: {id = null}}, data}) => ({
data,
modQueueResort: modQueueResort(id, data.fetchMore)
modQueueResort: modQueueResort(id, data.fetchMore),
loadMore: loadMore(data.fetchMore)
})
});
@@ -30,6 +32,40 @@ export const getMetrics = graphql(METRICS, {
}
});
export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) => {
let statuses;
switch(tab) {
case 'premod':
statuses = ['PREMOD'];
break;
case 'flagged':
statuses = ['NONE', 'PREMOD'];
break;
case 'rejected':
statuses = ['REJECTED'];
break;
}
return fetchMore({
query: MOD_QUEUE_LOAD_MORE,
variables: {
limit,
cursor,
sort,
statuses,
asset_id
},
updateQuery: (oldData, {fetchMoreResult:{data:{comments}}}) => {
return {
...oldData,
[tab]: [
...oldData[tab],
...comments
]
};
}
});
};
export const modQueueResort = (id, fetchMore) => (sort) => {
return fetchMore({
query: MOD_QUEUE_QUERY,
@@ -0,0 +1,13 @@
#import "../fragments/commentView.graphql"
query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!]) {
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort}) {
...commentView
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
+4
View File
@@ -117,6 +117,8 @@
"write_message": "Write a message"
},
"dashboard": {
"next-update": "{0} minutes until next update.",
"auto-update": "Data automatically updates every five minutes or when you Reload.",
"no_flags": "There have been no flags in the last 5 minutes! Hooray!",
"no_likes": "There have been no likes in the last 5 minutes. All quiet.",
"flags": "Flags",
@@ -235,6 +237,8 @@
"yes_ban_user": "Si, Suspendan el usuario"
},
"dashbord": {
"next-update": "{0} minutos hasta la siguiente actualización.",
"auto-update": "Los datos se actualizan automaticamente cada 5 minutos o cuando Recargas.",
"no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!",
"no_likes": "A nadie le ha gustado algún comentario en los últimos 5 minutos. Todo tranquilo.",
"flags": "Marcados",
+1 -1
View File
@@ -256,7 +256,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
requestConfirmEmail: () => dispatch(requestConfirmEmail()),
loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)),
addNotification: (type, text) => dispatch(addNotification(type, text)),
addNotification: (type, text) => addNotification(type, text),
clearNotification: () => dispatch(clearNotification()),
editName: (username) => dispatch(editName(username)),
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
+15 -3
View File
@@ -32,18 +32,30 @@ class FlagButton extends Component {
if (flagged) {
this.setState((prev) => prev.localPost ? {...prev, localPost: null, step: 0} : {...prev, localDelete: true});
deleteAction(localPost || flag.current_user.id);
} else if (this.state.showMenu){
this.closeMenu();
} else {
this.setState({showMenu: !this.state.showMenu});
this.setState({showMenu: true});
}
}
closeMenu = () => {
this.setState({
showMenu: false,
itemType: '',
reason: '',
message: '',
step: 0
});
}
onPopupContinue = () => {
const {postFlag, postDontAgree, id, author_id} = this.props;
const {itemType, reason, step, posted, message} = this.state;
// 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});
this.closeMenu();
} else {
this.setState({step: step + 1});
}
@@ -114,7 +126,7 @@ class FlagButton extends Component {
}
handleClickOutside () {
this.setState({showMenu: false});
this.closeMenu();
}
render () {
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react';
import {Icon as IconMDL} from 'react-mdl';
const Icon = ({className, name}) => (
const Icon = ({className = '', name}) => (
<IconMDL className={className} name={name} />
);