diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js
index 9d679473e..8371cec45 100644
--- a/client/coral-embed-stream/src/Comment.js
+++ b/client/coral-embed-stream/src/Comment.js
@@ -177,7 +177,7 @@ class Comment extends React.Component {
comment.replies &&
comment.replies.length}
diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js
index a7c4dcb38..6ff153a52 100644
--- a/client/coral-embed-stream/src/Embed.js
+++ b/client/coral-embed-stream/src/Embed.js
@@ -12,6 +12,7 @@ const {fetchAssetSuccess} = assetActions;
import {queryStream} from 'coral-framework/graphql/queries';
import {postComment, postFlag, postLike, deleteAction} from 'coral-framework/graphql/mutations';
import {editName} from 'coral-framework/actions/user';
+import {updateCountCache} from 'coral-framework/actions/asset';
import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework';
import Stream from './Stream';
@@ -28,6 +29,7 @@ import SettingsContainer from 'coral-settings/containers/SettingsContainer';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer';
import LoadMore from './LoadMore';
+import NewCount from './NewCount';
class Embed extends Component {
@@ -82,7 +84,7 @@ class Embed extends Component {
render () {
const {activeTab} = this.state;
- const {closedAt} = this.props.asset;
+ const {closedAt, countCache = {}} = this.props.asset;
const {loading, asset, refetch} = this.props.data;
const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth;
@@ -147,6 +149,7 @@ class Embed extends Component {
}
{!loggedIn && }
{loggedIn && user && }
+
({
clearNotification: () => dispatch(clearNotification()),
editName: (username) => dispatch(editName(username)),
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
+ updateCountCache: (id, count) => dispatch(updateCountCache(id, count)),
logout: () => dispatch(logout()),
dispatch: d => dispatch(d)
});
diff --git a/client/coral-embed-stream/src/NewCount.js b/client/coral-embed-stream/src/NewCount.js
new file mode 100644
index 000000000..712c36be3
--- /dev/null
+++ b/client/coral-embed-stream/src/NewCount.js
@@ -0,0 +1,18 @@
+import React, {PropTypes} from 'react';
+
+const NewCount = ({commentCount, countCache}) =>
+
+ {
+ countCache && commentCount - countCache > 0 &&
+
+ Load {commentCount - countCache} More Comments
+
+ }
+
;
+
+NewCount.propTypes = {
+ commentCount: PropTypes.number.isRequired,
+ countCache: PropTypes.number
+};
+
+export default NewCount;
diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js
index 2f0f4b01b..d59521484 100644
--- a/client/coral-embed-stream/src/Stream.js
+++ b/client/coral-embed-stream/src/Stream.js
@@ -17,10 +17,30 @@ class Stream extends React.Component {
constructor(props) {
super(props);
- this.state = {activeReplyBox: ''};
+ this.state = {activeReplyBox: '', countPoll: null};
this.setActiveReplyBox = this.setActiveReplyBox.bind(this);
}
+ componentDidMount() {
+ const {asset, getCounts, updateCountCache} = this.props;
+
+ updateCountCache(asset.id, asset.comments.length);
+
+ // Note: Apollo's built-in polling doesn't work with fetchMore queries, so a
+ // setInterval is being used instead.
+ this.setState({
+ countPoll: setInterval(() => getCounts({
+ asset_id: asset.id,
+ limit: asset.comments.length,
+ sort: 'REVERSE_CHRONOLOGICAL'
+ }), 5000),
+ });
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.state.countPoll);
+ }
+
setActiveReplyBox (reactKey) {
if (!this.props.currentUser) {
const offset = document.getElementById(`c_${reactKey}`).getBoundingClientRect().top - 75;
diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js
index d5db289c3..02e72ea98 100644
--- a/client/coral-framework/actions/asset.js
+++ b/client/coral-framework/actions/asset.js
@@ -38,6 +38,7 @@ export const updateOpenStream = closedBody => (dispatch, getState) => {
const openStream = () => ({type: actions.OPEN_COMMENTS});
const closeStream = () => ({type: actions.CLOSE_COMMENTS});
+export const updateCountCache = (id, count) => ({type: actions.UPDATE_COUNT_CACHE, id, count});
export const updateOpenStatus = status => dispatch => {
if (status === 'open') {
diff --git a/client/coral-framework/constants/asset.js b/client/coral-framework/constants/asset.js
index 40f746706..234095d9d 100644
--- a/client/coral-framework/constants/asset.js
+++ b/client/coral-framework/constants/asset.js
@@ -8,3 +8,4 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE';
export const OPEN_COMMENTS = 'OPEN_COMMENTS';
export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
+export const UPDATE_COUNT_CACHE = 'UPDATE_COUNT_CACHE';
diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js
index 09b3159d0..494ffa30e 100644
--- a/client/coral-framework/graphql/queries/index.js
+++ b/client/coral-framework/graphql/queries/index.js
@@ -1,6 +1,7 @@
import {graphql} from 'react-apollo';
import STREAM_QUERY from './streamQuery.graphql';
import LOAD_MORE from './loadMore.graphql';
+import GET_COUNTS from './getCounts.graphql';
import MY_COMMENT_HISTORY from './myCommentHistory.graphql';
function getQueryVariable(variable) {
@@ -17,6 +18,62 @@ function getQueryVariable(variable) {
return 'http://localhost/default/stream';
}
+export const getCounts = (data) => ({asset_id, limit, sort}) => {
+ return data.fetchMore({
+ query: GET_COUNTS,
+ variables: {
+ asset_id,
+ limit,
+ sort
+ },
+ updateQuery: (oldData, {fetchMoreResult:{data}}) => {
+
+ return {
+ ...oldData,
+ asset: {
+ ...oldData.asset,
+ commentCount: data.asset.commentCount
+ }
+ };
+ }
+ });
+};
+
+export const loadMore = (data) => ({limit, cursor, parent_id, asset_id, sort}) => {
+ return data.fetchMore({
+ query: LOAD_MORE,
+ variables: {
+ limit,
+ cursor,
+ parent_id,
+ asset_id,
+ sort
+ },
+ updateQuery: (oldData, {fetchMoreResult:{data:{new_top_level_comments}}}) =>
+
+ // If loading more replies
+ parent_id ? {
+ ...oldData,
+ asset: {
+ ...oldData.asset,
+ comments: oldData.asset.comments.map((comment) =>
+ comment.id === parent_id
+ ? {...comment, replies: [...comment.replies, ...new_top_level_comments]}
+ : comment)
+ }
+ }
+
+ // If loading more top-level comments
+ : {
+ ...oldData,
+ asset: {
+ ...oldData.asset,
+ comments: [...oldData.asset.comments, ...new_top_level_comments]
+ }
+ }
+ });
+};
+
export const queryStream = graphql(STREAM_QUERY, {
options: () => ({
variables: {
@@ -25,40 +82,8 @@ export const queryStream = graphql(STREAM_QUERY, {
}),
props: ({data}) => ({
data,
- loadMore: ({limit, cursor, parent_id, asset_id, sort}) => {
- return data.fetchMore({
- query: LOAD_MORE,
- variables: {
- limit,
- cursor,
- parent_id,
- asset_id,
- sort
- },
- updateQuery: (oldData, {fetchMoreResult:{data:{new_top_level_comments}}}) =>
-
- // If loading more replies
- parent_id ? {
- ...oldData,
- asset: {
- ...oldData.asset,
- comments: oldData.asset.comments.map((comment) =>
- comment.id === parent_id
- ? {...comment, replies: [...comment.replies, ...new_top_level_comments]}
- : comment)
- }
- }
-
- // If loading more top-level comments
- : {
- ...oldData,
- asset: {
- ...oldData.asset,
- comments: [...oldData.asset.comments, ...new_top_level_comments]
- }
- }
- });
- }
+ loadMore: loadMore(data),
+ getCounts: getCounts(data),
})
});
diff --git a/client/coral-framework/reducers/asset.js b/client/coral-framework/reducers/asset.js
index f9d0a55e3..067edfc5b 100644
--- a/client/coral-framework/reducers/asset.js
+++ b/client/coral-framework/reducers/asset.js
@@ -19,6 +19,9 @@ export default function asset (state = initialState, action) {
case actions.UPDATE_ASSET_SETTINGS_SUCCESS:
return state
.setIn(['settings'], action.settings);
+ case actions.UPDATE_COUNT_CACHE:
+ return state
+ .setIn(['countCache', action.id], action.count);
default:
return state;
}
diff --git a/test/client/coral-framework/reducers/assetReducer.spec.js b/test/client/coral-framework/reducers/assetReducer.spec.js
new file mode 100644
index 000000000..c54f51737
--- /dev/null
+++ b/test/client/coral-framework/reducers/assetReducer.spec.js
@@ -0,0 +1,19 @@
+import {Map} from 'immutable';
+import {expect} from 'chai';
+import assetReducer from '../../../../client/coral-framework/reducers/asset';
+import * as actions from '../../../../client/coral-framework/constants/asset';
+
+describe ('coral-embed-stream assetReducer', () => {
+ describe('UPDATE_COUNT_CACHE', () => {
+ it('should update the count cache', () => {
+ const action = {
+ type: actions.UPDATE_COUNT_CACHE,
+ id: '123',
+ count: 456
+ };
+ const store = new Map({});
+ const result = assetReducer(store, action);
+ expect(result.getIn(['countCache', '123'])).to.equal(456);
+ });
+ });
+});
diff --git a/test/client/coral-framework/reducers/notificationReducer.spec.js b/test/client/coral-framework/reducers/notificationReducer.spec.js
new file mode 100644
index 000000000..40c50d11a
--- /dev/null
+++ b/test/client/coral-framework/reducers/notificationReducer.spec.js
@@ -0,0 +1,35 @@
+import {Map} from 'immutable';
+import {expect} from 'chai';
+import notificationReducer from '../../../../client/coral-framework/reducers/notification';
+import * as actions from '../../../../client/coral-framework/actions/notification';
+
+describe ('notificationsReducer', () => {
+ describe('ADD_NOTIFICATION', () => {
+ it('should add a notification', () => {
+ const action = {
+ type: actions.ADD_NOTIFICATION,
+ text: 'Test notification',
+ notifType: 'test'
+ };
+ const store = new Map({});
+ const result = notificationReducer(store, action);
+ expect(result.get('text')).to.equal(action.text);
+ expect(result.get('type')).to.equal(action.notifType);
+ });
+ });
+
+ describe('CLEAR_NOTIFICATION', () => {
+ it('should clear a notification', () => {
+ const action = {
+ type: actions.CLEAR_NOTIFICATION
+ };
+ const store = new Map({
+ text: 'Test notification',
+ type: 'test'
+ });
+ const result = notificationReducer(store, action);
+ expect(result.get('text')).to.equal('');
+ expect(result.get('type')).to.equal('');
+ });
+ });
+});