From ab16e5bfe7a396377e4784873549384f9bffe125 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 27 Feb 2017 14:26:20 -0500 Subject: [PATCH 1/5] Retrieving comment y id from backend --- graph/loaders/comments.js | 17 +++++++++++++++++ graph/resolvers/comment.js | 7 +++++++ graph/resolvers/root_query.js | 4 +++- graph/typeDefs.graphql | 6 ++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index ad78a1e92..b5f493831 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -270,6 +270,22 @@ const genRecentComments = (_, ids) => { .then(util.arrayJoinBy(ids, 'asset_id')); }; +/** + * genComments returns the comments by the id's specified that are considered + * public comments (i.e., accepted or not rejected). + */ +const genComments = (context, ids) => { + return CommentModel.find({ + id: { + $in: ids + }, + status: { + $in: ['NONE', 'ACCEPTED'] + } + }) + .then(util.singleJoinBy(ids, 'id')); +}; + /** * Creates a set of loaders based on a GraphQL context. * @param {Object} context the context of the GraphQL request @@ -277,6 +293,7 @@ const genRecentComments = (_, ids) => { */ module.exports = (context) => ({ Comments: { + 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 ef77ff6b5..43577dca0 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 ced4e65cf..6622fd17f 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 fbdeb4279..b9ab770ac 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] From bf23fc29740ba1f70cd15dbbde9acfef555cbf2e Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 28 Feb 2017 12:02:51 -0500 Subject: [PATCH 2/5] Moving comment id into query. --- client/coral-embed-stream/src/Embed.js | 3 ++- client/coral-embed/index.js | 17 +++++++++++------ .../graphql/queries/commentQuery.graphql | 13 +++++++++++++ client/coral-framework/graphql/queries/index.js | 9 +++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 client/coral-framework/graphql/queries/commentQuery.graphql diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index ac9c09eac..b0f5d5c5a 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -12,7 +12,7 @@ const {logout, showSignInDialog, requestConfirmEmail} = authActions; const {addNotification, clearNotification} = notificationActions; const {fetchAssetSuccess} = assetActions; -import {queryStream} from 'coral-framework/graphql/queries'; +import {queryStream, commentQuery} from 'coral-framework/graphql/queries'; import {postComment, postFlag, postLike, postDontAgree, deleteAction} from 'coral-framework/graphql/mutations'; import {editName} from 'coral-framework/actions/user'; import {updateCountCache} from 'coral-framework/actions/asset'; @@ -251,5 +251,6 @@ export default compose( postLike, postDontAgree, deleteAction, + commentQuery, queryStream )(Embed); diff --git a/client/coral-embed/index.js b/client/coral-embed/index.js index 4a7e753ca..9f3b23aaf 100644 --- a/client/coral-embed/index.js +++ b/client/coral-embed/index.js @@ -58,10 +58,11 @@ // ensure el has an id, as pym can't directly accept the HTMLElement if ( ! el.id) {el.id = '_' + String(Math.random());} - var asset = opts.asset || window.location; + var asset = opts.asset || window.location.href.split('#')[0]; + var comment = window.location.hash.slice(1); var pymParent = new pym.Parent( el.id, - buildStreamIframeUrl(opts.talk, asset), + buildStreamIframeUrl(opts.talk, asset, comment), { title: opts.title, asset_url: asset, @@ -76,14 +77,18 @@ return Coral; // build the URL to load in the pym iframe - function buildStreamIframeUrl(talkBaseUrl, asset) { - var iframeUrl = [ + function buildStreamIframeUrl(talkBaseUrl, asset, comment) { + var iframeArray = [ talkBaseUrl, (talkBaseUrl.match(/\/$/) ? '' : '/'), // make sure no double-'/' if opts.talk already ends with '/' 'embed/stream?asset_url=', encodeURIComponent(asset) - ].join(''); - return iframeUrl; + ]; + + if (comment) { + iframeArray.push(`&comment_id=${comment}`); + } + return iframeArray.join(''); } // Set up postMessage listeners/handlers on the pymParent diff --git a/client/coral-framework/graphql/queries/commentQuery.graphql b/client/coral-framework/graphql/queries/commentQuery.graphql new file mode 100644 index 000000000..83f92b8f5 --- /dev/null +++ b/client/coral-framework/graphql/queries/commentQuery.graphql @@ -0,0 +1,13 @@ +#import "../fragments/commentView.graphql" + +query commentQuery($id: ID!) { + comment(id: $id) { + ...commentView + parent { + ...commentView + replies { + ...commentView + } + } + } +} diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index c993dfb6a..8d61c9f8b 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -2,6 +2,7 @@ import {graphql} from 'react-apollo'; import STREAM_QUERY from './streamQuery.graphql'; import LOAD_MORE from './loadMore.graphql'; import GET_COUNTS from './getCounts.graphql'; +import COMMENT_QUERY from './commentQuery.graphql'; import MY_COMMENT_HISTORY from './myCommentHistory.graphql'; function getQueryVariable(variable) { @@ -97,4 +98,12 @@ export const queryStream = graphql(STREAM_QUERY, { }) }); +export const commentQuery = graphql(COMMENT_QUERY, { + options: () => ({ + variables: { + id: getQueryVariable('comment_id') + } + }) +}); + export const myCommentHistory = graphql(MY_COMMENT_HISTORY, {}); From 158f31d603d8f1d755d08a17dba61e246758ee30 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 28 Feb 2017 13:55:28 -0500 Subject: [PATCH 3/5] Highlighting permalinked comment. --- client/coral-embed-stream/src/Comment.js | 10 ++- client/coral-embed-stream/src/Embed.js | 78 ++++++++++++++----- client/coral-embed-stream/src/Stream.js | 19 +---- client/coral-embed-stream/style/default.css | 5 ++ .../coral-framework/graphql/queries/index.js | 12 +-- .../graphql/queries/streamQuery.graphql | 11 ++- 6 files changed, 85 insertions(+), 50 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 7262d10ed..8a81f9fe6 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -38,12 +38,12 @@ class Comment extends React.Component { // id of currently opened ReplyBox. tracked in Stream.js activeReplyBox: PropTypes.string.isRequired, setActiveReplyBox: PropTypes.func.isRequired, - refetch: PropTypes.func.isRequired, showSignInDialog: PropTypes.func.isRequired, postFlag: PropTypes.func.isRequired, 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, @@ -85,10 +85,10 @@ class Comment extends React.Component { asset, depth, postItem, - refetch, addNotification, showSignInDialog, postLike, + highlighted, postFlag, postDontAgree, loadMore, @@ -100,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 (

@@ -158,7 +160,6 @@ class Comment extends React.Component { comment.replies && comment.replies.map(reply => { return { @@ -60,22 +61,24 @@ 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); - } + // pym.onMessage('DOMContentLoaded', hash => { - 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); - }); + // 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) { @@ -83,13 +86,27 @@ class Embed extends Component { if(!isEqual(nextProps.data.asset, this.props.data.asset)) { loadAsset(nextProps.data.asset); } + + // if(!isEqual(nextProps.data.comment, this.props.data.comment)) { + // pym.scrollParentToChildEl(nextProps.data.comment.id); + // } + } + + 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 +176,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}) => ({ @@ -98,12 +98,4 @@ export const queryStream = graphql(STREAM_QUERY, { }) }); -export const commentQuery = graphql(COMMENT_QUERY, { - options: () => ({ - variables: { - id: getQueryVariable('comment_id') - } - }) -}); - export const myCommentHistory = graphql(MY_COMMENT_HISTORY, {}); 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 From 62fe63d323c34cb42ebc5fa2e5f60271669f889e Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 28 Feb 2017 14:27:53 -0500 Subject: [PATCH 4/5] Scrolling to permalinked comment or reply. --- client/coral-embed-stream/src/Embed.js | 29 +++++++------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 2854de68c..5526d70e1 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -60,25 +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) { @@ -86,10 +67,14 @@ class Embed extends Component { if(!isEqual(nextProps.data.asset, this.props.data.asset)) { loadAsset(nextProps.data.asset); } + } - // if(!isEqual(nextProps.data.comment, this.props.data.comment)) { - // pym.scrollParentToChildEl(nextProps.data.comment.id); - // } + 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) { From d8117494d1f968bb6452bcd3a98ab6141959699c Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 28 Feb 2017 18:18:17 -0500 Subject: [PATCH 5/5] Cleaning up after merge conflict. --- graph/loaders/comments.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 6c9d4bcbc..afa79ea5c 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -39,14 +39,7 @@ const getCountsByAssetID = (context, asset_ids) => { /** * Returns the comment count for all comments that are public based on their * parent ids. - * @param {Object} - - - - - - - graph context + * * @param {Array} parent_ids the ids of parents for which there are * comments that we want to get */ @@ -284,7 +277,7 @@ const genRecentComments = (_, ids) => { * @return {Promise} resolves to the comments */ const genComments = ({user}, ids) => { - let comments + let comments; if (user && user.hasRoles('ADMIN')) { comments = CommentModel.find({ id: { @@ -300,11 +293,8 @@ const genComments = ({user}, ids) => { $in: ['NONE', 'ACCEPTED'] } }); + } return comments.then(util.singleJoinBy(ids, 'id')); - - */ -const genCommentsByID = (context, ids) => { - }; /**