Refactor embed

This commit is contained in:
Chi Vinh Le
2017-04-18 23:20:39 +07:00
parent b40eed4751
commit af5be0469f
20 changed files with 431 additions and 375 deletions
@@ -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});
@@ -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';
+70 -281
View File
@@ -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 <Spinner />;
}
// 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 = <UserBox user={user} logout={() => this.props.logout().then(refetch)} changeTab={this.changeTab}/>;
const userBox = <UserBox user={user} logout={this.props.logout} changeTab={this.changeTab}/>;
return (
<div style={expandForLogin}>
@@ -169,137 +61,50 @@ class Embed extends React.Component {
<Tab restricted={!isAdmin}>Configure Stream</Tab>
</TabBar>
{
highlightedComment &&
<Button
cStyle='darkGrey'
style={{float: 'right'}}
onClick={() => {
this.props.viewAllComments();
this.props.data.refetch();
}}>{lang.t('showAllComments')}</Button>
comment &&
<Button
cStyle='darkGrey'
style={{float: 'right'}}
onClick={this.exitHighlighting}
>
{lang.t('showAllComments')}
</Button>
}
<TabContent show={activeTab === 0}>
{ loggedIn ? userBox : null }
{
openStream
? <div id="commentBox">
<InfoBox
content={asset.settings.infoBoxContent}
enable={asset.settings.infoBoxEnable}
/>
<QuestionBox
content={asset.settings.questionBoxContent}
enable={asset.settings.questionBoxEnable}
/>
<RestrictedContent restricted={banned} restrictedComp={
<SuspendedAccount
canEditName={user && user.canEditName}
editName={this.props.editName}
/>
}>
{
user
? <CommentBox
addNotification={this.props.addNotification}
postItem={this.props.postItem}
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
updateCountCache={this.props.updateCountCache}
countCache={countCache[asset.id]}
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
}
</RestrictedContent>
</div>
: <p>{asset.settings.closedMessage}</p>
}
{!loggedIn && <SignInContainer
requireEmailConfirmation={asset.settings.requireEmailConfirmation}
refetch={refetch}
offset={signInOffset}/>}
{loggedIn && user && <ChangeUsernameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
{loggedIn && <ModerationLink assetId={asset.id} isAdmin={isAdmin} />}
{/* the highlightedComment is isolated after the user followed a permalink */}
{
highlightedComment
? <HighlightedComment
refetch={refetch}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.state.activeReplyBox}
addNotification={this.props.addNotification}
depth={0}
postItem={this.props.postItem}
asset={asset}
currentUser={user}
highlighted={comment.id}
postLike={this.props.postLike}
postFlag={this.props.postFlag}
postDontAgree={this.props.postDontAgree}
loadMore={this.props.loadMore}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
key={highlightedComment.id}
reactKey={highlightedComment.id}
comment={highlightedComment} />
: <div>
<NewCount
commentCount={asset.commentCount}
countCache={countCache[asset.id]}
loadMore={this.props.loadMore}
firstCommentDate={firstCommentDate}
assetId={asset.id}
updateCountCache={this.props.updateCountCache}
/>
<div className="embed__stream">
<Stream
open={openStream}
addNotification={this.props.addNotification}
postItem={this.props.postItem}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.state.activeReplyBox}
asset={asset}
currentUser={user}
postLike={this.props.postLike}
postFlag={this.props.postFlag}
postDontAgree={this.props.postDontAgree}
addCommentTag={this.props.addCommentTag}
removeCommentTag={this.props.removeCommentTag}
ignoreUser={this.props.ignoreUser}
loadMore={this.props.loadMore}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
comments={asset.comments}
ignoredUsers={this.props.userData.ignoredUsers} />
</div>
<LoadMore
topLevel={true}
assetId={asset.id}
comments={asset.comments}
moreComments={hasOlderComments}
loadMore={this.props.loadMore} />
</div>
}
<Stream
addNotification={this.props.addNotification}
postItem={this.props.postItem}
setActiveReplyBox={this.props.setActiveReplyBox}
activeReplyBox={this.props.activeReplyBox}
asset={asset}
currentUser={user}
postLike={this.props.postLike}
postFlag={this.props.postFlag}
postDontAgree={this.props.postDontAgree}
addCommentTag={this.props.addCommentTag}
removeCommentTag={this.props.removeCommentTag}
ignoreUser={this.props.ignoreUser}
loadMore={this.props.loadMore}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
comments={asset.comments}
ignoredUsers={this.props.data.myIgnoredUsers}
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}
/>
</TabContent>
<TabContent show={activeTab === 1}>
<ProfileContainer
loggedIn={loggedIn}
userData={this.props.userData}
showSignInDialog={this.props.showSignInDialog}
/>
<ProfileContainer />
</TabContent>
<TabContent show={activeTab === 2}>
<RestrictedContent restricted={!loggedIn}>
{ loggedIn ? userBox : null }
<ConfigureStreamContainer
status={status}
onClick={this.toggleStatus}
/>
<ConfigureStreamContainer />
</RestrictedContent>
</TabContent>
</div>
@@ -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,
};
@@ -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);
@@ -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 <div className='coral-new-comments coral-load-more'>
{
props.countCache && newComments > 0 ?
props.commentCountCache && newComments > 0 ?
<button onClick={onLoadMoreClick(props)}>
{newComments === 1
? lang.t('newCount', newComments, lang.t('comment'))
@@ -32,7 +32,7 @@ const NewCount = (props) => {
NewCount.propTypes = {
commentCount: PropTypes.number.isRequired,
countCache: PropTypes.number,
commentCountCache: PropTypes.number,
loadMore: PropTypes.func.isRequired,
assetId: PropTypes.string.isRequired,
firstCommentDate: PropTypes.string.isRequired
@@ -1,48 +1,36 @@
import React, {PropTypes} from 'react';
import Comment from './Comment';
import CommentBox from 'coral-plugin-commentbox/CommentBox';
import SignInContainer from 'coral-sign-in/containers/SignInContainer';
import SuspendedAccount from 'coral-framework/components/SuspendedAccount';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import ChangeUsernameContainer from 'coral-sign-in/containers/ChangeUsernameContainer';
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
import InfoBox from 'coral-plugin-infobox/InfoBox';
import QuestionBox from 'coral-plugin-questionbox/QuestionBox';
import LoadMore from './LoadMore';
import NewCount from './NewCount';
import {ModerationLink} from 'coral-plugin-moderation';
class Stream extends React.Component {
static propTypes = {
addNotification: PropTypes.func.isRequired,
postItem: PropTypes.func.isRequired,
asset: PropTypes.object.isRequired,
open: PropTypes.bool.isRequired,
comments: PropTypes.array.isRequired,
currentUser: PropTypes.shape({
username: PropTypes.string,
id: PropTypes.string
}),
// dispatch action to add a tag to a comment
addCommentTag: PropTypes.func,
// dispatch action to remove a tag from a comment
removeCommentTag: PropTypes.func,
// dispatch action to ignore another user
ignoreUser: React.PropTypes.func,
// list of user ids that should be rendered as ignored
ignoredUsers: React.PropTypes.arrayOf(React.PropTypes.string)
}
constructor(props) {
super(props);
this.state = {activeReplyBox: '', countPoll: null};
setActiveReplyBox = (reactKey) => {
if (!this.props.auth.user) {
const offset = document.getElementById(`c_${reactKey}`).getBoundingClientRect().top - 75;
this.props.showSignInDialog(offset);
} else {
this.props.setActiveReplyBox(reactKey);
}
}
render () {
const {
comments,
currentUser,
asset,
postItem,
addNotification,
postFlag,
postLike,
open,
postDontAgree,
loadMore,
deleteAction,
@@ -52,45 +40,173 @@ class Stream extends React.Component {
pluginProps,
ignoreUser,
ignoredUsers,
auth: {signInOffset, loggedIn, isAdmin, user},
comment,
refetch,
commentCountCache,
editName,
} = this.props;
const open = asset.closedAt === null;
// even though the permalinked comment is the highlighted one, we're displaying its parent + replies
const highlightedComment = comment && comment.parent ? comment.parent : comment;
const banned = user && user.status === 'BANNED';
const hasOlderComments = !!(
asset &&
asset.lastComment &&
asset.lastComment.id !== asset.comments[asset.comments.length - 1].id
);
// 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 commentIsIgnored = (comment) => ignoredUsers && ignoredUsers.includes(comment.user.id);
return (
<div id='stream'>
{
comments.map(comment =>
commentIsIgnored(comment)
? <IgnoredCommentTombstone
key={comment.id}
/>
: <Comment
disableReply={!open}
setActiveReplyBox={this.props.setActiveReplyBox}
activeReplyBox={this.props.activeReplyBox}
addNotification={addNotification}
depth={0}
postItem={postItem}
asset={asset}
currentUser={currentUser}
postLike={postLike}
postFlag={postFlag}
postDontAgree={postDontAgree}
addCommentTag={addCommentTag}
removeCommentTag={removeCommentTag}
ignoreUser={ignoreUser}
commentIsIgnored={commentIsIgnored}
loadMore={loadMore}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
reactKey={comment.id}
comment={comment}
pluginProps={pluginProps}
/>
)
open
? <div id="commentBox">
<InfoBox
content={asset.settings.infoBoxContent}
enable={asset.settings.infoBoxEnable}
/>
<QuestionBox
content={asset.settings.questionBoxContent}
enable={asset.settings.questionBoxEnable}
/>
<RestrictedContent restricted={banned} restrictedComp={
<SuspendedAccount
canEditName={user && user.canEditName}
editName={editName}
/>
}>
{
user
? <CommentBox
addNotification={this.props.addNotification}
postItem={this.props.postItem}
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
setCommentCountCache={this.props.setCommentCountCache}
commentCountCache={commentCountCache}
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
}
</RestrictedContent>
</div>
: <p>{asset.settings.closedMessage}</p>
}
{!loggedIn && <SignInContainer
requireEmailConfirmation={asset.settings.requireEmailConfirmation}
refetch={refetch}
offset={signInOffset}/>}
{loggedIn && user && <ChangeUsernameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
{loggedIn && <ModerationLink assetId={asset.id} isAdmin={isAdmin} />}
{/* the highlightedComment is isolated after the user followed a permalink */}
{
highlightedComment
? <Comment
refetch={refetch}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.props.activeReplyBox}
addNotification={addNotification}
depth={0}
postItem={this.props.postItem}
asset={asset}
currentUser={user}
highlighted={comment.id}
postLike={this.props.postLike}
postFlag={this.props.postFlag}
postDontAgree={this.props.postDontAgree}
loadMore={this.props.loadMore}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
key={highlightedComment.id}
commentIsIgnored={commentIsIgnored}
reactKey={highlightedComment.id}
comment={highlightedComment} />
: <div>
<NewCount
commentCount={asset.commentCount}
commentCountCache={commentCountCache}
loadMore={this.props.loadMore}
firstCommentDate={firstCommentDate}
assetId={asset.id}
setCommentCountCache={this.props.setCommentCountCache}
/>
<div className="embed__stream">
{
comments.map(comment =>
commentIsIgnored(comment)
? <IgnoredCommentTombstone
key={comment.id}
/>
: <Comment
disableReply={!open}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.props.activeReplyBox}
addNotification={addNotification}
depth={0}
postItem={postItem}
asset={asset}
currentUser={user}
postLike={postLike}
postFlag={postFlag}
postDontAgree={postDontAgree}
addCommentTag={addCommentTag}
removeCommentTag={removeCommentTag}
ignoreUser={ignoreUser}
commentIsIgnored={commentIsIgnored}
loadMore={loadMore}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
reactKey={comment.id}
comment={comment}
pluginProps={pluginProps}
/>
)
}
</div>
<LoadMore
topLevel={true}
assetId={asset.id}
comments={asset.comments}
moreComments={hasOlderComments}
loadMore={this.props.loadMore} />
</div>
}
</div>
);
}
}
Stream.propTypes = {
addNotification: PropTypes.func.isRequired,
postItem: PropTypes.func.isRequired,
asset: PropTypes.object.isRequired,
comments: PropTypes.array.isRequired,
// dispatch action to add a tag to a comment
addCommentTag: PropTypes.func,
// dispatch action to remove a tag from a comment
removeCommentTag: PropTypes.func,
// dispatch action to ignore another user
ignoreUser: React.PropTypes.func,
// list of user ids that should be rendered as ignored
ignoredUsers: React.PropTypes.arrayOf(React.PropTypes.string)
};
export default Stream;
@@ -0,0 +1,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;
@@ -0,0 +1,103 @@
import React from 'react';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import isEqual from 'lodash/isEqual';
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';
const {logout, showSignInDialog, requestConfirmEmail} = authActions;
const {addNotification, clearNotification} = notificationActions;
const {fetchAssetSuccess} = assetActions;
class EmbedContainer extends React.Component {
componentDidMount() {
pym.sendMessage('childReady');
}
componentWillUnmount() {
clearInterval(this.countPoll);
}
componentWillReceiveProps(nextProps) {
const {fetchAssetSuccess} = this.props;
if(!isEqual(nextProps.data.asset, this.props.data.asset)) {
// TODO: remove asset data from redux store.
fetchAssetSuccess(nextProps.data.asset);
const {getCounts, setCommentCountCache, commentCountCache} = this.props;
const {asset} = nextProps.data;
if (commentCountCache === -1) {
setCommentCountCache(asset.commentCount);
}
this.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);
}
}
render() {
if (!this.props.data.asset) {
return <Spinner />;
}
return <Embed {...this.props} />;
}
}
const mapStateToProps = state => ({
auth: state.auth.toJS(),
commentCountCache: state.stream.commentCountCache,
activeReplyBox: state.stream.activeReplyBox,
});
const mapDispatchToProps = dispatch =>
bindActionCreators({
showSignInDialog,
requestConfirmEmail,
fetchAssetSuccess,
addNotification,
clearNotification,
editName,
setCommentCountCache,
viewAllComments,
logout,
setActiveReplyBox,
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
postComment,
postFlag,
postLike,
postDontAgree,
addCommentTag,
removeCommentTag,
ignoreUser,
deleteAction,
queryStream,
)(EmbedContainer);
@@ -3,9 +3,12 @@ import {render} from 'react-dom';
import {ApolloProvider} from 'react-apollo';
import {client} from 'coral-framework/services/client';
import store from 'coral-framework/services/store';
import {store, injectReducers} from 'coral-framework/services/store';
import Embed from './Embed';
import Embed from './containers/Embed';
import reducers from './reducers';
injectReducers(reducers);
render(
<ApolloProvider client={client} store={store}>
@@ -0,0 +1,5 @@
import stream from './stream';
export default {
stream
};
@@ -0,0 +1,23 @@
import * as actions from '../constants/stream';
const initialState = {
activeReplyBox: '',
commentCountCache: -1,
};
export default function stream(state = initialState, action) {
switch (action.type) {
case actions.SET_ACTIVE_REPLY_BOX:
return {
...state,
activeReplyBox: action.id,
};
case actions.SET_COMMENT_COUNT_CACHE:
return {
...state,
commentCountCache: action.amount,
};
default:
return state;
}
}
-1
View File
@@ -39,7 +39,6 @@ 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') {
@@ -8,6 +8,5 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE';
export const OPEN_COMMENTS = 'OPEN_COMMENTS';
export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
export const UPDATE_COUNT_CACHE = 'UPDATE_COUNT_CACHE';
export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS';
@@ -1,2 +0,0 @@
export const ADDTL_COMMENTS_ON_LOAD_MORE = 10;
export const NEW_COMMENT_COUNT_POLL_INTERVAL = 20000;
@@ -50,4 +50,8 @@ query AssetQuery($asset_id: ID, $asset_url: String, $comment_id: ID!, $has_comme
}
}
}
myIgnoredUsers {
id,
username,
}
}
-3
View File
@@ -19,9 +19,6 @@ 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;
}
+14 -5
View File
@@ -24,11 +24,20 @@ if (window.devToolsExtension) {
middlewares.push(window.devToolsExtension());
}
export default createStore(
combineReducers({
...mainReducer,
apollo: client.reducer()
}),
let storeReducers = {
...mainReducer,
apollo: client.reducer()
};
export const store = createStore(
combineReducers(storeReducers),
{},
compose(...middlewares)
);
export default store;
export function injectReducers(reducers) {
storeReducers = {...storeReducers, ...reducers};
store.replaceReducer(combineReducers(storeReducers));
}
+9 -6
View File
@@ -29,9 +29,9 @@ class CommentBox extends Component {
commentPostedHandler,
postItem,
assetId,
updateCountCache,
setCommentCountCache,
isReply,
countCache,
commentCountCache,
parentId,
addNotification,
authorId
@@ -47,24 +47,27 @@ class CommentBox extends Component {
if (this.props.charCount && this.state.body.length > this.props.charCount) {
return;
}
!isReply && updateCountCache(assetId, countCache + 1);
!isReply && setCommentCountCache(commentCountCache + 1);
postItem(comment, 'comments')
.then(({data}) => {
const postedComment = data.createComment.comment;
if (postedComment.status === 'REJECTED') {
addNotification('error', lang.t('comment-post-banned-word'));
!isReply && updateCountCache(assetId, countCache);
!isReply && setCommentCountCache(commentCountCache);
} else if (postedComment.status === 'PREMOD') {
addNotification('success', lang.t('comment-post-notif-premod'));
!isReply && updateCountCache(assetId, countCache);
!isReply && setCommentCountCache(commentCountCache);
}
if (commentPostedHandler) {
commentPostedHandler();
}
})
.catch((err) => console.error(err));
.catch((err) => {
console.error(err);
setCommentCountCache(commentCountCache);
});
this.setState({body: ''});
}
@@ -2,6 +2,7 @@ import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import React, {Component} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import {bindActionCreators} from 'redux';
import {myCommentHistory, myIgnoredUsers} from 'coral-framework/graphql/queries';
import {stopIgnoringUser} from 'coral-framework/graphql/mutations';
@@ -11,6 +12,7 @@ import NotLoggedIn from '../components/NotLoggedIn';
import IgnoredUsers from '../components/IgnoredUsers';
import {Spinner} from 'coral-ui';
import CommentHistory from 'coral-plugin-history/CommentHistory';
import {showSignInDialog} from 'coral-framework/actions/auth';
import translations from '../translations';
const lang = new I18n(translations);
@@ -32,10 +34,10 @@ class ProfileContainer extends Component {
}
render() {
const {loggedIn, asset, showSignInDialog, data, myIgnoredUsersData, stopIgnoringUser} = this.props;
const {asset, showSignInDialog, data, myIgnoredUsersData, stopIgnoringUser} = this.props;
const {me} = this.props.data;
if (!loggedIn || !me) {
if (!me) {
return <NotLoggedIn showSignInDialog={showSignInDialog} requireEmailConfirmation={asset.settings.requireEmailConfirmation}/>;
}
@@ -48,7 +50,7 @@ class ProfileContainer extends Component {
return (
<div>
<h2>{this.props.userData.username}</h2>
<h2>{this.props.user.username}</h2>
{ emailAddress
? <p>{ emailAddress }</p>
: null
@@ -93,10 +95,8 @@ const mapStateToProps = state => ({
auth: state.auth.toJS()
});
const mapDispatchToProps = () => ({
// saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData))
});
const mapDispatchToProps = dispatch =>
bindActionCreators({showSignInDialog}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
@@ -1,5 +1,5 @@
import React from 'react';
import styles from 'coral-embed-stream/src/Comment.css';
import styles from 'coral-embed-stream/src/components/Comment.css';
import AuthorName from 'coral-plugin-author-name/AuthorName';
import Content from 'coral-plugin-commentcontent/CommentContent';