From df859dc7825b3df2f1591d1818d93d9f239e0cf6 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 17 Apr 2017 14:12:45 -0600 Subject: [PATCH 01/28] add all tab --- client/coral-admin/src/AppRouter.js | 3 ++ .../ModerationQueue/ModerationContainer.js | 4 +++ .../ModerationQueue/components/LoadMore.js | 2 +- .../components/ModerationMenu.js | 28 ++++++++++--------- .../coral-admin/src/graphql/queries/index.js | 3 ++ .../src/graphql/queries/modQueueQuery.graphql | 9 ++++++ 6 files changed, 35 insertions(+), 14 deletions(-) diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 2bd59b79d..bfb2e197c 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -39,6 +39,9 @@ const routes = ( {/* Moderation Routes */} + + + diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 61352b921..47e5674cb 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -135,6 +135,9 @@ class ModerationContainer extends Component { const comments = data[activeTab]; let activeTabCount; switch(activeTab) { + case 'all': + activeTabCount = data.allCount; + break; case 'premod': activeTabCount = data.premodCount; break; @@ -151,6 +154,7 @@ class ModerationContainer extends Component { { - const premodPath = asset - ? `/admin/moderate/premod/${asset.id}` - : '/admin/moderate/premod'; - const rejectPath = asset - ? `/admin/moderate/rejected/${asset.id}` - : '/admin/moderate/rejected'; - const flagPath = asset - ? `/admin/moderate/flagged/${asset.id}` - : '/admin/moderate/flagged'; + + function getPath (type) { + return asset ? `/admin/moderate/${type}/${asset.id}` : `/admin/moderate/${type}`; + } return (
@@ -27,19 +22,25 @@ const ModerationMenu = (
+ {lang.t('modqueue.all')} + + {lang.t('modqueue.premod')} {lang.t('modqueue.flagged')} {lang.t('modqueue.rejected')} @@ -59,6 +60,7 @@ const ModerationMenu = ( }; ModerationMenu.propTypes = { + allCount: PropTypes.number.isRequired, premodCount: PropTypes.number.isRequired, rejectedCount: PropTypes.number.isRequired, flaggedCount: PropTypes.number.isRequired, diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js index f0e8e5b70..4f1cc2d13 100644 --- a/client/coral-admin/src/graphql/queries/index.js +++ b/client/coral-admin/src/graphql/queries/index.js @@ -36,6 +36,9 @@ export const getMetrics = graphql(METRICS, { export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) => { let statuses; switch(tab) { + case 'all': + statuses = null; + break; case 'premod': statuses = ['PREMOD']; break; diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index b4f2a20a0..09fab4a66 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -1,6 +1,12 @@ #import "../fragments/commentView.graphql" query ModQueue ($asset_id: ID, $sort: SORT_ORDER) { + all: comments(query: { + asset_id: $asset_id, + sort: $sort + }) { + ...commentView + } premod: comments(query: { statuses: [PREMOD], asset_id: $asset_id, @@ -34,6 +40,9 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) { title url } + allCount: commentCount(query: { + asset_id: $asset_id + }) premodCount: commentCount(query: { statuses: [PREMOD], asset_id: $asset_id From 449a6f67f1fd155aee7affa120fe740d77310688 Mon Sep 17 00:00:00 2001 From: riley Date: Mon, 17 Apr 2017 15:37:00 -0600 Subject: [PATCH 02/28] add some tab icons --- .../ModerationQueue/components/ModerationMenu.js | 9 +++++---- .../src/containers/ModerationQueue/components/styles.css | 5 +++++ client/coral-ui/components/Icon.js | 6 +++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index dfeb4f4b1..032aa740e 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -4,6 +4,7 @@ import styles from './styles.css'; import {SelectField, Option} from 'react-mdl-selectfield'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations.json'; +import {Icon} from 'coral-ui'; import {Link} from 'react-router'; const lang = new I18n(translations); @@ -25,25 +26,25 @@ const ModerationMenu = ( to={getPath('all')} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}> - {lang.t('modqueue.all')} + {lang.t('modqueue.all')} - {lang.t('modqueue.premod')} + {lang.t('modqueue.premod')} - {lang.t('modqueue.flagged')} + {lang.t('modqueue.flagged')} - {lang.t('modqueue.rejected')} + {lang.t('modqueue.rejected')}
( ); +Icon.propTypes = { + name: PropTypes.string.isRequired +}; + export default Icon; From b40eed47512aa58648fe5fed94e52fa2fdb0fa66 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 18 Apr 2017 23:14:28 +0700 Subject: [PATCH 03/28] Move to components folder --- client/coral-embed-stream/src/{ => components}/Comment.css | 0 client/coral-embed-stream/src/{ => components}/Comment.js | 0 client/coral-embed-stream/src/{ => components}/Embed.js | 0 .../coral-embed-stream/src/{ => components}/IgnoreUserWizard.css | 0 .../coral-embed-stream/src/{ => components}/IgnoreUserWizard.js | 0 .../src/{ => components}/IgnoredCommentTombstone.js | 0 client/coral-embed-stream/src/{ => components}/LoadMore.js | 0 client/coral-embed-stream/src/{ => components}/NewCount.js | 0 client/coral-embed-stream/src/{ => components}/Stream.js | 0 client/coral-embed-stream/src/{ => components}/TopRightMenu.css | 0 client/coral-embed-stream/src/{ => components}/TopRightMenu.js | 0 client/coral-embed-stream/src/{ => components}/index.js | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename client/coral-embed-stream/src/{ => components}/Comment.css (100%) rename client/coral-embed-stream/src/{ => components}/Comment.js (100%) rename client/coral-embed-stream/src/{ => components}/Embed.js (100%) rename client/coral-embed-stream/src/{ => components}/IgnoreUserWizard.css (100%) rename client/coral-embed-stream/src/{ => components}/IgnoreUserWizard.js (100%) rename client/coral-embed-stream/src/{ => components}/IgnoredCommentTombstone.js (100%) rename client/coral-embed-stream/src/{ => components}/LoadMore.js (100%) rename client/coral-embed-stream/src/{ => components}/NewCount.js (100%) rename client/coral-embed-stream/src/{ => components}/Stream.js (100%) rename client/coral-embed-stream/src/{ => components}/TopRightMenu.css (100%) rename client/coral-embed-stream/src/{ => components}/TopRightMenu.js (100%) rename client/coral-embed-stream/src/{ => components}/index.js (100%) diff --git a/client/coral-embed-stream/src/Comment.css b/client/coral-embed-stream/src/components/Comment.css similarity index 100% rename from client/coral-embed-stream/src/Comment.css rename to client/coral-embed-stream/src/components/Comment.css diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/components/Comment.js similarity index 100% rename from client/coral-embed-stream/src/Comment.js rename to client/coral-embed-stream/src/components/Comment.js diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/components/Embed.js similarity index 100% rename from client/coral-embed-stream/src/Embed.js rename to client/coral-embed-stream/src/components/Embed.js diff --git a/client/coral-embed-stream/src/IgnoreUserWizard.css b/client/coral-embed-stream/src/components/IgnoreUserWizard.css similarity index 100% rename from client/coral-embed-stream/src/IgnoreUserWizard.css rename to client/coral-embed-stream/src/components/IgnoreUserWizard.css diff --git a/client/coral-embed-stream/src/IgnoreUserWizard.js b/client/coral-embed-stream/src/components/IgnoreUserWizard.js similarity index 100% rename from client/coral-embed-stream/src/IgnoreUserWizard.js rename to client/coral-embed-stream/src/components/IgnoreUserWizard.js diff --git a/client/coral-embed-stream/src/IgnoredCommentTombstone.js b/client/coral-embed-stream/src/components/IgnoredCommentTombstone.js similarity index 100% rename from client/coral-embed-stream/src/IgnoredCommentTombstone.js rename to client/coral-embed-stream/src/components/IgnoredCommentTombstone.js diff --git a/client/coral-embed-stream/src/LoadMore.js b/client/coral-embed-stream/src/components/LoadMore.js similarity index 100% rename from client/coral-embed-stream/src/LoadMore.js rename to client/coral-embed-stream/src/components/LoadMore.js diff --git a/client/coral-embed-stream/src/NewCount.js b/client/coral-embed-stream/src/components/NewCount.js similarity index 100% rename from client/coral-embed-stream/src/NewCount.js rename to client/coral-embed-stream/src/components/NewCount.js diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/components/Stream.js similarity index 100% rename from client/coral-embed-stream/src/Stream.js rename to client/coral-embed-stream/src/components/Stream.js diff --git a/client/coral-embed-stream/src/TopRightMenu.css b/client/coral-embed-stream/src/components/TopRightMenu.css similarity index 100% rename from client/coral-embed-stream/src/TopRightMenu.css rename to client/coral-embed-stream/src/components/TopRightMenu.css diff --git a/client/coral-embed-stream/src/TopRightMenu.js b/client/coral-embed-stream/src/components/TopRightMenu.js similarity index 100% rename from client/coral-embed-stream/src/TopRightMenu.js rename to client/coral-embed-stream/src/components/TopRightMenu.js diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/components/index.js similarity index 100% rename from client/coral-embed-stream/src/index.js rename to client/coral-embed-stream/src/components/index.js From af5be0469f845063a916cb050652461735be0153 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 18 Apr 2017 23:20:39 +0700 Subject: [PATCH 04/28] Refactor embed --- .../coral-embed-stream/src/actions/stream.js | 4 + .../src/components/Comment.js | 2 +- .../src/components/Embed.js | 351 ++++-------------- .../src/components/LoadMore.js | 2 +- .../src/components/NewCount.js | 10 +- .../src/components/Stream.js | 234 +++++++++--- .../src/constants/stream.js | 4 + .../src/containers/Embed.js | 103 +++++ .../src/{components => }/index.js | 7 +- .../coral-embed-stream/src/reducers/index.js | 5 + .../coral-embed-stream/src/reducers/stream.js | 23 ++ client/coral-framework/actions/asset.js | 1 - client/coral-framework/constants/asset.js | 1 - client/coral-framework/constants/comments.js | 2 - .../graphql/queries/streamQuery.graphql | 4 + client/coral-framework/reducers/asset.js | 3 - client/coral-framework/services/store.js | 19 +- client/coral-plugin-commentbox/CommentBox.js | 15 +- .../containers/ProfileContainer.js | 14 +- .../coral-sign-in/components/FakeComment.js | 2 +- 20 files changed, 431 insertions(+), 375 deletions(-) create mode 100644 client/coral-embed-stream/src/actions/stream.js create mode 100644 client/coral-embed-stream/src/constants/stream.js create mode 100644 client/coral-embed-stream/src/containers/Embed.js rename client/coral-embed-stream/src/{components => }/index.js (63%) create mode 100644 client/coral-embed-stream/src/reducers/index.js create mode 100644 client/coral-embed-stream/src/reducers/stream.js delete mode 100644 client/coral-framework/constants/comments.js diff --git a/client/coral-embed-stream/src/actions/stream.js b/client/coral-embed-stream/src/actions/stream.js new file mode 100644 index 000000000..eea2ff5c4 --- /dev/null +++ b/client/coral-embed-stream/src/actions/stream.js @@ -0,0 +1,4 @@ +import * as actions from '../constants/stream'; + +export const setActiveReplyBox = (id) => ({type: actions.SET_ACTIVE_REPLY_BOX, id}); +export const setCommentCountCache = (amount) => ({type: actions.SET_COMMENT_COUNT_CACHE, amount}); diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index 4b01e19a7..ea0b34e5b 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -18,8 +18,8 @@ import {ReplyBox, ReplyButton} from 'coral-plugin-replies'; import FlagComment from 'coral-plugin-flags/FlagComment'; import LikeButton from 'coral-plugin-likes/LikeButton'; import {BestButton, IfUserCanModifyBest, BEST_TAG, commentIsBest, BestIndicator} from 'coral-plugin-best/BestButton'; -import LoadMore from 'coral-embed-stream/src/LoadMore'; import {Slot} from 'coral-framework'; +import LoadMore from './LoadMore'; import IgnoredCommentTombstone from './IgnoredCommentTombstone'; import {TopRightMenu} from './TopRightMenu'; diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index b67e9e1c4..e1da63c28 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -1,58 +1,39 @@ import React from 'react'; -import {compose} from 'react-apollo'; -import {connect} from 'react-redux'; -import isEqual from 'lodash/isEqual'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-framework/translations'; const lang = new I18n(translations); -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, ignoreUser} from 'coral-framework/graphql/mutations'; -import {editName} from 'coral-framework/actions/user'; -import {updateCountCache, viewAllComments} from 'coral-framework/actions/asset'; -import {notificationActions, authActions, assetActions, pym} from 'coral-framework'; +import {TabBar, Tab, TabContent, Button} from 'coral-ui'; import Stream from './Stream'; -import InfoBox from 'coral-plugin-infobox/InfoBox'; -import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; -import {ModerationLink} from 'coral-plugin-moderation'; import Count from 'coral-plugin-comment-count/CommentCount'; -import CommentBox from 'coral-plugin-commentbox/CommentBox'; import UserBox from 'coral-sign-in/components/UserBox'; -import SignInContainer from 'coral-sign-in/containers/SignInContainer'; -import SuspendedAccount from 'coral-framework/components/SuspendedAccount'; -import ChangeUsernameContainer from '../../coral-sign-in/containers/ChangeUsernameContainer'; import ProfileContainer from 'coral-settings/containers/ProfileContainer'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; -import HighlightedComment from './Comment'; -import LoadMore from './LoadMore'; -import NewCount from './NewCount'; -class Embed extends React.Component { +export default class Embed extends React.Component { + state = { + activeTab: 0, + }; - constructor(props) { - super(props); - this.state = { - activeTab: 0, - showSignInDialog: false, - activeReplyBox: '' - }; - } + exitHighlighting = () => { + this.props.viewAllComments(); + + // TODO: don't rely on refetching. + this.props.data.refetch(); + }; changeTab = (tab) => { - - // Everytime the comes from another tab, the Stream needs to be updated. if (tab === 0) { - this.props.viewAllComments(); - this.props.data.refetch(); + if (this.props.data.comment) { + this.exitHighlighting(); + } + else { + + // TODO: don't rely on refetching. + this.props.data.refetch(); + } } this.setState({ @@ -60,105 +41,16 @@ class Embed extends React.Component { }); } - static propTypes = { - data: React.PropTypes.shape({ - loading: React.PropTypes.bool, - error: React.PropTypes.object - }).isRequired, - - // dispatch action to add a tag to a comment - addCommentTag: React.PropTypes.func, - - // dispatch action to remove a tag from a comment - removeCommentTag: React.PropTypes.func, - - // dispatch action to ignore another user - ignoreUser: React.PropTypes.func, - } - - componentDidMount () { - 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, asset: {countCache}} = this.props; - const {asset} = nextProps.data; - - if (!countCache) { - 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) - }); - } - } - - 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('coralStream'), 0); - } - } - - setActiveReplyBox = (reactKey) => { - if (!this.props.auth.user) { - 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 {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; - - const banned = user && user.status === 'BANNED'; - - const hasOlderComments = !!( - asset && - asset.lastComment && - asset.lastComment.id !== asset.comments[asset.comments.length - 1].id - ); + const {asset, comment} = this.props.data; + const {loggedIn, isAdmin, user, showSignInDialog} = this.props.auth; const expandForLogin = showSignInDialog ? { minHeight: document.body.scrollHeight + 200 } : {}; - if (!asset) { - return ; - } - - // Find the created_at date of the first comment. If no comments exist, set the date to a week ago. - const firstCommentDate = asset.comments[0] - ? asset.comments[0].created_at - : new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(); - - const userBox = this.props.logout().then(refetch)} changeTab={this.changeTab}/>; + const userBox = ; return (
@@ -169,137 +61,50 @@ class Embed extends React.Component { Configure Stream { - highlightedComment && - + comment && + } { loggedIn ? userBox : null } - { - openStream - ?
- - - - }> - { - user - ? - : null - } - -
- :

{asset.settings.closedMessage}

- } - {!loggedIn && } - {loggedIn && user && } - {loggedIn && } - - {/* the highlightedComment is isolated after the user followed a permalink */} - { - highlightedComment - ? - :
- -
- -
- -
- } +
- + { loggedIn ? userBox : null } - +
@@ -308,34 +113,18 @@ class Embed extends React.Component { } } -const mapStateToProps = state => ({ - auth: state.auth.toJS(), - userData: state.user.toJS(), - asset: state.asset.toJS(), -}); +Embed.propTypes = { + data: React.PropTypes.shape({ + loading: React.PropTypes.bool, + error: React.PropTypes.object + }).isRequired, -const mapDispatchToProps = dispatch => ({ - requestConfirmEmail: () => dispatch(requestConfirmEmail()), - loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)), - addNotification: (type, text) => addNotification(type, text), - clearNotification: () => dispatch(clearNotification()), - 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), -}); + // dispatch action to add a tag to a comment + addCommentTag: React.PropTypes.func, -export default compose( - connect(mapStateToProps, mapDispatchToProps), - postComment, - postFlag, - postLike, - postDontAgree, - addCommentTag, - removeCommentTag, - ignoreUser, - deleteAction, - queryStream, -)(Embed); + // dispatch action to remove a tag from a comment + removeCommentTag: React.PropTypes.func, + + // dispatch action to ignore another user + ignoreUser: React.PropTypes.func, +}; diff --git a/client/coral-embed-stream/src/components/LoadMore.js b/client/coral-embed-stream/src/components/LoadMore.js index 1a58c8d19..8c665a2db 100644 --- a/client/coral-embed-stream/src/components/LoadMore.js +++ b/client/coral-embed-stream/src/components/LoadMore.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-framework/translations.json'; -import {ADDTL_COMMENTS_ON_LOAD_MORE} from 'coral-framework/constants/comments'; +import {ADDTL_COMMENTS_ON_LOAD_MORE} from '../constants/stream'; import {Button} from 'coral-ui'; const lang = new I18n(translations); diff --git a/client/coral-embed-stream/src/components/NewCount.js b/client/coral-embed-stream/src/components/NewCount.js index 8d7333aab..bb31e6d1d 100644 --- a/client/coral-embed-stream/src/components/NewCount.js +++ b/client/coral-embed-stream/src/components/NewCount.js @@ -3,9 +3,9 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-framework/translations.json'; const lang = new I18n(translations); -const onLoadMoreClick = ({loadMore, commentCount, firstCommentDate, assetId, updateCountCache}) => (e) => { +const onLoadMoreClick = ({loadMore, commentCount, firstCommentDate, assetId, setCommentCountCache}) => (e) => { e.preventDefault(); - updateCountCache(assetId, commentCount); + setCommentCountCache(commentCount); loadMore({ limit: 500, cursor: firstCommentDate, @@ -15,11 +15,11 @@ const onLoadMoreClick = ({loadMore, commentCount, firstCommentDate, assetId, upd }; const NewCount = (props) => { - const newComments = props.commentCount - props.countCache; + const newComments = props.commentCount - props.commentCountCache; return
{ - props.countCache && newComments > 0 ? + props.commentCountCache && newComments > 0 ? @@ -89,7 +82,7 @@ export default class Embed extends React.Component { deleteAction={this.props.deleteAction} showSignInDialog={this.props.showSignInDialog} comments={asset.comments} - ignoredUsers={this.props.data.myIgnoredUsers} + ignoredUsers={this.props.data.myIgnoredUsers.map(u => u.id)} auth={this.props.auth} comment={this.props.data.comment} commentCountCache={this.props.commentCountCache} diff --git a/client/coral-embed-stream/src/constants/stream.js b/client/coral-embed-stream/src/constants/stream.js index 41e9dcd97..cb17edb2f 100644 --- a/client/coral-embed-stream/src/constants/stream.js +++ b/client/coral-embed-stream/src/constants/stream.js @@ -2,3 +2,4 @@ export const SET_ACTIVE_REPLY_BOX = 'SET_ACTIVE_REPLY_BOX'; export const SET_COMMENT_COUNT_CACHE = 'SET_COMMENT_COUNT_CACHE'; export const ADDTL_COMMENTS_ON_LOAD_MORE = 10; export const NEW_COMMENT_COUNT_POLL_INTERVAL = 20000; +export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS'; diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index 62a9b97af..808af8645 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -1,18 +1,20 @@ import React from 'react'; -import {compose} from 'react-apollo'; +import {compose, gql, graphql} from 'react-apollo'; import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import isEqual from 'lodash/isEqual'; +import uniqBy from 'lodash/uniqBy'; +import sortBy from 'lodash/sortBy'; +import isNil from 'lodash/isNil'; import {Spinner} from 'coral-ui'; -import {queryStream} from 'coral-framework/graphql/queries'; import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser} from 'coral-framework/graphql/mutations'; import {editName} from 'coral-framework/actions/user'; -import {viewAllComments} from 'coral-framework/actions/asset'; import {notificationActions, authActions, assetActions, pym} from 'coral-framework'; import {NEW_COMMENT_COUNT_POLL_INTERVAL} from '../constants/stream'; import Embed from '../components/Embed'; -import {setCommentCountCache, setActiveReplyBox} from '../actions/stream'; +import {setCommentCountCache, setActiveReplyBox, viewAllComments} from '../actions/stream'; +import * as Stream from './Stream'; const {logout, showSignInDialog, requestConfirmEmail} = authActions; const {addNotification, clearNotification} = notificationActions; @@ -69,10 +71,195 @@ class EmbedContainer extends React.Component { } } +const fragments = { + commentView: gql` + fragment commentView on Comment { + id + body + created_at + status + tags { + name + } + user { + id + name: username + } + action_summaries { + ...actionSummaryView + } + } + `, + actionSummaryView: gql` + fragment actionSummaryView on ActionSummary { + __typename + count + current_user { + id + created_at + } + } + `, +}; + +const LOAD_COMMENT_COUNTS_QUERY = gql` + query LoadCommentCounts($asset_id: ID, $limit: Int = 5, $sort: SORT_ORDER) { + asset(id: $asset_id) { + id + commentCount + comments(sort: $sort, limit: $limit) { + id + replyCount + } + } + } +`; + +const LOAD_MORE_QUERY = gql` + query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) { + new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) { + ...commentView + replyCount(excludeIgnored: $excludeIgnored) + replies(limit: 3) { + ...commentView + } + } + } + ${fragments.commentView} + ${fragments.actionSummaryView} +`; + +const STREAM_QUERY = gql` + query StreamQuery($assetId: ID, $assetUrl: String, $commentId: ID!, $hasComment: Boolean!, $excludeIgnored: Boolean) { + __typename + ...Stream_root + } + ${Stream.fragment} +`; + +// get the counts of the top-level comments +const getCounts = (data) => ({asset_id, limit, sort}) => { + return data.fetchMore({ + query: LOAD_COMMENT_COUNTS_QUERY, + variables: { + asset_id, + limit, + sort, + excludeIgnored: data.variables.excludeIgnored, + }, + updateQuery: (oldData, {fetchMoreResult:{asset}}) => { + return { + ...oldData, + asset: { + ...oldData.asset, + commentCount: asset.commentCount + } + }; + } + }); +}; + +// handle paginated requests for more Comments pertaining to the Asset +const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, sort}, newComments) => { + return data.fetchMore({ + query: LOAD_MORE_QUERY, + variables: { + 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 + excludeIgnored: data.variables.excludeIgnored, + }, + updateQuery: (oldData, {fetchMoreResult:{new_top_level_comments}}) => { + let updatedAsset; + + 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 + + updatedAsset = { + ...oldData, + asset: { + ...oldData.asset, + comments: oldData.asset.comments.map(comment => { + + // since the dipslayed replies and the returned replies can overlap, + // pull out the unique ones. + const uniqueReplies = uniqBy([...new_top_level_comments, ...comment.replies], 'id'); + + // since we just gave the returned replies precedence, they're now out of order. + // resort according to date. + return comment.id === parent_id + ? {...comment, replies: sortBy(uniqueReplies, 'created_at')} + : comment; + }) + } + }; + } else { // If loading more top-level comments + + updatedAsset = { + ...oldData, + asset: { + ...oldData.asset, + comments: newComments ? [...new_top_level_comments.reverse(), ...oldData.asset.comments] + : [...oldData.asset.comments, ...new_top_level_comments] + } + }; + } + + return updatedAsset; + } + }); +}; + +export const withQuery = graphql(STREAM_QUERY, { + options: ({auth, commentId, assetId, assetUrl}) => ({ + variables: { + assetId, + assetUrl, + commentId, + hasComment: commentId !== '', + excludeIgnored: Boolean(auth && auth.user && auth.user.id), + }, + }), + props: ({data}) => ({ + data, + loadMore: loadMore(data), + getCounts: getCounts(data), + }) +}); + const mapStateToProps = state => ({ auth: state.auth.toJS(), commentCountCache: state.stream.commentCountCache, activeReplyBox: state.stream.activeReplyBox, + commentId: state.stream.commentId, + assetId: state.stream.assetId, + assetUrl: state.stream.assetUrl, }); const mapDispatchToProps = dispatch => @@ -99,5 +286,6 @@ export default compose( removeCommentTag, ignoreUser, deleteAction, - queryStream, + withQuery, )(EmbedContainer); + diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js new file mode 100644 index 000000000..e7c1153c2 --- /dev/null +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -0,0 +1,88 @@ +import {gql} from 'react-apollo'; + +const commentViewFragment = gql` + fragment commentView on Comment { + id + body + created_at + status + tags { + name + } + user { + id + name: username + } + action_summaries { + ...actionSummaryView + } + } +`; + +const actionSummaryViewFragment = gql` + fragment actionSummaryView on ActionSummary { + __typename + count + current_user { + id + created_at + } + } +`; + +export const fragment = gql` + fragment Stream_root on RootQuery { + comment(id: $commentId) @include(if: $hasComment) { + ...commentView + replyCount(excludeIgnored: $excludeIgnored) + replies { + ...commentView + } + parent { + ...commentView + replyCount(excludeIgnored: $excludeIgnored) + replies { + ...commentView + } + } + } + asset(id: $assetId, url: $assetUrl) { + id + title + url + closedAt + created_at + settings { + moderation + infoBoxEnable + infoBoxContent + premodLinksEnable + questionBoxEnable + questionBoxContent + closeTimeout + closedMessage + charCountEnable + charCount + requireEmailConfirmation + } + lastComment { + id + } + commentCount(excludeIgnored: $excludeIgnored) + totalCommentCount(excludeIgnored: $excludeIgnored) + comments(limit: 10, excludeIgnored: $excludeIgnored) { + ...commentView + replyCount(excludeIgnored: $excludeIgnored) + replies(limit: 3, excludeIgnored: $excludeIgnored) { + ...commentView + } + } + } + myIgnoredUsers { + id, + username, + } + } + ${commentViewFragment} + ${actionSummaryViewFragment} +`; diff --git a/client/coral-embed-stream/src/reducers/stream.js b/client/coral-embed-stream/src/reducers/stream.js index 24f5f2ddc..59f068530 100644 --- a/client/coral-embed-stream/src/reducers/stream.js +++ b/client/coral-embed-stream/src/reducers/stream.js @@ -1,8 +1,25 @@ import * as actions from '../constants/stream'; +function getQueryVariable(variable) { + let query = window.location.search.substring(1); + let vars = query.split('&'); + for (let i = 0; i < vars.length; i++) { + let pair = vars[i].split('='); + if (decodeURIComponent(pair[0]) === variable) { + return decodeURIComponent(pair[1]); + } + } + + // If not found, return null. + return null; +} + const initialState = { activeReplyBox: '', commentCountCache: -1, + assetId: getQueryVariable('asset_id'), + assetUrl: getQueryVariable('asset_url'), + commentId: getQueryVariable('comment_id'), }; export default function stream(state = initialState, action) { @@ -17,6 +34,11 @@ export default function stream(state = initialState, action) { ...state, commentCountCache: action.amount, }; + case actions.VIEW_ALL_COMMENTS: + return { + ...state, + commentId: '', + }; default: return state; } diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js index ecba0fb57..6de4eaf9f 100644 --- a/client/coral-framework/actions/asset.js +++ b/client/coral-framework/actions/asset.js @@ -1,7 +1,6 @@ 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'; @@ -50,36 +49,3 @@ export const updateOpenStatus = status => dispatch => { } }; -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 88dc89b5e..c26dd099d 100644 --- a/client/coral-framework/constants/asset.js +++ b/client/coral-framework/constants/asset.js @@ -9,4 +9,3 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE'; export const OPEN_COMMENTS = 'OPEN_COMMENTS'; export const CLOSE_COMMENTS = 'CLOSE_COMMENTS'; -export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS'; diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index 4a3abb48e..ed0579c25 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -10,8 +10,6 @@ import IGNORE_USER from './ignoreUser.graphql'; import STOP_IGNORING_USER from './stopIgnoringUser.graphql'; import MY_IGNORED_USERS from '../queries/myIgnoredUsers.graphql'; -import STREAM_QUERY from '../queries/streamQuery.graphql'; -import {variablesForStreamQuery} from '../queries'; import commentView from '../fragments/commentView.graphql'; @@ -155,6 +153,7 @@ export const removeCommentTag = graphql(REMOVE_COMMENT_TAG, { }}), }); +// TODO: don't rely on refetching. export const ignoreUser = graphql(IGNORE_USER, { props: ({mutate}) => ({ ignoreUser: ({id}) => { @@ -169,8 +168,9 @@ export const ignoreUser = graphql(IGNORE_USER, { }}), }); +// TODO: don't rely on refetching. export const stopIgnoringUser = graphql(STOP_IGNORING_USER, { - props: ({mutate, ownProps}) => { + props: ({mutate}) => { return { stopIgnoringUser: ({id}) => { return mutate({ @@ -181,10 +181,7 @@ export const stopIgnoringUser = graphql(STOP_IGNORING_USER, { { query: MY_IGNORED_USERS, }, - { - query: STREAM_QUERY, - variables: variablesForStreamQuery(ownProps), - } + 'StreamQuery', ] }); } diff --git a/client/coral-framework/graphql/queries/commentQuery.graphql b/client/coral-framework/graphql/queries/commentQuery.graphql deleted file mode 100644 index 83f92b8f5..000000000 --- a/client/coral-framework/graphql/queries/commentQuery.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "../fragments/commentView.graphql" - -query commentQuery($id: ID!) { - comment(id: $id) { - ...commentView - parent { - ...commentView - replies { - ...commentView - } - } - } -} diff --git a/client/coral-framework/graphql/queries/getCounts.graphql b/client/coral-framework/graphql/queries/getCounts.graphql deleted file mode 100644 index ff09a0498..000000000 --- a/client/coral-framework/graphql/queries/getCounts.graphql +++ /dev/null @@ -1,10 +0,0 @@ -query LoadCommentCounts($asset_id: ID, $limit: Int = 5, $sort: SORT_ORDER) { - asset(id: $asset_id) { - id - commentCount - comments(sort: $sort, limit: $limit) { - id - replyCount - } - } -} diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index 5dbea5822..d76614a68 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -1,153 +1,6 @@ import {graphql} from 'react-apollo'; -import STREAM_QUERY from './streamQuery.graphql'; -import LOAD_MORE from './loadMore.graphql'; -import GET_COUNTS from './getCounts.graphql'; import MY_COMMENT_HISTORY from './myCommentHistory.graphql'; import MY_IGNORED_USERS from './myIgnoredUsers.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); - let vars = query.split('&'); - for (let i = 0; i < vars.length; i++) { - let pair = vars[i].split('='); - if (decodeURIComponent(pair[0]) === variable) { - return decodeURIComponent(pair[1]); - } - } - - // If not found, return null. - return null; -} - -// get the counts of the top-level comments -export const getCounts = (data) => ({asset_id, limit, sort}) => { - return data.fetchMore({ - query: GET_COUNTS, - variables: { - asset_id, - limit, - sort, - excludeIgnored: data.variables.excludeIgnored, - }, - updateQuery: (oldData, {fetchMoreResult:{asset}}) => { - return { - ...oldData, - asset: { - ...oldData.asset, - commentCount: asset.commentCount - } - }; - } - }); -}; - -// 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, - variables: { - 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 - excludeIgnored: data.variables.excludeIgnored, - }, - updateQuery: (oldData, {fetchMoreResult:{new_top_level_comments}}) => { - let updatedAsset; - - 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 - - updatedAsset = { - ...oldData, - asset: { - ...oldData.asset, - comments: oldData.asset.comments.map(comment => { - - // since the dipslayed replies and the returned replies can overlap, - // pull out the unique ones. - const uniqueReplies = uniqBy([...new_top_level_comments, ...comment.replies], 'id'); - - // since we just gave the returned replies precedence, they're now out of order. - // resort according to date. - return comment.id === parent_id - ? {...comment, replies: sortBy(uniqueReplies, 'created_at')} - : comment; - }) - } - }; - } else { // If loading more top-level comments - - updatedAsset = { - ...oldData, - asset: { - ...oldData.asset, - comments: newComments ? [...new_top_level_comments.reverse(), ...oldData.asset.comments] - : [...oldData.asset.comments, ...new_top_level_comments] - } - }; - } - - return updatedAsset; - } - }); -}; - -export const variablesForStreamQuery = ({auth}) => { - - // where the query string is from the embeded iframe url - let comment_id = getQueryVariable('comment_id'); - let has_comment = comment_id != null; - return { - asset_id: getQueryVariable('asset_id'), - asset_url: getQueryVariable('asset_url'), - comment_id: has_comment ? comment_id : 'no-comment', - has_comment, - excludeIgnored: Boolean(auth && auth.user && auth.user.id), - }; -}; - -// load the comment stream. -export const queryStream = graphql(STREAM_QUERY, { - options: (props) => { - return { - variables: variablesForStreamQuery(props) - }; - }, - props: ({data}) => ({ - data, - loadMore: loadMore(data), - getCounts: getCounts(data), - }) -}); export const myCommentHistory = graphql(MY_COMMENT_HISTORY, {}); diff --git a/client/coral-framework/graphql/queries/loadMore.graphql b/client/coral-framework/graphql/queries/loadMore.graphql deleted file mode 100644 index b18f4d84e..000000000 --- a/client/coral-framework/graphql/queries/loadMore.graphql +++ /dev/null @@ -1,11 +0,0 @@ -#import "../fragments/commentView.graphql" - -query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) { - new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) { - ...commentView - replyCount(excludeIgnored: $excludeIgnored) - replies(limit: 3) { - ...commentView - } - } -} diff --git a/client/coral-framework/graphql/queries/streamQuery.graphql b/client/coral-framework/graphql/queries/streamQuery.graphql deleted file mode 100644 index 18718070d..000000000 --- a/client/coral-framework/graphql/queries/streamQuery.graphql +++ /dev/null @@ -1,57 +0,0 @@ -#import "../fragments/commentView.graphql" - -query AssetQuery($asset_id: ID, $asset_url: String, $comment_id: ID!, $has_comment: Boolean!, $excludeIgnored: 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(excludeIgnored: $excludeIgnored) - replies { - ...commentView - } - parent { - ...commentView - replyCount(excludeIgnored: $excludeIgnored) - replies { - ...commentView - } - } - } - asset(id: $asset_id, url: $asset_url) { - id - title - url - closedAt - created_at - settings { - moderation - infoBoxEnable - infoBoxContent - premodLinksEnable - questionBoxEnable - questionBoxContent - closeTimeout - closedMessage - charCountEnable - charCount - requireEmailConfirmation - } - lastComment { - id - } - commentCount(excludeIgnored: $excludeIgnored) - totalCommentCount(excludeIgnored: $excludeIgnored) - comments(limit: 10, excludeIgnored: $excludeIgnored) { - ...commentView - replyCount(excludeIgnored: $excludeIgnored) - replies(limit: 3, excludeIgnored: $excludeIgnored) { - ...commentView - } - } - } - myIgnoredUsers { - id, - username, - } -} From 0e6b0c01fd7565ad4c0e2e3485218752fcf0cb88 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 19 Apr 2017 21:06:57 +0700 Subject: [PATCH 07/28] Fix issue with ignoredUsers --- graph/resolvers/root_query.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index b26ecabca..8de1988ff 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -84,7 +84,9 @@ const RootQuery = { }, myIgnoredUsers: async (_, args, {user, loaders: {Users}}) => { - + if (user == null) { + return []; + } // get currentUser again since context.user was out of date when running test/graph/mutations/ignoreUser const currentUser = (await Users.getByQuery({ids: [user.id], limit: 1}))[0]; if ( ! (currentUser && Array.isArray(currentUser.ignoresUsers) && currentUser.ignoresUsers.length)) { From 34fee9439f971870512d9c305b67ffba92a3d74a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 19 Apr 2017 23:26:26 +0700 Subject: [PATCH 08/28] Move active tab to redux --- .../coral-embed-stream/src/actions/embed.js | 4 ++ .../src/components/Embed.js | 37 ++++++++----------- .../coral-embed-stream/src/constants/embed.js | 1 + .../src/containers/Embed.js | 3 ++ .../coral-embed-stream/src/reducers/embed.js | 17 +++++++++ .../coral-embed-stream/src/reducers/index.js | 4 +- 6 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 client/coral-embed-stream/src/actions/embed.js create mode 100644 client/coral-embed-stream/src/constants/embed.js create mode 100644 client/coral-embed-stream/src/reducers/embed.js diff --git a/client/coral-embed-stream/src/actions/embed.js b/client/coral-embed-stream/src/actions/embed.js new file mode 100644 index 000000000..09d68dbf1 --- /dev/null +++ b/client/coral-embed-stream/src/actions/embed.js @@ -0,0 +1,4 @@ +import * as actions from '../constants/embed'; + +export const setActiveTab = (tab) => ({type: actions.SET_ACTIVE_TAB, tab}); + diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 09d919c76..e84d86ec2 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -13,29 +13,24 @@ import RestrictedContent from 'coral-framework/components/RestrictedContent'; import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; export default class Embed extends React.Component { - state = { - activeTab: 0, - }; - changeTab = (tab) => { - if (tab === 0) { - if (this.props.data.comment) { - this.props.viewAllComments(); - } - else { - - // TODO: don't rely on refetching. - this.props.data.refetch(); - } + switch(tab) { + case 0: + this.props.setActiveTab('stream'); + break; + case 1: + this.props.setActiveTab('profile'); + break; + case 2: + this.props.setActiveTab('config'); + break; + default: + throw new Error(`Unknown tab ${tab}`); } - - this.setState({ - activeTab: tab - }); } render () { - const {activeTab} = this.state; + const {activeTab} = this.props; const {asset, comment} = this.props.data; const {loggedIn, isAdmin, user, showSignInDialog} = this.props.auth; @@ -63,7 +58,7 @@ export default class Embed extends React.Component { {lang.t('showAllComments')} } - + { loggedIn ? userBox : null } - + - + { loggedIn ? userBox : null } diff --git a/client/coral-embed-stream/src/constants/embed.js b/client/coral-embed-stream/src/constants/embed.js new file mode 100644 index 000000000..178e5d6e4 --- /dev/null +++ b/client/coral-embed-stream/src/constants/embed.js @@ -0,0 +1 @@ +export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index 808af8645..937cec89f 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -14,6 +14,7 @@ import {notificationActions, authActions, assetActions, pym} from 'coral-framewo import {NEW_COMMENT_COUNT_POLL_INTERVAL} from '../constants/stream'; import Embed from '../components/Embed'; import {setCommentCountCache, setActiveReplyBox, viewAllComments} from '../actions/stream'; +import {setActiveTab} from '../actions/embed'; import * as Stream from './Stream'; const {logout, showSignInDialog, requestConfirmEmail} = authActions; @@ -260,6 +261,7 @@ const mapStateToProps = state => ({ commentId: state.stream.commentId, assetId: state.stream.assetId, assetUrl: state.stream.assetUrl, + activeTab: state.embed.activeTab, }); const mapDispatchToProps = dispatch => @@ -274,6 +276,7 @@ const mapDispatchToProps = dispatch => viewAllComments, logout, setActiveReplyBox, + setActiveTab, }, dispatch); export default compose( diff --git a/client/coral-embed-stream/src/reducers/embed.js b/client/coral-embed-stream/src/reducers/embed.js new file mode 100644 index 000000000..e7fe5a533 --- /dev/null +++ b/client/coral-embed-stream/src/reducers/embed.js @@ -0,0 +1,17 @@ +import * as actions from '../constants/embed'; + +const initialState = { + activeTab: 'stream', +}; + +export default function stream(state = initialState, action) { + switch (action.type) { + case actions.SET_ACTIVE_TAB: + return { + ...state, + activeTab: action.tab, + }; + default: + return state; + } +} diff --git a/client/coral-embed-stream/src/reducers/index.js b/client/coral-embed-stream/src/reducers/index.js index 6a312a84a..a9049fc14 100644 --- a/client/coral-embed-stream/src/reducers/index.js +++ b/client/coral-embed-stream/src/reducers/index.js @@ -1,5 +1,7 @@ import stream from './stream'; +import embed from './embed'; export default { - stream + stream, + embed, }; From 3cd9e802dd81fd47506d3d7d4f69e775fc6f9d75 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 20 Apr 2017 00:37:45 +0700 Subject: [PATCH 09/28] WIP --- .../src/components/Embed.js | 5 +- .../src/containers/Stream.js | 218 ++++++++++++++---- 2 files changed, 174 insertions(+), 49 deletions(-) diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index e84d86ec2..f266fc79d 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -17,15 +17,16 @@ export default class Embed extends React.Component { switch(tab) { case 0: this.props.setActiveTab('stream'); + this.props.data.refetch(); break; case 1: this.props.setActiveTab('profile'); + this.props.data.refetch(); break; case 2: this.props.setActiveTab('config'); + this.props.data.refetch(); break; - default: - throw new Error(`Unknown tab ${tab}`); } } diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index e7c1153c2..cecc6edc3 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -1,4 +1,126 @@ +import React from 'react'; import {gql} from 'react-apollo'; +import Stream from '../components/Stream'; +import {NEW_COMMENT_COUNT_POLL_INTERVAL} from '../constants/stream'; + +export default class StreamContainer extends React.Component { + getCounts = ({asset_id, limit, sort}) => { + return this.props.data.fetchMore({ + query: LOAD_COMMENT_COUNTS_QUERY, + variables: { + asset_id, + limit, + sort, + excludeIgnored: data.variables.excludeIgnored, + }, + updateQuery: (oldData, {fetchMoreResult:{asset}}) => { + return { + ...oldData, + asset: { + ...oldData.asset, + commentCount: asset.commentCount + } + }; + } + }); + }; + + // handle paginated requests for more Comments pertaining to the Asset + loadMore = ({limit, cursor, parent_id = null, asset_id, sort}, newComments) => { + return this.props.data.fetchMore({ + query: LOAD_MORE_QUERY, + variables: { + 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 + excludeIgnored: data.variables.excludeIgnored, + }, + updateQuery: (oldData, {fetchMoreResult:{new_top_level_comments}}) => { + let updatedAsset; + + 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 + + updatedAsset = { + ...oldData, + asset: { + ...oldData.asset, + comments: oldData.asset.comments.map(comment => { + + // since the dipslayed replies and the returned replies can overlap, + // pull out the unique ones. + const uniqueReplies = uniqBy([...new_top_level_comments, ...comment.replies], 'id'); + + // since we just gave the returned replies precedence, they're now out of order. + // resort according to date. + return comment.id === parent_id + ? {...comment, replies: sortBy(uniqueReplies, 'created_at')} + : comment; + }) + } + }; + } else { // If loading more top-level comments + + updatedAsset = { + ...oldData, + asset: { + ...oldData.asset, + comments: newComments ? [...new_top_level_comments.reverse(), ...oldData.asset.comments] + : [...oldData.asset.comments, ...new_top_level_comments] + } + }; + } + + return updatedAsset; + } + }); + }; + + componentDidMount() { + this.countPoll = setInterval(() => { + const {asset} = this.props.data; + this.props.getCounts({ + asset_id: asset.id, + limit: asset.comments.length, + sort: 'REVERSE_CHRONOLOGICAL' + }); + }, NEW_COMMENT_COUNT_POLL_INTERVAL); + } + + componentWillUnmount() { + clearInterval(this.countPoll); + } + + render() { + return ; + } +} const commentViewFragment = gql` fragment commentView on Comment { @@ -30,59 +152,61 @@ const actionSummaryViewFragment = gql` } `; -export const fragment = gql` - fragment Stream_root on RootQuery { - comment(id: $commentId) @include(if: $hasComment) { - ...commentView - replyCount(excludeIgnored: $excludeIgnored) - replies { - ...commentView - } - parent { +StreamContainer.fragments = { + root: gql` + fragment Stream_root on RootQuery { + comment(id: $commentId) @include(if: $hasComment) { ...commentView replyCount(excludeIgnored: $excludeIgnored) replies { ...commentView } - } - } - asset(id: $assetId, url: $assetUrl) { - id - title - url - closedAt - created_at - settings { - moderation - infoBoxEnable - infoBoxContent - premodLinksEnable - questionBoxEnable - questionBoxContent - closeTimeout - closedMessage - charCountEnable - charCount - requireEmailConfirmation - } - lastComment { - id - } - commentCount(excludeIgnored: $excludeIgnored) - totalCommentCount(excludeIgnored: $excludeIgnored) - comments(limit: 10, excludeIgnored: $excludeIgnored) { - ...commentView - replyCount(excludeIgnored: $excludeIgnored) - replies(limit: 3, excludeIgnored: $excludeIgnored) { + parent { + ...commentView + replyCount(excludeIgnored: $excludeIgnored) + replies { ...commentView + } } } + asset(id: $assetId, url: $assetUrl) { + id + title + url + closedAt + created_at + settings { + moderation + infoBoxEnable + infoBoxContent + premodLinksEnable + questionBoxEnable + questionBoxContent + closeTimeout + closedMessage + charCountEnable + charCount + requireEmailConfirmation + } + lastComment { + id + } + commentCount(excludeIgnored: $excludeIgnored) + totalCommentCount(excludeIgnored: $excludeIgnored) + comments(limit: 10, excludeIgnored: $excludeIgnored) { + ...commentView + replyCount(excludeIgnored: $excludeIgnored) + replies(limit: 3, excludeIgnored: $excludeIgnored) { + ...commentView + } + } + } + myIgnoredUsers { + id, + username, + } } - myIgnoredUsers { - id, - username, - } - } - ${commentViewFragment} - ${actionSummaryViewFragment} -`; + ${commentViewFragment} + ${actionSummaryViewFragment} + `, +}; From 2743f6faf6acda66cdb44da93420b31d9d3b9aa1 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 21 Apr 2017 16:44:42 -0600 Subject: [PATCH 10/28] return all the known types --- client/coral-admin/src/graphql/queries/modQueueQuery.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index 437cec3ea..f124b87b5 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -2,6 +2,7 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) { all: comments(query: { + statuses: [NONE, PREMOD, ACCEPTED, REJECTED], asset_id: $asset_id, sort: $sort }) { From 11fb5421b503f41a2b649053b4e639ab9c5a8260 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 24 Apr 2017 19:27:54 +0700 Subject: [PATCH 11/28] Implement Stream Container --- .../coral-embed-stream/src/actions/embed.js | 8 +- .../src/components/Embed.js | 62 ++--- .../src/components/Stream.js | 15 +- .../src/containers/Embed.js | 217 ++---------------- .../src/containers/Stream.js | 88 ++++++- .../graphql/mutations/index.js | 15 +- client/coral-plugin-commentbox/CommentBox.js | 1 - 7 files changed, 127 insertions(+), 279 deletions(-) diff --git a/client/coral-embed-stream/src/actions/embed.js b/client/coral-embed-stream/src/actions/embed.js index 09d68dbf1..863494c68 100644 --- a/client/coral-embed-stream/src/actions/embed.js +++ b/client/coral-embed-stream/src/actions/embed.js @@ -1,4 +1,10 @@ import * as actions from '../constants/embed'; +import {viewAllComments} from './stream'; -export const setActiveTab = (tab) => ({type: actions.SET_ACTIVE_TAB, tab}); +export const setActiveTab = (tab) => (dispatch, getState) => { + dispatch({type: actions.SET_ACTIVE_TAB, tab}); + if (getState().stream.commentId) { + dispatch(viewAllComments()); + } +}; diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index ee0afe4cc..b8346db0f 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -5,7 +5,7 @@ const lang = new I18n(translations); import {TabBar, Tab, TabContent, Button} from 'coral-ui'; -import Stream from './Stream'; +import Stream from '../containers/Stream'; import Count from 'coral-plugin-comment-count/CommentCount'; import UserBox from 'coral-sign-in/components/UserBox'; import ProfileContainer from 'coral-settings/containers/ProfileContainer'; @@ -17,75 +17,50 @@ export default class Embed extends React.Component { switch(tab) { case 0: this.props.setActiveTab('stream'); - this.props.data.refetch(); break; case 1: this.props.setActiveTab('profile'); + + // TODO: move data fetching to profile container. this.props.data.refetch(); break; case 2: this.props.setActiveTab('config'); + + // TODO: move data fetching to config container. this.props.data.refetch(); break; } } render () { - const {activeTab} = this.props; - const {asset, comment} = this.props.data; - const {loggedIn, isAdmin, user, showSignInDialog} = this.props.auth; + const {activeTab, logout, viewAllComments, commentId} = this.props; + const {asset: {totalCommentCount}} = this.props.data; + const {loggedIn, isAdmin, user} = this.props.auth; - const expandForLogin = showSignInDialog ? { - minHeight: document.body.scrollHeight + 200 - } : {}; - - const userBox = ; + const userBox = ; return ( -
+
- + {lang.t('myProfile')} Configure Stream { - comment && + commentId && } { loggedIn ? userBox : null } - u.id) : []} - auth={this.props.auth} - comment={this.props.data.comment} - commentCountCache={this.props.commentCountCache} - refetch={this.props.data.refetch} - editName={this.props.editName} - setCommentCountCache={this.props.setCommentCountCache} - /> + @@ -107,13 +82,4 @@ Embed.propTypes = { loading: React.PropTypes.bool, error: React.PropTypes.object }).isRequired, - - // dispatch action to add a tag to a comment - addCommentTag: React.PropTypes.func, - - // dispatch action to remove a tag from a comment - removeCommentTag: React.PropTypes.func, - - // dispatch action to ignore another user - ignoreUser: React.PropTypes.func, }; diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 5b9fbdaa7..b4b23091c 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -24,8 +24,7 @@ class Stream extends React.Component { render () { const { - comments, - asset, + data: {asset, asset: {comments}, comment, myIgnoredUsers}, postItem, addNotification, postFlag, @@ -38,10 +37,7 @@ class Stream extends React.Component { removeCommentTag, pluginProps, ignoreUser, - ignoredUsers, auth: {loggedIn, isAdmin, user}, - comment, - refetch, commentCountCache, editName, } = this.props; @@ -62,7 +58,7 @@ class Stream extends React.Component { const firstCommentDate = asset.comments[0] ? asset.comments[0].created_at : new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(); - const commentIsIgnored = (comment) => ignoredUsers && ignoredUsers.includes(comment.user.id); + const commentIsIgnored = (comment) => myIgnoredUsers && myIgnoredUsers.includes(comment.user.id); return (
{ @@ -94,7 +90,6 @@ class Stream extends React.Component { assetId={asset.id} premod={asset.settings.moderation} isReply={false} - currentUser={this.props.auth.user} authorId={user.id} charCount={asset.settings.charCountEnable && asset.settings.charCount} /> : null @@ -111,7 +106,6 @@ class Stream extends React.Component { { highlightedComment ? { - const {asset} = this.props.data; - getCounts({ - asset_id: asset.id, - limit: asset.comments.length, - sort: 'REVERSE_CHRONOLOGICAL' - }); - }, NEW_COMMENT_COUNT_POLL_INTERVAL); } } @@ -73,172 +59,20 @@ class EmbedContainer extends React.Component { } } -const fragments = { - commentView: gql` - fragment commentView on Comment { - id - body - created_at +const EMBED_QUERY = gql` + query EmbedQuery($assetId: ID, $assetUrl: String, $commentId: ID!, $hasComment: Boolean!, $excludeIgnored: Boolean) { + asset(id: $assetId, url: $assetUrl) { + totalCommentCount(excludeIgnored: $excludeIgnored) + } + me { status - tags { - name - } - user { - id - name: username - } - action_summaries { - ...actionSummaryView - } } - `, - actionSummaryView: gql` - fragment actionSummaryView on ActionSummary { - __typename - count - current_user { - id - created_at - } - } - `, -}; - -const LOAD_COMMENT_COUNTS_QUERY = gql` - query LoadCommentCounts($asset_id: ID, $limit: Int = 5, $sort: SORT_ORDER) { - asset(id: $asset_id) { - id - commentCount - comments(sort: $sort, limit: $limit) { - id - replyCount - } - } - } -`; - -const LOAD_MORE_QUERY = gql` - query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) { - new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) { - ...commentView - replyCount(excludeIgnored: $excludeIgnored) - replies(limit: 3) { - ...commentView - } - } - } - ${fragments.commentView} - ${fragments.actionSummaryView} -`; - -const STREAM_QUERY = gql` - query StreamQuery($assetId: ID, $assetUrl: String, $commentId: ID!, $hasComment: Boolean!, $excludeIgnored: Boolean) { - __typename ...Stream_root } ${Stream.fragments.root} `; -// get the counts of the top-level comments -const getCounts = (data) => ({asset_id, limit, sort}) => { - return data.fetchMore({ - query: LOAD_COMMENT_COUNTS_QUERY, - variables: { - asset_id, - limit, - sort, - excludeIgnored: data.variables.excludeIgnored, - }, - updateQuery: (oldData, {fetchMoreResult:{asset}}) => { - return { - ...oldData, - asset: { - ...oldData.asset, - commentCount: asset.commentCount - } - }; - } - }); -}; - -// handle paginated requests for more Comments pertaining to the Asset -const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, sort}, newComments) => { - return data.fetchMore({ - query: LOAD_MORE_QUERY, - variables: { - 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 - excludeIgnored: data.variables.excludeIgnored, - }, - updateQuery: (oldData, {fetchMoreResult:{new_top_level_comments}}) => { - let updatedAsset; - - 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 - - updatedAsset = { - ...oldData, - asset: { - ...oldData.asset, - comments: oldData.asset.comments.map(comment => { - - // since the dipslayed replies and the returned replies can overlap, - // pull out the unique ones. - const uniqueReplies = uniqBy([...new_top_level_comments, ...comment.replies], 'id'); - - // since we just gave the returned replies precedence, they're now out of order. - // resort according to date. - return comment.id === parent_id - ? {...comment, replies: sortBy(uniqueReplies, 'created_at')} - : comment; - }) - } - }; - } else { // If loading more top-level comments - - updatedAsset = { - ...oldData, - asset: { - ...oldData.asset, - comments: newComments ? [...new_top_level_comments.reverse(), ...oldData.asset.comments] - : [...oldData.asset.comments, ...new_top_level_comments] - } - }; - } - - return updatedAsset; - } - }); -}; - -export const withQuery = graphql(STREAM_QUERY, { +export const withQuery = graphql(EMBED_QUERY, { options: ({auth, commentId, assetId, assetUrl}) => ({ variables: { assetId, @@ -250,15 +84,12 @@ export const withQuery = graphql(STREAM_QUERY, { }), props: ({data}) => ({ data, - loadMore: loadMore(data), - getCounts: getCounts(data), }) }); const mapStateToProps = state => ({ auth: state.auth.toJS(), commentCountCache: state.stream.commentCountCache, - activeReplyBox: state.stream.activeReplyBox, commentId: state.stream.commentId, assetId: state.stream.assetId, assetUrl: state.stream.assetUrl, @@ -267,30 +98,16 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => bindActionCreators({ - showSignInDialog, - requestConfirmEmail, fetchAssetSuccess, - addNotification, - clearNotification, checkLogin, - editName, setCommentCountCache, viewAllComments, logout, - setActiveReplyBox, setActiveTab, }, dispatch); export default compose( connect(mapStateToProps, mapDispatchToProps), - postComment, - postFlag, - postLike, - postDontAgree, - addCommentTag, - removeCommentTag, - ignoreUser, - deleteAction, withQuery, )(EmbedContainer); diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index cecc6edc3..d57c37b33 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -1,9 +1,21 @@ import React from 'react'; -import {gql} from 'react-apollo'; +import {gql, compose} from 'react-apollo'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import uniqBy from 'lodash/uniqBy'; +import sortBy from 'lodash/sortBy'; +import isNil from 'lodash/isNil'; import Stream from '../components/Stream'; import {NEW_COMMENT_COUNT_POLL_INTERVAL} from '../constants/stream'; +import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser} from 'coral-framework/graphql/mutations'; +import {notificationActions, authActions} from 'coral-framework'; +import {editName} from 'coral-framework/actions/user'; +import {setCommentCountCache, setActiveReplyBox} from '../actions/stream'; -export default class StreamContainer extends React.Component { +const {showSignInDialog} = authActions; +const {addNotification} = notificationActions; + +class StreamContainer extends React.Component { getCounts = ({asset_id, limit, sort}) => { return this.props.data.fetchMore({ query: LOAD_COMMENT_COUNTS_QUERY, @@ -11,7 +23,7 @@ export default class StreamContainer extends React.Component { asset_id, limit, sort, - excludeIgnored: data.variables.excludeIgnored, + excludeIgnored: this.props.data.variables.excludeIgnored, }, updateQuery: (oldData, {fetchMoreResult:{asset}}) => { return { @@ -35,7 +47,7 @@ export default class StreamContainer extends React.Component { 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 - excludeIgnored: data.variables.excludeIgnored, + excludeIgnored: this.props.data.variables.excludeIgnored, }, updateQuery: (oldData, {fetchMoreResult:{new_top_level_comments}}) => { let updatedAsset; @@ -103,9 +115,10 @@ export default class StreamContainer extends React.Component { }; componentDidMount() { + this.props.data.refetch(); this.countPoll = setInterval(() => { const {asset} = this.props.data; - this.props.getCounts({ + this.getCounts({ asset_id: asset.id, limit: asset.comments.length, sort: 'REVERSE_CHRONOLOGICAL' @@ -118,7 +131,7 @@ export default class StreamContainer extends React.Component { } render() { - return ; + return ; } } @@ -152,6 +165,33 @@ const actionSummaryViewFragment = gql` } `; +const LOAD_COMMENT_COUNTS_QUERY = gql` + query LoadCommentCounts($asset_id: ID, $limit: Int = 5, $sort: SORT_ORDER) { + asset(id: $asset_id) { + id + commentCount + comments(sort: $sort, limit: $limit) { + id + replyCount + } + } + } +`; + +const LOAD_MORE_QUERY = gql` + query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) { + new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) { + ...commentView + replyCount(excludeIgnored: $excludeIgnored) + replies(limit: 3) { + ...commentView + } + } + } + ${commentViewFragment} + ${actionSummaryViewFragment} +`; + StreamContainer.fragments = { root: gql` fragment Stream_root on RootQuery { @@ -205,8 +245,44 @@ StreamContainer.fragments = { id, username, } + me { + status + } } ${commentViewFragment} ${actionSummaryViewFragment} `, }; + +const mapStateToProps = state => ({ + auth: state.auth.toJS(), + commentCountCache: state.stream.commentCountCache, + activeReplyBox: state.stream.activeReplyBox, + + commentId: state.stream.commentId, + assetId: state.stream.assetId, + assetUrl: state.stream.assetUrl, + activeTab: state.embed.activeTab, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators({ + showSignInDialog, + addNotification, + setActiveReplyBox, + editName, + setCommentCountCache, + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + postComment, + postFlag, + postLike, + postDontAgree, + addCommentTag, + removeCommentTag, + ignoreUser, + deleteAction, +)(StreamContainer); + diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index ed0579c25..eb0b5386a 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -9,8 +9,6 @@ import REMOVE_COMMENT_TAG from './removeCommentTag.graphql'; import IGNORE_USER from './ignoreUser.graphql'; import STOP_IGNORING_USER from './stopIgnoringUser.graphql'; -import MY_IGNORED_USERS from '../queries/myIgnoredUsers.graphql'; - import commentView from '../fragments/commentView.graphql'; export const postComment = graphql(POST_COMMENT, { @@ -43,7 +41,7 @@ export const postComment = graphql(POST_COMMENT, { } }, updateQueries: { - AssetQuery: (oldData, {mutationResult: {data: {createComment: {comment}}}}) => { + EmbedQuery: (oldData, {mutationResult: {data: {createComment: {comment}}}}) => { if (oldData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') { return oldData; @@ -161,9 +159,9 @@ export const ignoreUser = graphql(IGNORE_USER, { variables: { id, }, - refetchQueries: [{ - query: MY_IGNORED_USERS, - }] + refetchQueries: [ + 'EmbedQuery', 'myIgnoredUsers', + ] }); }}), }); @@ -178,10 +176,7 @@ export const stopIgnoringUser = graphql(STOP_IGNORING_USER, { id, }, refetchQueries: [ - { - query: MY_IGNORED_USERS, - }, - 'StreamQuery', + 'EmbedQuery', 'myIgnoredUsers', ] }); } diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 3db1c6ad0..c4e0df432 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -196,7 +196,6 @@ CommentBox.propTypes = { authorId: PropTypes.string.isRequired, isReply: PropTypes.bool.isRequired, canPost: PropTypes.bool, - currentUser: PropTypes.object }; const mapStateToProps = ({commentBox}) => ({commentBox}); From c261cdc0c691db752e7d577a145ceb8215632b1f Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 24 Apr 2017 22:49:52 +0700 Subject: [PATCH 12/28] Compose comment fragments --- .../src/containers/Comment.js | 28 +++++++++ .../src/containers/Embed.js | 3 +- .../src/containers/Stream.js | 59 +++++-------------- client/coral-framework/hocs/withFragments.js | 16 +++++ 4 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 client/coral-embed-stream/src/containers/Comment.js create mode 100644 client/coral-framework/hocs/withFragments.js diff --git a/client/coral-embed-stream/src/containers/Comment.js b/client/coral-embed-stream/src/containers/Comment.js new file mode 100644 index 000000000..93c9f2471 --- /dev/null +++ b/client/coral-embed-stream/src/containers/Comment.js @@ -0,0 +1,28 @@ +import {gql} from 'react-apollo'; +import Comment from '../components/Comment'; +import withFragments from 'coral-framework/hocs/withFragments'; + +export default withFragments({ + comment: gql` + fragment Comment_comment on Comment { + id + body + created_at + status + tags { + name + } + user { + id + name: username + } + action_summaries { + __typename + count + current_user { + id + created_at + } + } + }`, +})(Comment); diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index fe715cd3b..0a03462fc 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -9,7 +9,7 @@ import {authActions, assetActions, pym} from 'coral-framework'; import Embed from '../components/Embed'; import {setCommentCountCache, viewAllComments} from '../actions/stream'; import {setActiveTab} from '../actions/embed'; -import * as Stream from './Stream'; +import Stream from './Stream'; const {logout, checkLogin} = authActions; const {fetchAssetSuccess} = assetActions; @@ -25,6 +25,7 @@ class EmbedContainer extends React.Component { if(this.props.data.me && !nextProps.data.me) { // Refetch because on logout `excludeIgnored` becomes `false`. + // TODO: logout via mutation and obsolete this? this.props.data.refetch(); } diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index d57c37b33..838df1084 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -5,12 +5,14 @@ import {bindActionCreators} from 'redux'; import uniqBy from 'lodash/uniqBy'; import sortBy from 'lodash/sortBy'; import isNil from 'lodash/isNil'; -import Stream from '../components/Stream'; import {NEW_COMMENT_COUNT_POLL_INTERVAL} from '../constants/stream'; import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser} from 'coral-framework/graphql/mutations'; import {notificationActions, authActions} from 'coral-framework'; import {editName} from 'coral-framework/actions/user'; import {setCommentCountCache, setActiveReplyBox} from '../actions/stream'; +import Stream from '../components/Stream'; +import Comment from './Comment'; +import withFragments from 'coral-framework/hocs/withFragments'; const {showSignInDialog} = authActions; const {addNotification} = notificationActions; @@ -135,36 +137,6 @@ class StreamContainer extends React.Component { } } -const commentViewFragment = gql` - fragment commentView on Comment { - id - body - created_at - status - tags { - name - } - user { - id - name: username - } - action_summaries { - ...actionSummaryView - } - } -`; - -const actionSummaryViewFragment = gql` - fragment actionSummaryView on ActionSummary { - __typename - count - current_user { - id - created_at - } - } -`; - const LOAD_COMMENT_COUNTS_QUERY = gql` query LoadCommentCounts($asset_id: ID, $limit: Int = 5, $sort: SORT_ORDER) { asset(id: $asset_id) { @@ -181,31 +153,30 @@ const LOAD_COMMENT_COUNTS_QUERY = gql` const LOAD_MORE_QUERY = gql` query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) { new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) { - ...commentView + ...Comment_comment replyCount(excludeIgnored: $excludeIgnored) replies(limit: 3) { - ...commentView + ...Comment_comment } } } - ${commentViewFragment} - ${actionSummaryViewFragment} + ${Comment.fragments.comment} `; -StreamContainer.fragments = { +const fragments = { root: gql` fragment Stream_root on RootQuery { comment(id: $commentId) @include(if: $hasComment) { - ...commentView + ...Comment_comment replyCount(excludeIgnored: $excludeIgnored) replies { - ...commentView + ...Comment_comment } parent { - ...commentView + ...Comment_comment replyCount(excludeIgnored: $excludeIgnored) replies { - ...commentView + ...Comment_comment } } } @@ -234,10 +205,10 @@ StreamContainer.fragments = { commentCount(excludeIgnored: $excludeIgnored) totalCommentCount(excludeIgnored: $excludeIgnored) comments(limit: 10, excludeIgnored: $excludeIgnored) { - ...commentView + ...Comment_comment replyCount(excludeIgnored: $excludeIgnored) replies(limit: 3, excludeIgnored: $excludeIgnored) { - ...commentView + ...Comment_comment } } } @@ -249,8 +220,7 @@ StreamContainer.fragments = { status } } - ${commentViewFragment} - ${actionSummaryViewFragment} + ${Comment.fragments.comment} `, }; @@ -284,5 +254,6 @@ export default compose( removeCommentTag, ignoreUser, deleteAction, + withFragments(fragments), )(StreamContainer); diff --git a/client/coral-framework/hocs/withFragments.js b/client/coral-framework/hocs/withFragments.js new file mode 100644 index 000000000..8e5fbccb9 --- /dev/null +++ b/client/coral-framework/hocs/withFragments.js @@ -0,0 +1,16 @@ +import React from 'react'; + +function getDisplayName(WrappedComponent) { + return WrappedComponent.displayName || WrappedComponent.name || 'Component'; +} + +export default fragments => WrappedComponent => { + class WithFragments extends React.Component { + render() { + return ; + } + } + WithFragments.fragments = fragments; + WithFragments.displayName = `WithFragments(${getDisplayName(WrappedComponent)})`; + return WithFragments; +}; From a91db35f91625c0987513783f98657eee6267336 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 24 Apr 2017 23:34:53 +0700 Subject: [PATCH 13/28] Add comments for filter --- client/coral-framework/hocs/withFragments.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/coral-framework/hocs/withFragments.js b/client/coral-framework/hocs/withFragments.js index 8e5fbccb9..a2686d94b 100644 --- a/client/coral-framework/hocs/withFragments.js +++ b/client/coral-framework/hocs/withFragments.js @@ -1,5 +1,7 @@ import React from 'react'; +// TODO: revisit `filtering` after https://github.com/apollographql/graphql-anywhere/issues/38. + function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; } From 027358913df21818bf80ae8321c32e2b3278f6c2 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 24 Apr 2017 23:35:49 +0700 Subject: [PATCH 14/28] Use container --- client/coral-embed-stream/src/components/Stream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index b4b23091c..d03dda571 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -1,6 +1,6 @@ import React, {PropTypes} from 'react'; import {Button} from 'coral-ui'; -import Comment from './Comment'; +import Comment from '../containers/Comment'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import SuspendedAccount from 'coral-framework/components/SuspendedAccount'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; From 54ea71bedf2bb80a4e7919a1c6e6ee375b2ee18a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Apr 2017 18:24:25 +0700 Subject: [PATCH 15/28] Support fragments in plugins --- .../src/components/Comment.js | 21 ++++++++++-- .../src/components/Embed.js | 4 +-- .../src/components/Stream.js | 6 +++- .../src/containers/Comment.js | 15 ++++++++- .../src/containers/Embed.js | 23 ++++++++----- .../src/containers/Stream.js | 6 ++-- client/coral-framework/helpers/plugins.js | 33 +++++++++++++++++++ .../client/containers/RespectButton.js | 22 ++++++++++++- 8 files changed, 111 insertions(+), 19 deletions(-) diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index c44e06282..b2289716a 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -174,8 +174,14 @@ class Comment extends React.Component { ? : null } - - + { (currentUser && (comment.user.id !== currentUser.id)) ? - +
@@ -257,6 +270,8 @@ class Comment extends React.Component { return commentIsIgnored(reply) ? : ; @@ -60,7 +60,7 @@ export default class Embed extends React.Component { } { loggedIn ? userBox : null } - + diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index d03dda571..cedd1cc1c 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -24,7 +24,7 @@ class Stream extends React.Component { render () { const { - data: {asset, asset: {comments}, comment, myIgnoredUsers}, + root: {asset, asset: {comments}, comment, myIgnoredUsers}, postItem, addNotification, postFlag, @@ -106,6 +106,8 @@ class Stream extends React.Component { { highlightedComment ? : pym.scrollParentToChildEl('coralStream'), 0); @@ -53,7 +53,7 @@ class EmbedContainer extends React.Component { } render() { - if (!this.props.data.asset) { + if (!this.props.root.asset) { return ; } return ; @@ -83,9 +83,14 @@ export const withQuery = graphql(EMBED_QUERY, { excludeIgnored: Boolean(auth && auth.user && auth.user.id), }, }), - props: ({data}) => ({ - data, - }) + props: ({data: { + fetchMore, loading, networkStatus, refetch, startPolling, + stopPolling, subscribeToMore, updateQuery, variables, + ...root}}) => ({ + data: {fetchMore, loading, networkStatus, refetch, startPolling, + stopPolling, subscribeToMore, updateQuery, variables}, + root, + }), }); const mapStateToProps = state => ({ diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index 838df1084..4f5018c2a 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -119,7 +119,7 @@ class StreamContainer extends React.Component { componentDidMount() { this.props.data.refetch(); this.countPoll = setInterval(() => { - const {asset} = this.props.data; + const {asset} = this.props.root; this.getCounts({ asset_id: asset.id, limit: asset.comments.length, @@ -219,7 +219,9 @@ const fragments = { me { status } + ...Comment_root } + ${Comment.fragments.root} ${Comment.fragments.comment} `, }; @@ -245,6 +247,7 @@ const mapDispatchToProps = dispatch => }, dispatch); export default compose( + withFragments(fragments), connect(mapStateToProps, mapDispatchToProps), postComment, postFlag, @@ -254,6 +257,5 @@ export default compose( removeCommentTag, ignoreUser, deleteAction, - withFragments(fragments), )(StreamContainer); diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index e0d345db4..02f97b101 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -1,7 +1,10 @@ import React from 'react'; import merge from 'lodash/merge'; import flatten from 'lodash/flatten'; +import flattenDeep from 'lodash/flattenDeep'; +import uniq from 'lodash/uniq'; import plugins from 'pluginsConfig'; +import {gql} from 'react-apollo'; export const pluginReducers = merge( ...plugins @@ -19,3 +22,33 @@ export function getSlotElements(slot, props = {}) { return components .map((component, i) => React.createElement(component, {...props, key: i})); } + +function getComponentFragments(components) { + return components + .map(c => c.fragments) + .filter(fragments => fragments) + .reduce((res, fragments) => { + Object.keys(fragments).forEach(key => { + if (!(key in res)) { + res[key] = {names: '', definitions: ''}; + } + res[key].names += `...${fragments[key].definitions[0].name.value}\n`; + res[key].definitions = gql`${res[key].definitions}${fragments[key]}`; + }); + return res; + }, {}); +} + +export function getSlotsFragments(slots) { + if (!Array.isArray(slots)) { + slots = [slots]; + } + const components = uniq(flattenDeep(slots.map(slot => { + return plugins + .filter(o => o.module.slots[slot]) + .map(o => o.module.slots[slot]); + }))); + + return getComponentFragments(components); +} + diff --git a/plugins/coral-plugin-respect/client/containers/RespectButton.js b/plugins/coral-plugin-respect/client/containers/RespectButton.js index bcf0e342d..a69c62032 100644 --- a/plugins/coral-plugin-respect/client/containers/RespectButton.js +++ b/plugins/coral-plugin-respect/client/containers/RespectButton.js @@ -2,7 +2,7 @@ import {compose, gql, graphql} from 'react-apollo'; import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import get from 'lodash/get'; - +import withFragments from 'coral-framework/hocs/withFragments'; import {showSignInDialog} from 'coral-framework/actions/auth'; import RespectButton from '../components/RespectButton'; @@ -155,6 +155,26 @@ const mapDispatchToProps = dispatch => bindActionCreators({showSignInDialog}, dispatch); const enhance = compose( + withFragments({ + root: gql` + fragment RespectButton_root on RootQuery { + me { + status + } + } + `, + comment: gql` + fragment RespectButton_comment on Comment { + action_summaries { + ... on RespectActionSummary { + count + current_user { + id + } + } + } + }`, + }), connect(null, mapDispatchToProps), withDeleteAction, withPostRespect, From 3d10534da103fd197a9160491e4530c7d5c2ed76 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Apr 2017 19:54:54 +0700 Subject: [PATCH 16/28] RespectPlugin use fat query --- .../client/components/RespectButton.js | 10 +- .../client/containers/RespectButton.js | 134 ++++++++---------- 2 files changed, 62 insertions(+), 82 deletions(-) diff --git a/plugins/coral-plugin-respect/client/components/RespectButton.js b/plugins/coral-plugin-respect/client/components/RespectButton.js index c1e0c1b47..faeda1991 100644 --- a/plugins/coral-plugin-respect/client/components/RespectButton.js +++ b/plugins/coral-plugin-respect/client/components/RespectButton.js @@ -12,8 +12,8 @@ const lang = new I18n(translations); class RespectButton extends Component { handleClick = () => { - const {postRespect, showSignInDialog, deleteAction, commentId} = this.props; - const {me, comment} = this.props.data; + const {postRespect, showSignInDialog, deleteAction} = this.props; + const {root: {me}, comment} = this.props; const myRespectActionSummary = getMyActionSummary('RespectActionSummary', comment); @@ -29,17 +29,17 @@ class RespectButton extends Component { } if (myRespectActionSummary) { - deleteAction(myRespectActionSummary.current_user.id); + deleteAction(myRespectActionSummary.current_user.id, comment.id); } else { postRespect({ - item_id: commentId, + item_id: comment.id, item_type: 'COMMENTS' }); } } render() { - const {comment} = this.props.data; + const {comment} = this.props; if (!comment) { return null; diff --git a/plugins/coral-plugin-respect/client/containers/RespectButton.js b/plugins/coral-plugin-respect/client/containers/RespectButton.js index a69c62032..be389c72a 100644 --- a/plugins/coral-plugin-respect/client/containers/RespectButton.js +++ b/plugins/coral-plugin-respect/client/containers/RespectButton.js @@ -6,33 +6,22 @@ import withFragments from 'coral-framework/hocs/withFragments'; import {showSignInDialog} from 'coral-framework/actions/auth'; import RespectButton from '../components/RespectButton'; -// TODO: use `update` instead of `updateQueries` for optimistic mutations. -// See https://dev-blog.apollodata.com/apollo-clients-new-imperative-store-api-6cb69318a1e3 -// and https://github.com/apollographql/apollo-client/issues/1224 - const isRespectAction = (a) => a.__typename === 'RespectActionSummary'; -export const RESPECT_QUERY = gql` - query RespectQuery($commentId: ID!) { - comment(id: $commentId) { - id - action_summaries { - ... on RespectActionSummary { - count - current_user { - id - } +const COMMENT_FRAGMENT = gql` + fragment RespectButton_comment on Comment { + id + action_summaries { + ... on RespectActionSummary { + count + current_user { + id } } } - me { - status - } } `; -const withQuery = graphql(RESPECT_QUERY); - const withDeleteAction = graphql(gql` mutation deleteAction($id: ID!) { deleteAction(id:$id) { @@ -43,7 +32,7 @@ const withDeleteAction = graphql(gql` } `, { props: ({mutate}) => ({ - deleteAction: (id) => { + deleteAction: (id, commentId) => { return mutate({ variables: {id}, optimisticResponse: { @@ -52,27 +41,26 @@ const withDeleteAction = graphql(gql` errors: null, } }, - updateQueries: { - RespectQuery: (prev) => { - const action_summaries = prev.comment.action_summaries; - const idx = action_summaries.findIndex(isRespectAction); - if (idx < 0 || get(action_summaries[idx], 'current_user.id') !== id) { - return prev; - } - const next = { - ...prev, - comment: { - ...prev.comment, - action_summaries: action_summaries.map( - (a, i) => i !== idx ? a : ({ - ...a, - count: a.count - 1, - current_user: null, - })), - } - }; - return next; - }, + update: (proxy) => { + const fragmentId = `Comment_${commentId}`; + + // Read the data from our cache for this query. + const data = proxy.readFragment({fragment: COMMENT_FRAGMENT, id: fragmentId}); + + // Check whether we respected this comment. + const idx = data.action_summaries.findIndex(isRespectAction); + if (idx < 0 || get(data.action_summaries[idx], 'current_user.id') !== id) { + return; + } + + data.action_summaries[idx] = { + ...data.action_summaries[idx], + count: data.action_summaries[idx].count - 1, + current_user: null, + }; + + // Write our data back to the cache. + proxy.writeFragment({fragment: COMMENT_FRAGMENT, id: fragmentId, data}); }, }); }, @@ -105,46 +93,39 @@ const withPostRespect = graphql(gql` }, } }, - updateQueries: { - RespectQuery: (prev, {mutationResult, queryVariables}) => { - if (queryVariables.commentId !== respect.item_id) { - return prev; - } + update: (proxy, mutationResult) => { + const fragmentId = `Comment_${respect.item_id}`; - let action_summaries = prev.comment.action_summaries; - let idx = action_summaries.findIndex(isRespectAction); + // Read the data from our cache for this query. + const data = proxy.readFragment({fragment: COMMENT_FRAGMENT, id: fragmentId}); - // Check whether we already respected this comment. - if (idx >= 0 && action_summaries[idx].current_user) { - return prev; - } + // Add our comment from the mutation to the end. + let idx = data.action_summaries.findIndex(isRespectAction); - if (idx < 0) { + // Check whether we already respected this comment. + if (idx >= 0 && data.action_summaries[idx].current_user) { + return; + } - // Add initial action when it doesn't exist. - action_summaries = action_summaries.concat([{ - __typename: 'RespectActionSummary', - count: 0, - current_user: null, - }]); - idx = action_summaries.length - 1; - } + if (idx < 0) { - const respectAction = mutationResult.data.createRespect.respect; - const next = { - ...prev, - comment: { - ...prev.comment, - action_summaries: action_summaries.map( - (a, i) => i !== idx ? a : ({ - ...a, - count: a.count + 1, - current_user: respectAction, - })), - } - }; - return next; - }, + // Add initial action when it doesn't exist. + data.action_summaries.push({ + __typename: 'RespectActionSummary', + count: 0, + current_user: null, + }); + idx = data.action_summaries.length - 1; + } + + data.action_summaries[idx] = { + ...data.action_summaries[idx], + count: data.action_summaries[idx].count + 1, + current_user: mutationResult.data.createRespect.respect, + }; + + // Write our data back to the cache. + proxy.writeFragment({fragment: COMMENT_FRAGMENT, id: fragmentId, data}); }, }); }, @@ -178,7 +159,6 @@ const enhance = compose( connect(null, mapDispatchToProps), withDeleteAction, withPostRespect, - withQuery, ); export default enhance(RespectButton); From 4bacc2b0c292b07227b72ea94cfadaf9ee1368e2 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Apr 2017 22:31:59 +0700 Subject: [PATCH 17/28] Update Apollo --- package.json | 5 +++-- yarn.lock | 46 ++++++++++++++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 6c725a852..e2bd683ca 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,8 @@ "parse-duration": "^0.1.1", "passport": "^0.3.2", "passport-local": "^1.0.0", - "react-apollo": "^1.0.0", + "prop-types": "^15.5.8", + "react-apollo": "^1.1.0", "react-recaptcha": "^2.2.6", "redis": "^2.7.1", "resolve": "^1.3.2", @@ -100,7 +101,7 @@ "uuid": "^2.0.3" }, "devDependencies": { - "apollo-client": "^1.0.0", + "apollo-client": "^1.0.4", "autoprefixer": "^6.5.2", "babel-cli": "^6.24.0", "babel-core": "^6.24.0", diff --git a/yarn.lock b/yarn.lock index 378e5a6c0..34025cfd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -179,11 +179,11 @@ anymatch@^1.3.0: arrify "^1.0.0" micromatch "^2.1.5" -apollo-client@^1.0.0, apollo-client@^1.0.0-rc.9: - version "1.0.2" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.2.tgz#4355bd49d53a1489bc91d9f56d5b3d0ffe33fb3c" +apollo-client@^1.0.2, apollo-client@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.4.tgz#af75db8cdd27e08a835ddfb39807849e178540f9" dependencies: - graphql "^0.9.1" + graphql "^0.9.3" graphql-anywhere "^3.0.1" graphql-tag "^2.0.0" redux "^3.4.0" @@ -2948,6 +2948,18 @@ fbjs@^0.8.1, fbjs@^0.8.4: setimmediate "^1.0.5" ua-parser-js "^0.7.9" +fbjs@^0.8.9: + version "0.8.12" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + fd-slicer@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" @@ -3513,10 +3525,6 @@ graphql-tag@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.2.3.tgz#74c62443fbf3e693647426d7359f7e3e6ce7dace" -graphql-tag@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.3.2.tgz#7abb3a8fd9f3415d07163314ed237061c785b759" - graphql-tag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.0.0.tgz#f3efe3b4d64f33bfe8479ae06a461c9d72f2a6fe" @@ -3543,9 +3551,9 @@ graphql@^0.8.2: dependencies: iterall "1.0.2" -graphql@^0.9.1: - version "0.9.2" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.9.2.tgz#2cb5c635de13f790a77c5879649cb401b1589386" +graphql@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.9.3.tgz#71fc0fa331bffb9c20678485861cfb370803118e" dependencies: iterall "1.0.3" @@ -6366,6 +6374,12 @@ promise@^7.0.1, promise@^7.1.1: dependencies: asap "~2.0.3" +prop-types@^15.5.8: + version "15.5.8" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394" + dependencies: + fbjs "^0.8.9" + protocols@^1.1.0, protocols@^1.4.0: version "1.4.3" resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.3.tgz#635b1c0785f0b389e8a012df1b1afffda9608b76" @@ -6613,13 +6627,13 @@ react-addons-test-utils@^15.4.2: fbjs "^0.8.4" object-assign "^4.1.0" -react-apollo@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-1.0.0.tgz#7fcc14adcc7aa4ca4d9e04ddedf50b8fb74daa91" +react-apollo@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-1.1.0.tgz#0c5027da72420919b62083e4c473cf406959892c" dependencies: - apollo-client "^1.0.0-rc.9" + apollo-client "^1.0.2" graphql-anywhere "^3.0.0" - graphql-tag "^1.3.1" + graphql-tag "^2.0.0" hoist-non-react-statics "^1.2.0" invariant "^2.2.1" lodash.flatten "^4.2.0" From 3a7b17750e04843ec6ff7104ac43ceb1f0da9010 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Apr 2017 23:00:11 +0700 Subject: [PATCH 18/28] Use unique name for fragment --- .../coral-plugin-respect/client/containers/RespectButton.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/coral-plugin-respect/client/containers/RespectButton.js b/plugins/coral-plugin-respect/client/containers/RespectButton.js index be389c72a..38be8a978 100644 --- a/plugins/coral-plugin-respect/client/containers/RespectButton.js +++ b/plugins/coral-plugin-respect/client/containers/RespectButton.js @@ -9,8 +9,7 @@ import RespectButton from '../components/RespectButton'; const isRespectAction = (a) => a.__typename === 'RespectActionSummary'; const COMMENT_FRAGMENT = gql` - fragment RespectButton_comment on Comment { - id + fragment RespectButton_updateFragment on Comment { action_summaries { ... on RespectActionSummary { count From 15d410ba12932e21a28ebd92d1bef4dd1a2b2e9d Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Apr 2017 23:20:23 +0700 Subject: [PATCH 19/28] Workaround weird Apollo bug --- client/coral-embed-stream/src/containers/Comment.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/coral-embed-stream/src/containers/Comment.js b/client/coral-embed-stream/src/containers/Comment.js index 6345075fd..4bad8aaab 100644 --- a/client/coral-embed-stream/src/containers/Comment.js +++ b/client/coral-embed-stream/src/containers/Comment.js @@ -31,7 +31,6 @@ export default withFragments({ count current_user { id - created_at } } ${pluginFragments.comment && pluginFragments.comment.names} From cee1c5bc1c9d8f38bc08be3b74e3e3223c870e29 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 25 Apr 2017 11:21:28 -0600 Subject: [PATCH 20/28] have active state on action buttons depending on comment status --- .../coral-admin/src/components/ActionButton.js | 18 ++++++++++++++---- .../src/components/ModerationList.css | 12 ++++++++++++ .../ModerationQueue/components/Comment.js | 1 + client/coral-ui/components/Button.css | 2 +- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 3bd96abad..f962e768e 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -1,17 +1,27 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import styles from './ModerationList.css'; import {Button} from 'coral-ui'; import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap'; -const ActionButton = ({type = '', ...props}) => { +const ActionButton = ({type = '', status, ...props}) => { + const typeName = type.toLowerCase(); + let active = false; + if ((type === 'REJECT' && status === 'REJECTED') || (type === 'APPROVE' && status === 'APPROVED')) { + active = true; + } + return ( ); }; +ActionButton.propTypes = { + status: PropTypes.string +}; + export default ActionButton; diff --git a/client/coral-admin/src/components/ModerationList.css b/client/coral-admin/src/components/ModerationList.css index fc2ba9931..1e871f97c 100644 --- a/client/coral-admin/src/components/ModerationList.css +++ b/client/coral-admin/src/components/ModerationList.css @@ -188,3 +188,15 @@ margin: 0; width: 140px; } + +.approve__active { + box-shadow: none; + color: white; + background-color: #519954; +} + +.reject__active, .rejected__active { + color: white; + background-color: #D03235; + box-shadow: none; +} diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index b18f239e4..6a06a6ca0 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -64,6 +64,7 @@ const Comment = ({actions = [], comment, ...props}) => { props.acceptComment({commentId: comment.id})} rejectComment={() => props.rejectComment({commentId: comment.id})} /> diff --git a/client/coral-ui/components/Button.css b/client/coral-ui/components/Button.css index 14ed64aa2..219b70a65 100644 --- a/client/coral-ui/components/Button.css +++ b/client/coral-ui/components/Button.css @@ -149,7 +149,7 @@ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.03), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.09); width: 128px; -&:hover { + &:hover { color: white; background-color: #D03235; box-shadow: none; From f79af5646833f704f34d827b485a0e7fb94045ae Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 26 Apr 2017 00:44:02 +0700 Subject: [PATCH 21/28] Better API for slot fragments --- .../src/containers/Comment.js | 8 ++++---- client/coral-framework/helpers/plugins.js | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/client/coral-embed-stream/src/containers/Comment.js b/client/coral-embed-stream/src/containers/Comment.js index 4bad8aaab..524f7ec62 100644 --- a/client/coral-embed-stream/src/containers/Comment.js +++ b/client/coral-embed-stream/src/containers/Comment.js @@ -9,9 +9,9 @@ export default withFragments({ root: gql` fragment Comment_root on RootQuery { __typename - ${pluginFragments.root && pluginFragments.root.names} + ${pluginFragments.spreads('root')} } - ${pluginFragments.root && pluginFragments.root.definitions} + ${pluginFragments.definitions('root')} `, comment: gql` fragment Comment_comment on Comment { @@ -33,8 +33,8 @@ export default withFragments({ id } } - ${pluginFragments.comment && pluginFragments.comment.names} + ${pluginFragments.spreads('comment')} } - ${pluginFragments.comment && pluginFragments.comment.definitions} + ${pluginFragments.definitions('comment')} `, })(Comment); diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index 02f97b101..c284cae43 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -30,15 +30,18 @@ function getComponentFragments(components) { .reduce((res, fragments) => { Object.keys(fragments).forEach(key => { if (!(key in res)) { - res[key] = {names: '', definitions: ''}; + res[key] = {spreads: '', definitions: ''}; } - res[key].names += `...${fragments[key].definitions[0].name.value}\n`; + res[key].spreads += `...${fragments[key].definitions[0].name.value}\n`; res[key].definitions = gql`${res[key].definitions}${fragments[key]}`; }); return res; }, {}); } +/** + * @returns {[key]: {spreads: string, definitions: AST}} + */ export function getSlotsFragments(slots) { if (!Array.isArray(slots)) { slots = [slots]; @@ -49,6 +52,14 @@ export function getSlotsFragments(slots) { .map(o => o.module.slots[slot]); }))); - return getComponentFragments(components); + const fragments = getComponentFragments(components); + return { + spreads(key) { + return fragments[key] && fragments[key].spreads; + }, + definitions(key) { + return fragments[key] && fragments[key].definitions; + }, + }; } From 5666c4ac903095ebee3671f57ea2aacd5982e263 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 25 Apr 2017 11:57:36 -0600 Subject: [PATCH 22/28] condense some var definitions --- client/coral-admin/src/components/ActionButton.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index f962e768e..9365a76b6 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -5,10 +5,7 @@ import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQu const ActionButton = ({type = '', status, ...props}) => { const typeName = type.toLowerCase(); - let active = false; - if ((type === 'REJECT' && status === 'REJECTED') || (type === 'APPROVE' && status === 'APPROVED')) { - active = true; - } + const active = ((type === 'REJECT' && status === 'REJECTED') || (type === 'APPROVE' && status === 'APPROVED')); return (
{comment.user.status === 'banned' ? From 9ff2ccf1ed6349a5d5ad1a819e0de90b96dd0102 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 26 Apr 2017 18:52:30 +0700 Subject: [PATCH 28/28] Safari IOS ignores width=100% in iframes --- client/coral-embed/src/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index 722f4ca8e..950fa91bc 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -57,6 +57,10 @@ function configurePymParent(pymParent) { window.document.body.appendChild(snackbar); + // Workaround: IOS Safari ignores `width` but respects `min-width` value. + pymParent.el.firstChild.style.width = '1px'; + pymParent.el.firstChild.style.minWidth = '100%'; + // Resize parent iframe height when child height changes pymParent.onMessage('height', function(height) { if (height !== cachedHeight) {