mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 01:25:52 -05:00
Merge pull request #2230 from paulkaplan/single-comment-mode
Showing specific comment by url hash on preview
This commit is contained in:
commit
fa7c2d5b09
6 changed files with 96 additions and 7 deletions
|
@ -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 = {};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue