Merge pull request #2230 from paulkaplan/single-comment-mode

Showing specific comment by url hash on preview
This commit is contained in:
Paul Kaplan 2018-10-24 15:41:14 -04:00 committed by GitHub
commit fa7c2d5b09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 96 additions and 7 deletions

View file

@ -417,6 +417,35 @@ module.exports.getTopLevelComments = (id, offset, isAdmin, token) => (dispatch =
});
});
module.exports.getCommentById = (projectId, commentId, isAdmin, token) => (dispatch => {
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
api({
uri: `${isAdmin ? '/admin' : ''}/projects/comments/${commentId}`,
authentication: isAdmin ? token : null
}, (err, body) => {
if (err) {
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
if (!body) {
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
dispatch(module.exports.setError('No comment info'));
return;
}
if (body.parent_id) {
// If the comment is a reply, load the parent
return dispatch(module.exports.getCommentById(projectId, body.parent_id, isAdmin, token));
}
// 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));
});
});
module.exports.getReplies = (projectId, commentIds, isAdmin, token) => (dispatch => {
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING));
const fetchedReplies = {};

View file

@ -26,7 +26,8 @@ class Comment extends React.Component {
'handleCancelReport',
'handlePostReply',
'handleToggleReplying',
'handleRestore'
'handleRestore',
'setRef'
]);
this.state = {
deleting: false,
@ -36,6 +37,12 @@ class Comment extends React.Component {
};
}
componentDidMount () {
if (this.props.highlighted) {
this.ref.scrollIntoView({behavior: 'smooth'});
}
}
handlePostReply (comment) {
this.setState({replying: false});
this.props.onAddComment(comment);
@ -82,6 +89,9 @@ class Comment extends React.Component {
reportConfirmed: false
});
}
setRef (ref) {
this.ref = ref;
}
render () {
const {
@ -92,6 +102,7 @@ class Comment extends React.Component {
canRestore,
content,
datetimeCreated,
highlighted,
id,
parentId,
projectId,
@ -103,8 +114,11 @@ class Comment extends React.Component {
return (
<div
className="flex-row comment"
id={`comments-${id}`}
className={classNames('flex-row', 'comment', {
'highlighted-comment': highlighted
})}
id={`comment-${id}`}
ref={this.setRef}
>
<a href={`/users/${author.username}`}>
<Avatar src={author.image} />
@ -238,6 +252,7 @@ Comment.propTypes = {
canRestore: PropTypes.bool,
content: PropTypes.string,
datetimeCreated: PropTypes.string,
highlighted: PropTypes.bool,
id: PropTypes.number,
onAddComment: PropTypes.func,
onDelete: PropTypes.func,

View file

@ -69,6 +69,17 @@
justify-content: space-between;
align-items: flex-start;
&.highlighted-comment:before {
position: absolute;
top: -.5rem;
left: -.5rem;
border-radius: .5rem;
background: $ui-blue-10percent;
width: calc(100% + 1rem);
height: 100%;
content: "";
}
.comment-top-row {
margin-bottom: 8px;
width: 100%;

View file

@ -20,7 +20,7 @@ class TopLevelComment extends React.Component {
'handleRestoreReply'
]);
this.state = {
expanded: false
expanded: this.props.defaultExpanded
};
// A cache of {userId: username, ...} in order to show reply usernames
@ -77,6 +77,7 @@ class TopLevelComment extends React.Component {
canRestore,
content,
datetimeCreated,
highlightedCommentId,
id,
onDelete,
onReport,
@ -91,6 +92,7 @@ class TopLevelComment extends React.Component {
return (
<FlexRow className="comment-container">
<Comment
highlighted={highlightedCommentId === id}
projectId={projectId}
onAddComment={this.handleAddComment}
{...{
@ -126,6 +128,7 @@ class TopLevelComment extends React.Component {
canRestore={canRestore && parentVisible}
content={reply.content}
datetimeCreated={reply.datetime_created}
highlighted={highlightedCommentId === reply.id}
id={reply.id}
key={reply.id}
parentId={id}
@ -171,7 +174,9 @@ TopLevelComment.propTypes = {
canRestore: PropTypes.bool,
content: PropTypes.string,
datetimeCreated: PropTypes.string,
defaultExpanded: PropTypes.bool,
deletable: PropTypes.bool,
highlightedCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
id: PropTypes.number,
onAddComment: PropTypes.func,
onDelete: PropTypes.func,
@ -183,4 +188,8 @@ TopLevelComment.propTypes = {
visibility: PropTypes.string
};
TopLevelComment.defaultProps = {
defaultExpanded: false
};
module.exports = TopLevelComment;

View file

@ -70,6 +70,7 @@ const PreviewPresentation = ({
replies,
addToStudioOpen,
projectStudios,
singleCommentId,
userOwnsProject,
onAddComment,
onDeleteComment,
@ -369,6 +370,8 @@ const PreviewPresentation = ({
canRestore={canRestoreComments}
content={comment.content}
datetimeCreated={comment.datetime_created}
defaultExpanded={!!singleCommentId}
highlightedCommentId={singleCommentId}
id={comment.id}
key={comment.id}
parentId={comment.parent_id}
@ -453,6 +456,7 @@ PreviewPresentation.propTypes = {
remixes: PropTypes.arrayOf(PropTypes.object),
replies: PropTypes.objectOf(PropTypes.array),
reportOpen: PropTypes.bool,
singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
userOwnsProject: PropTypes.bool
};

View file

@ -65,11 +65,18 @@ class Preview extends React.Component {
// parts[0]: 'preview'
// parts[1]: either :id or 'editor'
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
// Get single-comment id from url hash, using the #comment-{id} scheme from scratch2
const commentHashPrefix = '#comment-';
const singleCommentId = window.location.hash.indexOf(commentHashPrefix) !== -1 &&
parseInt(window.location.hash.replace(commentHashPrefix, ''), 10);
this.state = {
extensions: [],
favoriteCount: 0,
loveCount: 0,
projectId: parts[1] === 'editor' ? '0' : parts[1],
singleCommentId: singleCommentId,
addToStudioOpen: false,
reportOpen: false
};
@ -123,8 +130,13 @@ class Preview extends React.Component {
if (this.props.userPresent) {
const username = this.props.user.username;
const token = this.props.user.token;
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length,
this.props.isAdmin, token);
if (this.state.singleCommentId) {
this.props.getCommentById(this.state.projectId, this.state.singleCommentId,
this.props.isAdmin, token);
} else {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length,
this.props.isAdmin, token);
}
this.props.getProjectInfo(this.state.projectId, token);
this.props.getRemixes(this.state.projectId, token);
this.props.getProjectStudios(this.state.projectId, token);
@ -132,7 +144,11 @@ class Preview extends React.Component {
this.props.getFavedStatus(this.state.projectId, username, token);
this.props.getLovedStatus(this.state.projectId, username, token);
} else {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length);
if (this.state.singleCommentId) {
this.props.getCommentById(this.state.projectId, this.state.singleCommentId);
} else {
this.props.getTopLevelComments(this.state.projectId, this.props.comments.length);
}
this.props.getProjectInfo(this.state.projectId);
this.props.getRemixes(this.state.projectId);
this.props.getProjectStudios(this.state.projectId);
@ -410,6 +426,7 @@ class Preview extends React.Component {
remixes={this.props.remixes}
replies={this.props.replies}
reportOpen={this.state.reportOpen}
singleCommentId={this.state.singleCommentId}
userOwnsProject={this.props.userOwnsProject}
onAddComment={this.handleAddComment}
onAddToStudioClicked={this.handleAddToStudioClick}
@ -480,6 +497,7 @@ Preview.propTypes = {
enableCommunity: PropTypes.bool,
faved: PropTypes.bool,
fullScreen: PropTypes.bool,
getCommentById: PropTypes.func.isRequired,
getCuratedStudios: PropTypes.func.isRequired,
getFavedStatus: PropTypes.func.isRequired,
getLovedStatus: PropTypes.func.isRequired,
@ -653,6 +671,9 @@ const mapDispatchToProps = dispatch => ({
getTopLevelComments: (id, offset, isAdmin, token) => {
dispatch(previewActions.getTopLevelComments(id, offset, isAdmin, token));
},
getCommentById: (projectId, commentId, isAdmin, token) => {
dispatch(previewActions.getCommentById(projectId, commentId, isAdmin, token));
},
getFavedStatus: (id, username, token) => {
dispatch(previewActions.getFavedStatus(id, username, token));
},