diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index e52c4aff4..8a81f9fe6 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -43,6 +43,7 @@ class Comment extends React.Component { postLike: PropTypes.func.isRequired, deleteAction: PropTypes.func.isRequired, parentId: PropTypes.string, + highlighted: PropTypes.string, addNotification: PropTypes.func.isRequired, postItem: PropTypes.func.isRequired, depth: PropTypes.number.isRequired, @@ -87,6 +88,7 @@ class Comment extends React.Component { addNotification, showSignInDialog, postLike, + highlighted, postFlag, postDontAgree, loadMore, @@ -98,10 +100,12 @@ class Comment extends React.Component { const like = getActionSummary('LikeActionSummary', comment); const flag = getActionSummary('FlagActionSummary', comment); const dontagree = getActionSummary('DontAgreeActionSummary', comment); + let commentClass = parentId ? `reply ${styles.Reply}` : `comment ${styles.Comment}`; + commentClass += highlighted === comment.id ? ' highlighted-comment' : ''; return (

@@ -163,6 +167,7 @@ class Comment extends React.Component { postItem={postItem} depth={depth + 1} asset={asset} + highlighted={highlighted} currentUser={currentUser} postLike={postLike} postFlag={postFlag} diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index ac9c09eac..5526d70e1 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -31,12 +31,13 @@ import ChangeUsernameContainer from '../../coral-sign-in/containers/ChangeUserna import ProfileContainer from 'coral-settings/containers/ProfileContainer'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; +import Comment from './Comment'; import LoadMore from './LoadMore'; import NewCount from './NewCount'; class Embed extends Component { - state = {activeTab: 0, showSignInDialog: false}; + state = {activeTab: 0, showSignInDialog: false, activeReplyBox: ''}; changeTab = (tab) => { @@ -59,23 +60,6 @@ class Embed extends Component { componentDidMount () { pym.sendMessage('childReady'); - - pym.onMessage('DOMContentLoaded', hash => { - const commentId = hash.replace('#', 'c_'); - let count = 0; - const interval = setInterval(() => { - if (document.getElementById(commentId)) { - window.clearInterval(interval); - pym.scrollParentToChildEl(commentId); - } - - if (++count > 100) { // ~10 seconds - // give up waiting for the comments to load. - // it would be weird for the page to jump after that long. - window.clearInterval(interval); - } - }, 100); - }); } componentWillReceiveProps (nextProps) { @@ -85,11 +69,29 @@ class Embed extends Component { } } + componentDidUpdate(prevProps) { + if(!isEqual(prevProps.data.comment, this.props.data.comment)) { + + // Scroll to a permalinked comment if one is in the URL once the page is done rendering. + setTimeout(()=>pym.scrollParentToChildEl(`c_${this.props.data.comment.id}`), 0); + } + } + + setActiveReplyBox (reactKey) { + if (!this.props.currentUser) { + const offset = document.getElementById(`c_${reactKey}`).getBoundingClientRect().top - 75; + this.props.showSignInDialog(offset); + } else { + this.setState({activeReplyBox: reactKey}); + } + } + render () { const {activeTab} = this.state; const {closedAt, countCache = {}} = this.props.asset; - const {loading, asset, refetch} = this.props.data; + const {loading, asset, refetch, comment} = this.props.data; const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; + const highlightedComment = comment && comment.parent ? comment.parent : comment; const openStream = closedAt === null; @@ -159,6 +161,28 @@ class Embed extends Component { } {!loggedIn && } {loggedIn && user && } + { + highlightedComment && + + } ({limit, cursor, parent_id, asset_id, sort}, n export const queryStream = graphql(STREAM_QUERY, { options: () => ({ variables: { - asset_url: getQueryVariable('asset_url') + asset_url: getQueryVariable('asset_url'), + comment_id: getQueryVariable('comment_id') } }), props: ({data}) => ({ diff --git a/client/coral-framework/graphql/queries/streamQuery.graphql b/client/coral-framework/graphql/queries/streamQuery.graphql index 0a7f6c549..4f09f628e 100644 --- a/client/coral-framework/graphql/queries/streamQuery.graphql +++ b/client/coral-framework/graphql/queries/streamQuery.graphql @@ -1,6 +1,15 @@ #import "../fragments/commentView.graphql" -query AssetQuery($asset_url: String!) { +query AssetQuery($asset_url: String!, $comment_id: ID!) { + comment(id: $comment_id) { + ...commentView + parent { + ...commentView + replies { + ...commentView + } + } + } asset(url: $asset_url) { id title diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 47a8095dc..afa79ea5c 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -39,7 +39,7 @@ const getCountsByAssetID = (context, asset_ids) => { /** * Returns the comment count for all comments that are public based on their * parent ids. - * @param {Object} context graph context + * * @param {Array} parent_ids the ids of parents for which there are * comments that we want to get */ @@ -271,18 +271,30 @@ const genRecentComments = (_, ids) => { }; /** - * Returns the comment's by their id. + * genComments returns the comments by the id's. Only admins can see non-public comments. * @param {Object} context graph context * @param {Array} ids the comment id's to fetch * @return {Promise} resolves to the comments */ -const genCommentsByID = (context, ids) => { - return CommentModel.find({ - id: { - $in: ids - } - }) - .then(util.singleJoinBy(ids, 'id')); +const genComments = ({user}, ids) => { + let comments; + if (user && user.hasRoles('ADMIN')) { + comments = CommentModel.find({ + id: { + $in: ids + } + }); + } else { + comments = CommentModel.find({ + id: { + $in: ids + }, + status: { + $in: ['NONE', 'ACCEPTED'] + } + }); + } + return comments.then(util.singleJoinBy(ids, 'id')); }; /** @@ -292,7 +304,7 @@ const genCommentsByID = (context, ids) => { */ module.exports = (context) => ({ Comments: { - get: new DataLoader((ids) => genCommentsByID(context, ids)), + get: new DataLoader((ids) => genComments(context, ids)), getByQuery: (query) => getCommentsByQuery(context, query), getCountByQuery: (query) => getCommentCountByQuery(context, query), countByAssetID: new util.SharedCacheDataLoader('Comments.countByAssetID', 3600, (ids) => getCountsByAssetID(context, ids)), diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index de1909df1..2752ad0e3 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -1,4 +1,11 @@ const Comment = { + parent({parent_id}, _, {loaders: {Comments}}) { + if (parent_id == null) { + return null; + } + + return Comments.get.load(parent_id); + }, user({author_id}, _, {loaders: {Users}}) { return Users.getByID.load(author_id); }, diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 8800bfebf..bdcdcef78 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -38,7 +38,9 @@ const RootQuery = { return Comments.getByQuery(query); }, - + comment(_, {id}, {loaders: {Comments}}) { + return Comments.get.load(id); + }, commentCount(_, {query: {action_type, statuses, asset_id, parent_id}}, {user, loaders: {Actions, Comments}}) { if (user == null || !user.hasRoles('ADMIN')) { return null; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 4dd9a4ba8..92f0d41e2 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -149,6 +149,9 @@ input CommentCountQuery { # Comment is the base representation of user interaction in Talk. type Comment { + # The parent of the comment (if there is one). + parent: Comment + # The ID of the comment. id: ID! @@ -477,6 +480,9 @@ type RootQuery { # Site wide settings and defaults. settings: Settings + # Finds a specific comment based on it's id. + comment(id: ID!): Comment + # All assets. Requires the `ADMIN` role. assets: [Asset]