From 4e60689fa415b17d20ca8be6e791de8268cb0446 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 28 Mar 2017 14:31:43 -0600 Subject: [PATCH 1/8] view a permalink thread in isolation --- client/coral-embed-stream/src/Embed.js | 95 +++++++++++-------- client/coral-embed/src/index.js | 9 ++ client/coral-framework/actions/asset.js | 35 +++++++ client/coral-framework/constants/asset.js | 2 + .../coral-framework/graphql/queries/index.js | 3 + 5 files changed, 104 insertions(+), 40 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index a3d2a24a9..61e5ab1b9 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -6,7 +6,7 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-framework/translations'; const lang = new I18n(translations); -import {TabBar, Tab, TabContent, Spinner} from 'coral-ui'; +import {TabBar, Tab, TabContent, Spinner, Button} from 'coral-ui'; const {logout, showSignInDialog, requestConfirmEmail} = authActions; const {addNotification, clearNotification} = notificationActions; @@ -15,7 +15,7 @@ const {fetchAssetSuccess} = assetActions; import {queryStream} from 'coral-framework/graphql/queries'; import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag} from 'coral-framework/graphql/mutations'; import {editName} from 'coral-framework/actions/user'; -import {updateCountCache} from 'coral-framework/actions/asset'; +import {updateCountCache, viewAllComments} from 'coral-framework/actions/asset'; import {notificationActions, authActions, assetActions, pym} from 'coral-framework'; import Stream from './Stream'; @@ -79,7 +79,7 @@ class Embed extends Component { 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); + setTimeout(() => pym.scrollParentToChildEl('coralStream'), 0); } } @@ -124,6 +124,16 @@ class Embed extends Component { {lang.t('MY_COMMENTS')} Configure Stream + { + highlightedComment && + + } {loggedIn && this.props.logout().then(refetch)} changeTab={this.changeTab}/>} { @@ -170,9 +180,11 @@ class Embed extends Component { offset={signInOffset}/>} {loggedIn && user && } {loggedIn && } + + {/* the highlightedComment is isolated after the user followed a permalink */} { - highlightedComment && - + :
+ +
+ +
+ asset.comments.length} + loadMore={this.props.loadMore} /> +
} - -
- -
- asset.comments.length} - loadMore={this.props.loadMore}/>
({ editName: (username) => dispatch(editName(username)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), updateCountCache: (id, count) => dispatch(updateCountCache(id, count)), + viewAllComments: () => dispatch(viewAllComments()), logout: () => dispatch(logout()), dispatch: d => dispatch(d) }); diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index 8ebd403b8..e3350e0d3 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -74,6 +74,15 @@ function configurePymParent(pymParent, asset_url) { snackbar.style.opacity = 0; }); + // remove the permalink comment id from the hash + pymParent.onMessage('coral-view-all-comments', function () { + window.history.replaceState( + {}, + document.title, + location.origin + location.pathname + location.search + ); + }); + pymParent.onMessage('coral-alert', function (message) { const [type, text] = message.split('|'); snackbar.style.transform = 'translate(-50%, 20px)'; diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js index 609525986..fa927e58a 100644 --- a/client/coral-framework/actions/asset.js +++ b/client/coral-framework/actions/asset.js @@ -1,6 +1,7 @@ import * as actions from '../constants/asset'; import coralApi from '../helpers/response'; import {addNotification} from '../actions/notification'; +import {pym} from 'coral-framework'; import I18n from '../../coral-framework/modules/i18n/i18n'; import translations from './../translations'; @@ -49,3 +50,37 @@ export const updateOpenStatus = status => dispatch => { dispatch(updateOpenStream({closedAt: new Date().getTime()})); } }; + +function removeParam(key, sourceURL) { + let rtn = sourceURL.split('?')[0]; + let param; + let params_arr = []; + let queryString = (sourceURL.indexOf('?') !== -1) ? sourceURL.split('?')[1] : ''; + if (queryString !== '') { + params_arr = queryString.split('&'); + for (let i = params_arr.length - 1; i >= 0; i -= 1) { + param = params_arr[i].split('=')[0]; + if (param === key) { + params_arr.splice(i, 1); + } + } + rtn = `${rtn}?${params_arr.join('&')}`; + } + return rtn; +} + +export const viewAllComments = () => { + + // remove the comment_id url param + const modifiedUrl = removeParam('comment_id', location.href); + try { + + // "window" here refers to the embedded iframe + window.history.replaceState({}, document.title, modifiedUrl); + + // also change the parent url + pym.sendMessage('coral-view-all-comments'); + } catch (e) { /* not sure if we're worried about old browsers */ } + + return {type: actions.VIEW_ALL_COMMENTS}; +}; diff --git a/client/coral-framework/constants/asset.js b/client/coral-framework/constants/asset.js index 234095d9d..5547c5284 100644 --- a/client/coral-framework/constants/asset.js +++ b/client/coral-framework/constants/asset.js @@ -9,3 +9,5 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE'; export const OPEN_COMMENTS = 'OPEN_COMMENTS'; export const CLOSE_COMMENTS = 'CLOSE_COMMENTS'; export const UPDATE_COUNT_CACHE = 'UPDATE_COUNT_CACHE'; + +export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS'; diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index cdf2356d8..a6c8aa17b 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -20,6 +20,7 @@ function getQueryVariable(variable) { return null; } +// get the counts of the top-level comments export const getCounts = (data) => ({asset_id, limit, sort}) => { return data.fetchMore({ query: GET_COUNTS, @@ -41,6 +42,7 @@ export const getCounts = (data) => ({asset_id, limit, sort}) => { }); }; +// handle paginated requests for more Comments pertaining to the Asset export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, sort}, newComments) => { return data.fetchMore({ query: LOAD_MORE, @@ -94,6 +96,7 @@ export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, s }); }; +// load the comment stream. export const queryStream = graphql(STREAM_QUERY, { options: () => { let comment_id = getQueryVariable('comment_id'); From b7ce669cfc9fe178092fc8182ba7b174be16bc6c Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 28 Mar 2017 14:46:01 -0600 Subject: [PATCH 2/8] add translations --- client/coral-embed-stream/src/Embed.js | 2 +- client/coral-framework/translations.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 61e5ab1b9..918d8a4c7 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -132,7 +132,7 @@ class Embed extends Component { onClick={() => { this.props.viewAllComments(); this.props.data.refetch(); - }}>Show all comments + }}>{lang.t('showAllComments')} } {loggedIn && this.props.logout().then(refetch)} changeTab={this.changeTab}/>} diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index f9038aba7..31c3e98ea 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -13,6 +13,7 @@ "error": "Usernames can contain letters, numbers and _ only" }, "viewMoreComments": "view more comments", + "showAllComments": "Show all comments", "viewReply": "view reply", "viewAllRepliesInitial": "view all {0} replies", "viewAllReplies": "view {0} replies", @@ -56,6 +57,7 @@ "newCount": "Ver {0} {1} más", "comment": "commentario", "comments": "commentarios", + "showAllComments": "Mostrar todos los comentarios", "error": { "emailNotVerified": "E-mail {0} no verificado.", "email": "No es un e-mail válido", From f11a11cf675239914224d82fd3d4e4022a6dac64 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 29 Mar 2017 13:04:28 -0600 Subject: [PATCH 3/8] show view more button on a permalinked comment if applicable --- client/coral-framework/graphql/queries/streamQuery.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-framework/graphql/queries/streamQuery.graphql b/client/coral-framework/graphql/queries/streamQuery.graphql index 03af1de4c..f3b8576dd 100644 --- a/client/coral-framework/graphql/queries/streamQuery.graphql +++ b/client/coral-framework/graphql/queries/streamQuery.graphql @@ -5,6 +5,7 @@ query AssetQuery($asset_id: ID, $asset_url: String!, $comment_id: ID!, $has_comm ...commentView parent { ...commentView + replyCount replies { ...commentView } From 3007ba68f8709cf2f6482242bdf9d1f143de2c4a Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 29 Mar 2017 15:12:33 -0600 Subject: [PATCH 4/8] load more replies on a permalinked comment --- .../coral-framework/graphql/queries/index.js | 44 +++++++++++++++---- .../graphql/queries/streamQuery.graphql | 7 +++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index a6c8aa17b..ec0dcc336 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -5,6 +5,7 @@ import GET_COUNTS from './getCounts.graphql'; import MY_COMMENT_HISTORY from './myCommentHistory.graphql'; import uniqBy from 'lodash/uniqBy'; import sortBy from 'lodash/sortBy'; +import isNil from 'lodash/isNil'; function getQueryVariable(variable) { let query = window.location.search.substring(1); @@ -47,19 +48,43 @@ export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, s return data.fetchMore({ query: LOAD_MORE, variables: { - limit, - cursor, - parent_id, - asset_id, - sort + limit, // how many comments are we returning + cursor, // the date of the first/last comment depending on the sort order + parent_id, // if null, we're loading more top-level comments, if not, we're loading more replies to a comment + asset_id, // the id of the asset we're currently on + sort // CHRONOLOGICAL or REVERSE_CHRONOLOGICAL }, updateQuery: (oldData, {fetchMoreResult:{data:{new_top_level_comments}}}) => { let updatedAsset; - if (parent_id) { + if (!isNil(oldData.comment)) { // loaded replies on a highlighted (permalinked) comment + + let comment = {}; + if (oldData.comment && oldData.comment.parent) { + + // put comments (replies) onto the oldData.comment.parent object + // the initial comment permalinked was a reply + const uniqReplies = uniqBy([...new_top_level_comments, ...oldData.comment.parent.replies], 'id'); + comment.parent = {...oldData.comment.parent, replies: sortBy(uniqReplies, 'created_at')}; + } else if (oldData.comment) { + + // put the comments (replies) directly onto oldData.comment + // the initial comment permalinked was a top-level comment + const uniqReplies = uniqBy([...new_top_level_comments, ...oldData.comment.replies], 'id'); + comment.replies = sortBy(uniqReplies, 'created_at'); + } + + updatedAsset = { + ...oldData, + comment: { + ...oldData.comment, + ...comment + } + }; + + } else if (parent_id) { // If loading more replies - // If loading more replies updatedAsset = { ...oldData, asset: { @@ -78,9 +103,8 @@ export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, s }) } }; - } else { + } else { // If loading more top-level comments - // If loading more top-level comments updatedAsset = { ...oldData, asset: { @@ -99,6 +123,8 @@ export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, s // load the comment stream. export const queryStream = graphql(STREAM_QUERY, { options: () => { + + // where the query string is from the embeded iframe url let comment_id = getQueryVariable('comment_id'); let has_comment = comment_id != null; diff --git a/client/coral-framework/graphql/queries/streamQuery.graphql b/client/coral-framework/graphql/queries/streamQuery.graphql index f3b8576dd..c9f9d3d5b 100644 --- a/client/coral-framework/graphql/queries/streamQuery.graphql +++ b/client/coral-framework/graphql/queries/streamQuery.graphql @@ -1,8 +1,15 @@ #import "../fragments/commentView.graphql" query AssetQuery($asset_id: ID, $asset_url: String!, $comment_id: ID!, $has_comment: Boolean!) { + # the comment here is for loading one comment and it's children, probably after following a permalink + # $has_comment is derived from the comment_id query param in the iframe url, + # which is in turn pulled from the host page url comment(id: $comment_id) @include(if: $has_comment) { ...commentView + replyCount + replies { + ...commentView + } parent { ...commentView replyCount From f37c76866292787b25b33d3d470e6f374e64c643 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 29 Mar 2017 15:21:41 -0600 Subject: [PATCH 5/8] only highlight the actual comment permalinked --- client/coral-embed-stream/src/Comment.js | 49 ++++++++++++------------ client/coral-embed-stream/src/Embed.js | 2 + 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index caffb6894..d970a5f35 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -117,7 +117,6 @@ class Comment extends React.Component { 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' : ''; commentClass += comment.id === 'pending' ? ` ${styles.pendingComment}` : ''; // call a function, and if it errors, call addNotification('error', ...) (e.g. to show user a snackbar) @@ -147,18 +146,19 @@ class Comment extends React.Component { id={`c_${comment.id}`} style={{marginLeft: depth * 30}}>
- - { isStaff(comment.tags) - ? Staff +
+ + { isStaff(comment.tags) + ? Staff : null } - { commentIsBest(comment) - ? + { commentIsBest(comment) + ? : null } - + - +
-
- - - - - - +
+ + + + + + +
{ activeReplyBox === comment.id diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 5818da2dd..12d6733d1 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -97,6 +97,8 @@ class Embed extends Component { const {closedAt, countCache = {}} = this.props.asset; const {loading, asset, refetch, comment} = this.props.data; const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; + + // even though the permalinked comment is the highlighted one, we're displaying its parent + replies const highlightedComment = comment && comment.parent ? comment.parent : comment; const openStream = closedAt === null; From fd968b207056fcfd516dc7f140849715756328a9 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 30 Mar 2017 10:50:58 -0600 Subject: [PATCH 6/8] position permalink popup given different comment scenarios --- client/coral-embed-stream/src/Comment.css | 1 + client/coral-embed-stream/style/default.css | 3 --- client/coral-plugin-permalinks/PermalinkButton.js | 13 +++++++++++-- client/coral-plugin-permalinks/styles.css | 11 ++--------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.css b/client/coral-embed-stream/src/Comment.css index 01022ae66..4383cbc5b 100644 --- a/client/coral-embed-stream/src/Comment.css +++ b/client/coral-embed-stream/src/Comment.css @@ -5,6 +5,7 @@ .Comment { margin-bottom: 15px; + position: relative; } .pendingComment { diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 4c427543e..06c524d1d 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -306,10 +306,7 @@ button.comment__action-button[disabled], display: none; background-color: white; border: 1px solid black; - width: calc(100% - 15px); position: absolute; - top: 70px; - right: 0; padding: 5px; } diff --git a/client/coral-plugin-permalinks/PermalinkButton.js b/client/coral-plugin-permalinks/PermalinkButton.js index 4c6cbb571..464155b2e 100644 --- a/client/coral-plugin-permalinks/PermalinkButton.js +++ b/client/coral-plugin-permalinks/PermalinkButton.js @@ -23,6 +23,10 @@ class PermalinkButton extends React.Component { } toggle () { + + // I wish I could position this with a stylesheet, but top-level comments with + // nested replies throws everything off, as well as very long comments + this.popover.style.top = `${this.linkButton.offsetTop - 80}px`; this.setState({popoverOpen: !this.state.popoverOpen}); } @@ -48,11 +52,16 @@ class PermalinkButton extends React.Component { const {copySuccessful, copyFailure} = this.state; return (
- -
+
this.popover = ref} + className={`${name}-popover ${styles.container} ${this.state.popoverOpen ? 'active' : ''}`}> Date: Thu, 30 Mar 2017 15:36:05 -0600 Subject: [PATCH 7/8] add polling to permalink page --- client/coral-embed-stream/src/Embed.js | 27 +++++++++++++++++++++---- client/coral-embed-stream/src/Stream.js | 21 ------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 12d6733d1..3f3c395f8 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -11,6 +11,7 @@ import {TabBar, Tab, TabContent, Spinner, Button} from 'coral-ui'; const {logout, showSignInDialog, requestConfirmEmail} = authActions; const {addNotification, clearNotification} = notificationActions; const {fetchAssetSuccess} = assetActions; +import {NEW_COMMENT_COUNT_POLL_INTERVAL} from 'coral-framework/constants/comments'; import {queryStream} from 'coral-framework/graphql/queries'; import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag} from 'coral-framework/graphql/mutations'; @@ -31,7 +32,7 @@ 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 HighlightedComment from './Comment'; import LoadMore from './LoadMore'; import NewCount from './NewCount'; @@ -68,10 +69,30 @@ class Embed extends Component { pym.sendMessage('childReady'); } + componentWillUnmount () { + clearInterval(this.state.countPoll); + } + componentWillReceiveProps (nextProps) { const {loadAsset} = this.props; if(!isEqual(nextProps.data.asset, this.props.data.asset)) { loadAsset(nextProps.data.asset); + + const {getCounts, updateCountCache} = this.props; + const {asset} = nextProps.data; + + updateCountCache(asset.id, asset.commentCount); + + this.setState({ + countPoll: setInterval(() => { + const {asset} = this.props.data; + getCounts({ + asset_id: asset.id, + limit: asset.comments.length, + sort: 'REVERSE_CHRONOLOGICAL' + }); + }, NEW_COMMENT_COUNT_POLL_INTERVAL) + }); } } @@ -186,7 +207,7 @@ class Embed extends Component { {/* the highlightedComment is isolated after the user followed a permalink */} { highlightedComment - ? getCounts({ - asset_id: asset.id, - limit: asset.comments.length, - sort: 'REVERSE_CHRONOLOGICAL' - }), NEW_COMMENT_COUNT_POLL_INTERVAL), - }); - } - - componentWillUnmount() { - clearInterval(this.state.countPoll); - } - render () { const { comments, From 8196cb9c24f5a05590a38a73da1ef9ec071340a3 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 31 Mar 2017 13:37:28 -0600 Subject: [PATCH 8/8] load all the replies in one go --- client/coral-embed-stream/src/LoadMore.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/LoadMore.js b/client/coral-embed-stream/src/LoadMore.js index 89dc0104c..1a58c8d19 100644 --- a/client/coral-embed-stream/src/LoadMore.js +++ b/client/coral-embed-stream/src/LoadMore.js @@ -5,7 +5,7 @@ import {ADDTL_COMMENTS_ON_LOAD_MORE} from 'coral-framework/constants/comments'; import {Button} from 'coral-ui'; const lang = new I18n(translations); -const loadMoreComments = (assetId, comments, loadMore, parentId) => { +const loadMoreComments = (assetId, comments, loadMore, parentId, replyCount) => { let cursor = null; if (comments.length) { @@ -15,7 +15,7 @@ const loadMoreComments = (assetId, comments, loadMore, parentId) => { } loadMore({ - limit: ADDTL_COMMENTS_ON_LOAD_MORE, + limit: parentId ? replyCount : ADDTL_COMMENTS_ON_LOAD_MORE, cursor, asset_id: assetId, parent_id: parentId, @@ -48,7 +48,7 @@ class LoadMore extends React.Component {