Allow loading more than 20 replies

This commit is contained in:
Paul Kaplan 2019-01-10 10:46:01 -05:00
parent 195cdba19a
commit f7e8922757
6 changed files with 51 additions and 19 deletions

View file

@ -78,6 +78,7 @@
"lodash.defaultsdeep": "3.10.0",
"lodash.isarray": "3.0.4",
"lodash.merge": "3.3.2",
"lodash.mergewith": "4.6.1",
"lodash.omit": "3.1.0",
"lodash.range": "3.0.1",
"minilog": "2.0.8",

View file

@ -237,7 +237,7 @@
"comments.post": "Post",
"comments.cancel": "Cancel",
"comments.lengthWarning": "{remainingCharacters, plural, one {1 character left} other {{remainingCharacters} characters left}}",
"comments.seeMoreReplies": "{repliesCount, plural, one {See 1 more reply} other {See all {repliesCount} replies}}",
"comments.loadMoreReplies": "See more replies",
"comments.status.delbyusr": "Deleted by project owner",
"comments.status.censbyfilter": "Censored by filter",
"comments.status.delbyparentcomment": "Parent comment deleted",

View file

@ -1,11 +1,13 @@
const defaults = require('lodash.defaults');
const keyMirror = require('keymirror');
const async = require('async');
const merge = require('lodash.merge');
const mergeWith = require('lodash.mergewith');
const api = require('../lib/api');
const log = require('../lib/log');
const COMMENT_LIMIT = 20;
module.exports.Status = keyMirror({
FETCHED: null,
NOT_FETCHED: null,
@ -149,7 +151,19 @@ module.exports.previewReducer = (state, action) => {
});
case 'SET_REPLIES':
return Object.assign({}, state, {
replies: merge({}, state.replies, action.replies)
// Append new replies to the state.replies structure
replies: mergeWith({}, state.replies, action.replies, (replies, newReplies) => (
(replies || []).concat(newReplies || [])
)),
// Also set the `moreRepliesToLoad` property on the top-level comments
comments: state.comments.map(comment => {
if (action.replies[comment.id]) {
return Object.assign({}, comment, {
moreRepliesToLoad: action.replies[comment.id].length === COMMENT_LIMIT
});
}
return comment;
})
});
case 'SET_LOVED':
return Object.assign({}, state, {
@ -448,7 +462,6 @@ module.exports.getFavedStatus = (id, username, token) => (dispatch => {
});
module.exports.getTopLevelComments = (id, offset, isAdmin, token) => (dispatch => {
const COMMENT_LIMIT = 20;
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
api({
uri: `${isAdmin ? '/admin' : ''}/projects/${id}/comments`,
@ -467,7 +480,7 @@ module.exports.getTopLevelComments = (id, offset, isAdmin, token) => (dispatch =
}
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
dispatch(module.exports.setComments(body));
dispatch(module.exports.getReplies(id, body.map(comment => comment.id), isAdmin, token));
dispatch(module.exports.getReplies(id, body.map(comment => comment.id), 0, isAdmin, token));
// If we loaded a full page of comments, assume there are more to load.
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
@ -503,17 +516,18 @@ module.exports.getCommentById = (projectId, commentId, isAdmin, token) => (dispa
// If the comment is not a reply, show it as top level and load replies
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
dispatch(module.exports.setComments([body]));
dispatch(module.exports.getReplies(projectId, [body.id], isAdmin, token));
dispatch(module.exports.getReplies(projectId, [body.id], 0, isAdmin, token));
});
});
module.exports.getReplies = (projectId, commentIds, isAdmin, token) => (dispatch => {
module.exports.getReplies = (projectId, commentIds, offset, isAdmin, token) => (dispatch => {
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING));
const fetchedReplies = {};
async.eachLimit(commentIds, 10, (parentId, callback) => {
api({
uri: `${isAdmin ? '/admin' : ''}/projects/${projectId}/comments/${parentId}/replies`,
authentication: isAdmin ? token : null
authentication: isAdmin ? token : null,
params: {offset: offset || 0, limit: COMMENT_LIMIT}
}, (err, body) => {
if (err) {
return callback(`Error fetching comment replies: ${err}`);

View file

@ -28,9 +28,11 @@ class TopLevelComment extends React.Component {
}
handleExpandThread () {
this.setState({
expanded: true
});
if (this.state.expanded) {
this.props.onLoadMoreReplies(this.props.id, this.props.replies.length);
} else {
this.setState({expanded: true});
}
}
handleDeleteReply (replyId) {
@ -79,6 +81,7 @@ class TopLevelComment extends React.Component {
datetimeCreated,
highlightedCommentId,
id,
moreRepliesToLoad,
onDelete,
onReport,
onRestore,
@ -143,17 +146,13 @@ class TopLevelComment extends React.Component {
))}
</FlexRow>
}
{!this.state.expanded && replies.length > 3 &&
{((!this.state.expanded && replies.length > 3) ||
(this.state.expanded && moreRepliesToLoad)) &&
<a
className="expand-thread"
onClick={this.handleExpandThread}
>
<FormattedMessage
id="comments.seeMoreReplies"
values={{
repliesCount: replies.length
}}
/>
<FormattedMessage id="comments.loadMoreReplies" />
</a>
}
</FlexRow>
@ -178,8 +177,10 @@ TopLevelComment.propTypes = {
deletable: PropTypes.bool,
highlightedCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
id: PropTypes.number,
moreRepliesToLoad: PropTypes.bool,
onAddComment: PropTypes.func,
onDelete: PropTypes.func,
onLoadMoreReplies: PropTypes.func,
onReport: PropTypes.func,
onRestore: PropTypes.func,
parentId: PropTypes.number,
@ -189,7 +190,8 @@ TopLevelComment.propTypes = {
};
TopLevelComment.defaultProps = {
defaultExpanded: false
defaultExpanded: false,
moreRepliesToLoad: false
};
module.exports = TopLevelComment;

View file

@ -88,6 +88,7 @@ const PreviewPresentation = ({
onFavoriteClicked,
onGreenFlag,
onLoadMore,
onLoadMoreReplies,
onLoveClicked,
onOpenAdminPanel,
onRemix,
@ -550,12 +551,14 @@ const PreviewPresentation = ({
highlightedCommentId={singleCommentId}
id={comment.id}
key={comment.id}
moreRepliesToLoad={comment.moreRepliesToLoad}
parentId={comment.parent_id}
projectId={projectId}
replies={replies && replies[comment.id] ? replies[comment.id] : []}
visibility={comment.visibility}
onAddComment={onAddComment}
onDelete={onDeleteComment}
onLoadMoreReplies={onLoadMoreReplies}
onReport={onReportComment}
onRestore={onRestoreComment}
/>
@ -644,6 +647,7 @@ PreviewPresentation.propTypes = {
onFavoriteClicked: PropTypes.func,
onGreenFlag: PropTypes.func,
onLoadMore: PropTypes.func,
onLoadMoreReplies: PropTypes.func,
onLoveClicked: PropTypes.func,
onOpenAdminPanel: PropTypes.func,
onRemix: PropTypes.func,

View file

@ -60,6 +60,7 @@ class Preview extends React.Component {
'handleToggleStudio',
'handleFavoriteToggle',
'handleLoadMore',
'handleLoadMoreReplies',
'handleLoveToggle',
'handleMessage',
'handlePopState',
@ -440,6 +441,11 @@ class Preview extends React.Component {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length,
this.props.isAdmin, this.props.user && this.props.user.token);
}
handleLoadMoreReplies (commentId, offset) {
this.props.getMoreReplies(this.state.projectId, commentId, offset,
this.props.isAdmin, this.props.user && this.props.user.token
);
}
handleLoveToggle () {
if (!this.props.lovedLoaded) return;
@ -644,6 +650,7 @@ class Preview extends React.Component {
onFavoriteClicked={this.handleFavoriteToggle}
onGreenFlag={this.handleGreenFlag}
onLoadMore={this.handleLoadMore}
onLoadMoreReplies={this.handleLoadMoreReplies}
onLoveClicked={this.handleLoveToggle}
onOpenAdminPanel={this.handleOpenAdminPanel}
onRemix={this.handleRemix}
@ -736,6 +743,7 @@ Preview.propTypes = {
getCuratedStudios: PropTypes.func.isRequired,
getFavedStatus: PropTypes.func.isRequired,
getLovedStatus: PropTypes.func.isRequired,
getMoreReplies: PropTypes.func.isRequired,
getOriginalInfo: PropTypes.func.isRequired,
getParentInfo: PropTypes.func.isRequired,
getProjectInfo: PropTypes.func.isRequired,
@ -948,6 +956,9 @@ const mapDispatchToProps = dispatch => ({
getCommentById: (projectId, commentId, isAdmin, token) => {
dispatch(previewActions.getCommentById(projectId, commentId, isAdmin, token));
},
getMoreReplies: (projectId, commentId, offset, isAdmin, token) => {
dispatch(previewActions.getReplies(projectId, [commentId], offset, isAdmin, token));
},
getFavedStatus: (id, username, token) => {
dispatch(previewActions.getFavedStatus(id, username, token));
},