diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js
index a3ff2ca3e..18e31091f 100644
--- a/client/coral-admin/src/actions/moderation.js
+++ b/client/coral-admin/src/actions/moderation.js
@@ -34,3 +34,8 @@ export const storySearchChange = (value) => ({
export const clearState = () => ({
type: actions.MODERATION_CLEAR_STATE
});
+
+export const selectCommentId = (id) => ({
+ type: actions.MODERATION_SELECT_COMMENT,
+ id,
+});
diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js
index cae13947d..8eb939d76 100644
--- a/client/coral-admin/src/constants/moderation.js
+++ b/client/coral-admin/src/constants/moderation.js
@@ -6,3 +6,4 @@ export const SHOW_STORY_SEARCH = 'SHOW_STORY_SEARCH';
export const HIDE_STORY_SEARCH = 'HIDE_STORY_SEARCH';
export const STORY_SEARCH_CHANGE_VALUE = 'STORY_SEARCH_CHANGE_VALUE';
export const MODERATION_CLEAR_STATE = 'MODERATION_CLEAR_STATE';
+export const MODERATION_SELECT_COMMENT = 'MODERATION_SELECT_COMMENT';
diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js
index 43ae81573..6a64b380a 100644
--- a/client/coral-admin/src/reducers/moderation.js
+++ b/client/coral-admin/src/reducers/moderation.js
@@ -7,6 +7,7 @@ const initialState = {
storySearchString: '',
shortcutsNoteVisible: 'show',
sortOrder: 'DESC',
+ selectedCommentId: '',
};
export default function moderation (state = initialState, action) {
@@ -51,6 +52,11 @@ export default function moderation (state = initialState, action) {
...state,
sortOrder: action.order,
};
+ case actions.MODERATION_SELECT_COMMENT:
+ return {
+ ...state,
+ selectedCommentId: action.id,
+ };
default:
return state;
}
diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css
index 029a8e5d1..708761b95 100644
--- a/client/coral-admin/src/routes/Moderation/components/Comment.css
+++ b/client/coral-admin/src/routes/Moderation/components/Comment.css
@@ -12,6 +12,7 @@
padding: 10px 0;
margin-top: 13px;
min-height: 0;
+ outline: 0;
/*
Fix rendering issues in Safari by promoting this
diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js
index 9f913695a..4c08f1559 100644
--- a/client/coral-admin/src/routes/Moderation/components/Comment.js
+++ b/client/coral-admin/src/routes/Moderation/components/Comment.js
@@ -20,6 +20,16 @@ import t, {timeago} from 'coral-framework/services/i18n';
class Comment extends React.Component {
+ ref = null;
+
+ handleRef = (ref) => this.ref = ref;
+
+ handleFocusOrClick = () => {
+ if (!this.props.selected) {
+ this.props.selectComment();
+ }
+ };
+
showSuspendUserDialog = () => {
const {comment, showSuspendUserDialog} = this.props;
return showSuspendUserDialog({
@@ -55,6 +65,12 @@ class Comment extends React.Component {
: this.props.rejectComment({commentId: this.props.comment.id})
);
+ componentDidUpdate(prev) {
+ if (!prev.selected && this.props.selected) {
+ this.ref.focus();
+ }
+ }
+
render() {
const {
comment,
@@ -76,6 +92,9 @@ class Comment extends React.Component {
tabIndex={0}
className={cn(className, 'mdl-card', selectionStateCSS, styles.root, {[styles.selected]: selected}, 'talk-admin-moderate-comment')}
id={`comment_${comment.id}`}
+ onClick={this.handleFocusOrClick}
+ ref={this.handleRef}
+ onFocus={this.handleFocusOrClick}
>
@@ -190,7 +209,9 @@ class Comment extends React.Component {
Comment.propTypes = {
viewUserDetail: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
+ selectComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
+ onClick: PropTypes.func,
className: PropTypes.string,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js
index 392db36e3..594d5d57f 100644
--- a/client/coral-admin/src/routes/Moderation/components/Moderation.js
+++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js
@@ -12,15 +12,8 @@ import Slot from 'coral-framework/components/Slot';
import ViewOptions from './ViewOptions';
class Moderation extends Component {
- constructor(props) {
- super(props);
- const comments = this.getComments(props);
- this.state = {
- selectedCommentId: comments[0] ? comments[0].id : null,
- };
-
- }
+ state = {};
componentWillMount() {
const {toggleModal, singleView} = this.props;
@@ -30,8 +23,6 @@ class Moderation extends Component {
key('esc', () => toggleModal(false));
key('ctrl+f', () => this.openSearch());
key('t', () => this.nextQueue());
- key('j', () => this.select(true));
- key('k', () => this.select(false));
key('f', () => this.moderate(false));
key('d', () => this.moderate(true));
this.getMenuItems()
@@ -96,64 +87,6 @@ class Moderation extends Component {
return root[activeTab].nodes;
}
- scrollTo = (toId, smooth = true) =>
- document.querySelector(`#comment_${toId}`).scrollIntoView(smooth ? {behavior: 'smooth'} : {});
-
- select = async (next, props = this.props, selectedCommentId = this.state.selectedCommentId) => {
- const comments = this.getComments(props);
-
- // No comments to be selected.
- if (comments.length === 0){
- return;
- }
-
- // Find current index if we have a selected comment.
- const index = selectedCommentId
- ? comments.findIndex((comment) => comment.id === selectedCommentId)
- : null;
-
- if (next) {
-
- // Grab first one if we don't have a selected comment yet.
- if (!selectedCommentId) {
- this.setState({selectedCommentId: comments[0].id}, () => this.scrollTo(comments[0].id));
- return;
- }
-
- // Select next one when we still have more comments left.
- if (index < comments.length - 1) {
- this.setState({selectedCommentId: comments[index + 1].id}, () => this.scrollTo(comments[index + 1].id));
- return;
- } else {
-
- // We hit the end of the list, load more comments if we have.
- if (comments.length < this.getActiveTabCount()) {
- const res = await this.loadMore();
-
- // If `loadMore` was already in progress, res would be false.
- if (res) {
-
- // Select next comment after loading has completed.
- this.select(true);
- }
- }
- return;
- }
- } else {
-
- // We have no selected comment, so just skip it.
- if (!selectedCommentId) {
- return;
- }
-
- // If we still have previous comments take the one before.
- if (index > 0) {
- this.setState({selectedCommentId: comments[index - 1].id}, () => this.scrollTo(comments[index - 1].id));
- return;
- }
- }
- }
-
loadMore = async () => {
if (!this.isLoadingMore) {
this.isLoadingMore = true;
@@ -176,8 +109,6 @@ class Moderation extends Component {
key.unbind('esc');
key.unbind('ctrl+f');
key.unbind('t');
- key.unbind('j');
- key.unbind('k');
key.unbind('f');
key.unbind('d');
this.getMenuItems()
@@ -186,11 +117,8 @@ class Moderation extends Component {
componentWillReceiveProps(nextProps) {
- if (this.props.activeTab !== nextProps.activeTab) {
-
- // Reset selection when changing tabs.
- this.select(true, nextProps, null);
- } else {
+ // TODO: Adapt to react virtualized.
+ if (this.props.activeTab === nextProps.activeTab) {
// Detect if comment has left the queue and find next or prev selected comment to set it
// as the new selectedCommentId.
@@ -218,14 +146,6 @@ class Moderation extends Component {
}
}
- componentDidUpdate(prevProps) {
-
- // Scroll to comment when changing from single wiew to normal view.
- if (prevProps.moderation.singleView !== this.props.moderation.singleView && this.state.selectedCommentId) {
- this.scrollTo(this.state.selectedCommentId, false);
- }
- }
-
render () {
const {root, data, moderation, viewUserDetail, activeTab, getModPath, queueConfig, handleCommentChange, ...props} = this.props;
const {asset} = root;
@@ -268,7 +188,7 @@ class Moderation extends Component {
comments={comments.nodes}
activeTab={activeTab}
singleView={moderation.singleView}
- selectedCommentId={this.state.selectedCommentId}
+ selectedCommentId={moderation.selectedCommentId}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
@@ -277,6 +197,7 @@ class Moderation extends Component {
commentCount={activeTabCount}
currentUserId={this.props.auth.user.id}
viewUserDetail={viewUserDetail}
+ selectCommentId={props.selectCommentId}
/>
nodes.some((node) => node.id === id);
@@ -75,6 +76,46 @@ class ModerationQueue extends React.Component {
}
throw new Error(`unknown index ${index}`);
};
+ const view = this.getVisibleComments();
+ if (view.length) {
+ props.selectCommentId(view[0].id);
+ }
+ }
+
+ componentDidMount() {
+ key('j', () => this.selectDown());
+ key('k', () => this.selectUp());
+ }
+
+ componentWillUnmount() {
+ key.unbind('j');
+ key.unbind('k');
+ }
+
+ async selectDown() {
+ const view = this.getVisibleComments();
+ const index = view.findIndex(({id}) => id === this.props.selectedCommentId);
+ if (index === view.length - 1 && this.props.comments.length !== this.props.commentCount) {
+ await this.props.loadMore();
+ this.selectDown();
+ return;
+ }
+ if (index < view.length - 1) {
+ this.props.selectCommentId(view[index + 1].id);
+ }
+ }
+
+ selectUp() {
+ const view = this.getVisibleComments();
+ const index = view.findIndex(({id}) => id === this.props.selectedCommentId);
+
+ if (index === 0 && view.length < this.props.comments.length) {
+ this.viewNewComments(() => this.selectUp());
+ return;
+ }
+ if (index > 0) {
+ this.props.selectCommentId(view[index - 1].id);
+ }
}
componentDidUpdate (prev) {
@@ -86,6 +127,15 @@ class ModerationQueue extends React.Component {
if (prev.comments.length > 0 && comments.length === 0 && commentCount > 0) {
this.props.loadMore();
}
+
+ // Scroll to selected comment.
+ if (prev.selectedCommentId !== this.props.selectedCommentId) {
+
+ const view = this.getVisibleComments();
+ const index = view.findIndex(({id}) => id === this.props.selectedCommentId);
+
+ this.listRef.scrollToRow(index);
+ }
}
handleListRef = (list) => {
@@ -118,8 +168,11 @@ class ModerationQueue extends React.Component {
}
}
- viewNewComments = () => {
- this.setState(resetCursors, () => this.reflowList());
+ viewNewComments = (callback) => {
+ this.setState(resetCursors, () => {
+ this.reflowList();
+ callback && callback();
+ });
};
reflowList = throttle(() => {
@@ -203,6 +256,7 @@ class ModerationQueue extends React.Component {
currentAsset={this.props.currentAsset}
currentUserId={this.props.currentUserId}
clearHeightCache={() => {this.cache.clear(index); this.listRef.recomputeRowHeights(index); }}
+ selectComment={() => this.props.selectCommentId(comment.id)}
/>
@@ -244,6 +298,7 @@ class ModerationQueue extends React.Component {
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
currentUserId={this.props.currentUserId}
+ selectComment={() => this.props.selectCommentId(comment.id)}
/>;
);
@@ -287,6 +342,7 @@ ModerationQueue.propTypes = {
viewUserDetail: PropTypes.func.isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
+ selectCommentId: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js
index b936b4c30..d858cc856 100644
--- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js
+++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js
@@ -23,7 +23,8 @@ import {
toggleStorySearch,
setSortOrder,
storySearchChange,
- clearState
+ clearState,
+ selectCommentId,
} from 'actions/moderation';
import withQueueConfig from '../hoc/withQueueConfig';
import {notify} from 'coral-framework/actions/notification';
@@ -246,6 +247,7 @@ class ModerationContainer extends Component {
activeTab={this.activeTab}
queueConfig={currentQueueConfig}
handleCommentChange={this.handleCommentChange}
+ selectedCommentId={this.props.selectedCommentId}
/>;
}
}
@@ -423,6 +425,7 @@ const mapDispatchToProps = (dispatch) => ({
storySearchChange,
clearState,
notify,
+ selectCommentId,
}, dispatch),
});