Merge branch 'master' into 142993479-tags

This commit is contained in:
Kim Gardner
2017-05-02 13:41:39 -04:00
committed by GitHub
31 changed files with 366 additions and 228 deletions
+1 -1
View File
@@ -243,7 +243,7 @@ file under the `scripts` key including:
# Setup
Once you've installed Talk (either via Docker or source), you still need to
setup the application. If you are unfamiliar with any terminoligy used in the
setup the application. If you are unfamiliar with any terminology used in the
setup process, refer to the `TERMINOLOGY.md` document.
## Via Web
+3
View File
@@ -42,6 +42,9 @@ const routes = (
<Route path='all' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
<Route path='accepted' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
<Route path='premod' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
@@ -6,6 +6,13 @@ import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQu
const ActionButton = ({type = '', status, ...props}) => {
const typeName = type.toLowerCase();
const active = ((type === 'REJECT' && status === 'REJECTED') || (type === 'APPROVE' && status === 'ACCEPTED'));
let text = menuActionsMap[type].text;
if (text === 'Approve' && active) {
text = 'Approved';
} else if (text === 'Reject' && active) {
text = 'Rejected';
}
return (
<Button
@@ -13,7 +20,7 @@ const ActionButton = ({type = '', status, ...props}) => {
cStyle={typeName}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
>{menuActionsMap[type].text}</Button>
>{text}</Button>
);
};
@@ -138,6 +138,9 @@ class ModerationContainer extends Component {
case 'all':
activeTabCount = data.allCount;
break;
case 'accepted':
activeTabCount = data.acceptedCount;
break;
case 'premod':
activeTabCount = data.premodCount;
break;
@@ -155,6 +158,7 @@ class ModerationContainer extends Component {
<ModerationMenu
asset={asset}
allCount={data.allCount}
acceptedCount={data.acceptedCount}
premodCount={data.premodCount}
rejectedCount={data.rejectedCount}
flaggedCount={data.flaggedCount}
@@ -1,9 +1,25 @@
import React, {PropTypes} from 'react';
import styles from './CommentCount.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const CommentCount = props => (
<span className={styles.count}>{props.count}</span>
);
const CommentCount = ({count}) => {
let number = count;
// shorten large counts to abbreviations
if (number / 1e9 > 1) {
number = `${(number / 1e9).toFixed(1)}${lang.t('modqueue.billion')}`;
} else if (number / 1e6 > 1) {
number = `${(number / 1e6).toFixed(1)}${lang.t('modqueue.million')}`;
} else if (number / 1e3 > 1) {
number = `${(number / 1e3).toFixed(1)}${lang.t('modqueue.thousand')}`;
}
return (
<span className={styles.count}>{number}</span>
);
};
CommentCount.propTypes = {
count: PropTypes.number.isRequired
@@ -23,7 +23,7 @@ LoadMore.propTypes = {
comments: PropTypes.array.isRequired,
loadMore: PropTypes.func.isRequired,
sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged', 'all']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged', 'all', 'accepted']).isRequired,
assetId: PropTypes.string,
showLoadMore: PropTypes.bool.isRequired
};
@@ -10,7 +10,7 @@ import {Link} from 'react-router';
const lang = new I18n(translations);
const ModerationMenu = (
{asset, allCount, premodCount, rejectedCount, flaggedCount, selectSort, sort}
{asset, allCount, acceptedCount, premodCount, rejectedCount, flaggedCount, selectSort, sort}
) => {
function getPath (type) {
@@ -28,6 +28,12 @@ const ModerationMenu = (
activeClassName={styles.active}>
<Icon name='question_answer' className={styles.tabIcon} /> {lang.t('modqueue.all')} <CommentCount count={allCount} />
</Link>
<Link
to={getPath('accepted')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
<Icon name='check' className={styles.tabIcon} /> {lang.t('modqueue.approved')} <CommentCount count={acceptedCount} />
</Link>
<Link
to={getPath('premod')}
className={`mdl-tabs__tab ${styles.tab}`}
@@ -54,6 +54,19 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
},
updateQueries: {
ModQueue: (oldData) => {
const comment = oldData.all.find(c => c.id === commentId);
let accepted;
let acceptedCount = oldData.acceptedCount;
// if the comment was already in the Approved queue, don't re-add it
if (comment.status === 'ACCEPTED') {
accepted = [...oldData.accepted];
} else {
comment.status = 'ACCEPTED';
acceptedCount++;
accepted = [comment, ...oldData.accepted];
}
const premod = oldData.premod.filter(c => c.id !== commentId);
const flagged = oldData.flagged.filter(c => c.id !== commentId);
const rejected = oldData.rejected.filter(c => c.id !== commentId);
@@ -65,9 +78,11 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
...oldData,
premodCount,
flaggedCount,
acceptedCount,
rejectedCount,
premod,
flagged,
accepted,
rejected,
};
}
@@ -82,21 +97,35 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
},
updateQueries: {
ModQueue: (oldData) => {
const comment = oldData.premod.concat(oldData.flagged).filter(c => c.id === commentId)[0];
const rejected = [comment].concat(oldData.rejected);
const comment = oldData.all.find(c => c.id === commentId);
let rejected;
let rejectedCount = oldData.rejectedCount;
// if the item was already in the Rejected queue, don't put it in again
if (comment.status === 'REJECTED') {
rejected = oldData.rejected;
} else {
comment.status = 'REJECTED';
rejectedCount++;
rejected = [comment, ...oldData.rejected];
}
const premod = oldData.premod.filter(c => c.id !== commentId);
const flagged = oldData.flagged.filter(c => c.id !== commentId);
const accepted = oldData.accepted.filter(c => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = oldData.rejectedCount + 1;
const acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount;
return {
...oldData,
premodCount,
flaggedCount,
acceptedCount,
rejectedCount,
premod,
flagged,
accepted,
rejected
};
}
@@ -39,6 +39,9 @@ export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) =>
case 'all':
statuses = null;
break;
case 'accepted':
statuses = ['ACCEPTED'];
break;
case 'premod':
statuses = ['PREMOD'];
break;
@@ -8,6 +8,13 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
}) {
...commentView
}
accepted: comments(query: {
statuses: [ACCEPTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
premod: comments(query: {
statuses: [PREMOD],
asset_id: $asset_id,
@@ -38,6 +45,10 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
allCount: commentCount(query: {
asset_id: $asset_id
})
acceptedCount: commentCount(query: {
statuses: [ACCEPTED],
asset_id: $asset_id
})
premodCount: commentCount(query: {
statuses: [PREMOD],
asset_id: $asset_id
+11 -2
View File
@@ -36,6 +36,7 @@
"modqueue": {
"likes": "likes",
"all": "all",
"approved": "approved",
"premod": "pre-mod",
"rejected": "rejected",
"flagged": "flagged",
@@ -62,7 +63,10 @@
"impersonating": "Impersonating",
"offensive": "Offensive",
"spam/ads": "Spam/Ads",
"other": "Other"
"other": "Other",
"thousand": "k",
"million": "M",
"billion": "B"
},
"comment": {
"flagged": "flagged",
@@ -227,6 +231,8 @@
"loading": "Cargando resultados"
},
"modqueue": {
"all": "todos",
"approved": "aprobado",
"likes": "gustos",
"premod": "pre-mod",
"rejected": "rechazado",
@@ -245,7 +251,10 @@
"impersonating": "Suplantación",
"offensive": "Ofensivo",
"spam/ads": "Spam/Propaganda",
"other": "Otros"
"other": "Otros",
"thousand": "m",
"million": "M",
"billion": "B"
},
"comment": {
"flagged": "marcado",
@@ -197,7 +197,9 @@ class Comment extends React.Component {
}
<Content body={comment.body} />
<Slot fill="commentContent" />
<div className="commentActionsLeft comment__action-container">
<Slot fill="commentReactions" inline />
<ActionButton>
{/* TODO implmement iPerformedThisAction for the like */}
<LikeButton
@@ -228,7 +230,7 @@ class Comment extends React.Component {
</IfUserCanModifyBest>
</ActionButton>
<Slot
fill="commentDetail"
fill="commentActions"
data={this.props.data}
root={this.props.root}
comment={comment}
@@ -33,12 +33,14 @@ export default class Embed extends React.Component {
}
}
handleShowProfile = () => this.props.setActiveTab('profile');
render () {
const {activeTab, logout, viewAllComments, commentId} = this.props;
const {asset: {totalCommentCount}} = this.props.root;
const {loggedIn, isAdmin, user} = this.props.auth;
const userBox = <UserBox user={user} logout={logout} changeTab={this.changeTab}/>;
const userBox = <UserBox user={user} onLogout={logout} onShowProfile={this.handleShowProfile}/>;
return (
<div>
+146 -139
View File
@@ -1,28 +1,29 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
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';
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 Comment from '../containers/Comment';
import InfoBox from 'coral-plugin-infobox/InfoBox';
import {ModerationLink} from 'coral-plugin-moderation';
import CommentBox from 'coral-plugin-commentbox/CommentBox';
import QuestionBox from 'coral-plugin-questionbox/QuestionBox';
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
import SuspendedAccount from 'coral-framework/components/SuspendedAccount';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import ChangeUsernameContainer
from 'coral-sign-in/containers/ChangeUsernameContainer';
class Stream extends React.Component {
setActiveReplyBox = (reactKey) => {
setActiveReplyBox = reactKey => {
if (!this.props.auth.user) {
this.props.showSignInDialog();
} else {
this.props.setActiveReplyBox(reactKey);
}
}
};
render () {
render() {
const {
root: {asset, asset: {comments}, comment, myIgnoredUsers},
postItem,
@@ -39,152 +40,158 @@ class Stream extends React.Component {
ignoreUser,
auth: {loggedIn, isAdmin, user},
commentCountCache,
editName,
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 highlightedComment = comment && comment.parent
? comment.parent
: comment;
const banned = user && user.status === 'BANNED';
const hasOlderComments = !!(
asset &&
const hasOlderComments = !!(asset &&
asset.lastComment &&
asset.lastComment.id !== asset.comments[asset.comments.length - 1].id
);
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) => myIgnoredUsers && myIgnoredUsers.includes(comment.user.id);
const commentIsIgnored = comment =>
myIgnoredUsers && myIgnoredUsers.includes(comment.user.id);
return (
<div id='stream'>
{
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}
authorId={user.id}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount} />
: null
}
</RestrictedContent>
</div>
: <p>{asset.settings.closedMessage}</p>
}
{!loggedIn && <Button id='coralSignInButton' onClick={this.props.showSignInDialog} full>Sign in to comment</Button>}
{loggedIn && user && <ChangeUsernameContainer loggedIn={loggedIn} user={user} />}
<div id="stream">
{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}
authorId={user.id}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
/>
: null}
</RestrictedContent>
</div>
: <p>{asset.settings.closedMessage}</p>}
{!loggedIn &&
<Button
id="coralSignInButton"
onClick={this.props.showSignInDialog}
full
>
Sign in to comment
</Button>}
{loggedIn &&
user &&
<ChangeUsernameContainer loggedIn={loggedIn} user={user} />}
{loggedIn && <ModerationLink assetId={asset.id} isAdmin={isAdmin} />}
{/* the highlightedComment is isolated after the user followed a permalink */}
{
highlightedComment
{highlightedComment
? <Comment
data={this.props.data}
root={this.props.root}
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}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
/>
: <div>
<NewCount
commentCount={asset.commentCount}
commentCountCache={commentCountCache}
data={this.props.data}
root={this.props.root}
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}
firstCommentDate={firstCommentDate}
assetId={asset.id}
setCommentCountCache={this.props.setCommentCountCache}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
key={highlightedComment.id}
commentIsIgnored={commentIsIgnored}
reactKey={highlightedComment.id}
comment={highlightedComment}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
/>
: <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
data={this.props.data}
root={this.props.root}
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}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
/>
)
}
</div>
<LoadMore
topLevel={true}
assetId={asset.id}
comments={asset.comments}
moreComments={hasOlderComments}
loadMore={this.props.loadMore} />
</div>
}
<div className="embed__stream">
{comments.map(
comment =>
(commentIsIgnored(comment)
? <IgnoredCommentTombstone key={comment.id} />
: <Comment
data={this.props.data}
root={this.props.root}
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}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
/>)
)}
</div>
<LoadMore
topLevel={true}
assetId={asset.id}
comments={asset.comments}
moreComments={hasOlderComments}
loadMore={this.props.loadMore}
/>
</div>}
</div>
);
}
@@ -201,7 +208,7 @@ Stream.propTypes = {
removeCommentTag: PropTypes.func,
// dispatch action to ignore another user
ignoreUser: React.PropTypes.func,
ignoreUser: React.PropTypes.func
};
export default Stream;
@@ -3,7 +3,15 @@ import Comment from '../components/Comment';
import withFragments from 'coral-framework/hocs/withFragments';
import {getSlotsFragments} from 'coral-framework/helpers/plugins';
const pluginFragments = getSlotsFragments(['commentInfoBar', 'commentDetail']);
const pluginFragments = getSlotsFragments([
'streamQuestionArea',
'commentInputArea',
'commentInputDetailArea',
'commentInfoBar',
'commentActions',
'commentContent',
'commentReactions'
]);
export default withFragments({
root: gql`
@@ -36,5 +44,5 @@ export default withFragments({
${pluginFragments.spreads('comment')}
}
${pluginFragments.definitions('comment')}
`,
`
})(Comment);
@@ -3,6 +3,8 @@ import {compose, gql, graphql} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import isEqual from 'lodash/isEqual';
import branch from 'recompose/branch';
import renderComponent from 'recompose/renderComponent';
import {Spinner} from 'coral-ui';
import {authActions, assetActions, pym} from 'coral-framework';
@@ -19,7 +21,6 @@ class EmbedContainer extends React.Component {
componentDidMount() {
pym.sendMessage('childReady');
this.props.checkLogin();
}
componentWillReceiveProps(nextProps) {
@@ -108,6 +109,10 @@ const mapDispatchToProps = dispatch =>
export default compose(
connect(mapStateToProps, mapDispatchToProps),
branch(
props => !props.auth.checkedInitialLogin,
renderComponent(Spinner),
),
withQuery,
)(EmbedContainer);
@@ -19,24 +19,13 @@ const {showSignInDialog} = authActions;
const {addNotification} = notificationActions;
class StreamContainer extends React.Component {
getCounts = ({asset_id, limit, sort}) => {
getCounts = (variables) => {
return this.props.data.fetchMore({
query: LOAD_COMMENT_COUNTS_QUERY,
variables: {
asset_id,
limit,
sort,
excludeIgnored: this.props.data.variables.excludeIgnored,
},
updateQuery: (oldData, {fetchMoreResult:{asset}}) => {
return {
...oldData,
asset: {
...oldData.asset,
commentCount: asset.commentCount
}
};
}
variables,
// Apollo requires this, even though we don't use it...
updateQuery: data => data,
});
};
@@ -118,14 +107,11 @@ class StreamContainer extends React.Component {
};
componentDidMount() {
this.props.data.refetch();
if (this.props.previousTab) {
this.props.data.refetch();
}
this.countPoll = setInterval(() => {
const {asset} = this.props.root;
this.getCounts({
asset_id: asset.id,
limit: asset.comments.length,
sort: 'REVERSE_CHRONOLOGICAL'
});
this.getCounts(this.props.data.variables);
}, NEW_COMMENT_COUNT_POLL_INTERVAL);
}
@@ -139,13 +125,13 @@ class StreamContainer extends React.Component {
}
const LOAD_COMMENT_COUNTS_QUERY = gql`
query LoadCommentCounts($asset_id: ID, $limit: Int = 5, $sort: SORT_ORDER) {
asset(id: $asset_id) {
query LoadCommentCounts($assetUrl: String, $assetId: ID, $excludeIgnored: Boolean) {
asset(id: $assetId, url: $assetUrl) {
id
commentCount
comments(sort: $sort, limit: $limit) {
commentCount(excludeIgnored: $excludeIgnored)
comments(limit: 10) {
id
replyCount
replyCount(excludeIgnored: $excludeIgnored)
}
}
}
@@ -236,6 +222,7 @@ const mapStateToProps = state => ({
assetId: state.stream.assetId,
assetUrl: state.stream.assetUrl,
activeTab: state.embed.activeTab,
previousTab: state.embed.previousTab,
});
const mapDispatchToProps = dispatch =>
+6
View File
@@ -3,6 +3,7 @@ import {render} from 'react-dom';
import {ApolloProvider} from 'react-apollo';
import {client} from 'coral-framework/services/client';
import {checkLogin} from 'coral-framework/actions/auth';
import reducers from './reducers';
import localStore, {injectReducers} from 'coral-framework/services/store';
@@ -12,6 +13,11 @@ injectReducers(reducers);
const store = (window.opener && window.opener.coralStore) ? window.opener.coralStore : localStore;
// Don't run this in the popup.
if (store === localStore) {
store.dispatch(checkLogin());
}
render(
<ApolloProvider client={client} store={store}>
<AppRouter />
@@ -2,6 +2,7 @@ import * as actions from '../constants/embed';
const initialState = {
activeTab: 'stream',
previousTab: '',
};
export default function stream(state = initialState, action) {
@@ -10,6 +11,7 @@ export default function stream(state = initialState, action) {
return {
...state,
activeTab: action.tab,
previousTab: state.activeTab,
};
default:
return state;
+9 -13
View File
@@ -1,20 +1,16 @@
import React, {Component} from 'react';
import {getSlotElements} from 'coral-framework/helpers/plugins';
import React from 'react';
import cn from 'classnames';
import styles from './Slot.css';
import {getSlotElements} from 'coral-framework/helpers/plugins';
class Slot extends Component {
render() {
const {fill, inline = false, ...rest} = this.props;
return (
<div className={inline ? styles.inline : ''}>
{getSlotElements(fill, rest)}
</div>
);
}
export default function Slot ({fill, inline = false, ...rest}) {
return (
<div className={cn({[styles.inline]: inline})}>
{getSlotElements(fill, rest)}
</div>
);
}
Slot.propTypes = {
fill: React.PropTypes.string
};
export default Slot;
@@ -57,7 +57,7 @@ export const postComment = graphql(POST_COMMENT, {
...oldData.asset,
comments: oldData.asset.comments.map((oldComment) => {
return oldComment.id === parent_id
? {...oldComment, replies: [...oldComment.replies, comment]}
? {...oldComment, replies: [...oldComment.replies, comment], replyCount: oldComment.replyCount + 1}
: oldComment;
})
}
+3 -3
View File
@@ -44,7 +44,7 @@ function getComponentFragments(components) {
* Returns an object that can be used to compose fragments or queries.
*
* Example:
* const pluginFragments = getSlotsFragments(['commentInfoBar', 'commentDetail']);
* const pluginFragments = getSlotsFragments(['commentInfoBar', 'commentActions']);
* const rootFragment = gql`
* fragment Comment_root on RootQuery {
+ ${pluginFragments.spreads('root')}
@@ -65,10 +65,10 @@ export function getSlotsFragments(slots) {
const fragments = getComponentFragments(components);
return {
spreads(key) {
return fragments[key] && fragments[key].spreads;
return (fragments[key] && fragments[key].spreads) || '';
},
definitions(key) {
return fragments[key] && fragments[key].definitions;
return (fragments[key] && fragments[key].definitions) || '';
},
};
}
+8 -1
View File
@@ -8,6 +8,7 @@ const initialState = Map({
user: null,
showSignInDialog: false,
showCreateUsernameDialog: false,
checkedInitialLogin: false,
view: 'SIGNIN',
error: '',
passwordRequestSuccess: null,
@@ -71,10 +72,12 @@ export default function auth (state = initialState, action) {
.set('isLoading', true);
case actions.CHECK_LOGIN_FAILURE:
return state
.set('checkedInitialLogin', true)
.set('loggedIn', false)
.set('user', null);
case actions.CHECK_LOGIN_SUCCESS:
return state
.set('checkedInitialLogin', true)
.set('loggedIn', true)
.set('isAdmin', action.isAdmin)
.set('user', purge(action.user));
@@ -114,7 +117,11 @@ export default function auth (state = initialState, action) {
.set('isLoading', false)
.set('successSignUp', true);
case actions.LOGOUT_SUCCESS:
return initialState;
return state
.set('user', null)
.set('isLoading', false)
.set('loggedIn', false)
.set('isAdmin', false);
case actions.INVALID_FORM:
return state
.set('error', action.error);
+6 -5
View File
@@ -1,13 +1,13 @@
import React, {Component, PropTypes} from 'react';
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
import {connect} from 'react-redux';
import {I18n} from '../coral-framework';
import translations from './translations.json';
import {Button} from 'coral-ui';
import Slot from 'coral-framework/components/Slot';
import {connect} from 'react-redux';
const name = 'coral-plugin-commentbox';
class CommentBox extends Component {
class CommentBox extends React.Component {
constructor(props) {
super(props);
@@ -151,13 +151,14 @@ class CommentBox extends Component {
id={isReply ? 'replyText' : 'commentText'}
onChange={this.handleChange}
rows={3}/>
<Slot fill='commentInputArea' />
</div>
<div className={`${name}-char-count ${length > maxCharCount ? `${name}-char-max` : ''}`}>
{maxCharCount && `${maxCharCount - length} ${lang.t('characters-remaining')}`}
</div>
<div className={`${name}-button-container`}>
<Slot
fill="commentBoxDetail"
fill="commentInputDetailArea"
registerHook={this.registerHook}
unregisterHook={this.unregisterHook}
inline
@@ -1,5 +1,6 @@
import React from 'react';
const packagename = 'coral-plugin-questionbox';
import Slot from 'coral-framework/components/Slot';
const QuestionBox = ({enable, content}) =>
<div className={`${packagename}-info ${enable ? null : 'hidden'}` }>
@@ -10,6 +11,7 @@ const QuestionBox = ({enable, content}) =>
<div className={`${packagename}-content`}>
{content}
</div>
<Slot fill="streamQuestionArea" />
</div>;
export default QuestionBox;
+3 -3
View File
@@ -4,11 +4,11 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const UserBox = ({className, user, logout, changeTab}) => (
const UserBox = ({className, user, onLogout, onShowProfile}) => (
<div className={`${styles.userBox} ${className ? className : ''}`}>
{lang.t('signIn.loggedInAs')}
<a onClick={() => changeTab(1)}>{user.username}</a>. {lang.t('signIn.notYou')}
<a className={styles.logout} onClick={logout} id='logout'>{lang.t('signIn.logout')}</a>
<a onClick={onShowProfile}>{user.username}</a>. {lang.t('signIn.notYou')}
<a className={styles.logout} onClick={onLogout} id='logout'>{lang.t('signIn.logout')}</a>
</div>
);
View File
+6 -2
View File
@@ -96,11 +96,15 @@
"prop-types": "^15.5.8",
"react-apollo": "^1.1.0",
"react-recaptcha": "^2.2.6",
"recompose": "^0.23.1",
"redis": "^2.7.1",
"uuid": "^3.0.1",
"simplemde": "^1.11.2",
"subscriptions-transport-ws": "^0.5.5-alpha.0",
"resolve": "^1.3.2",
"semver": "^5.3.0"
"semver": "^5.3.0",
"simplemde": "^1.11.2",
"uuid": "^3.0.1"
},
"devDependencies": {
"apollo-client": "^1.0.4",
@@ -179,8 +183,8 @@
"redux-thunk": "^2.1.0",
"regenerator": "^0.8.46",
"selenium-standalone": "^5.11.2",
"subscriptions-transport-ws": "^0.5.5-alpha.0",
"style-loader": "^0.16.0",
"subscriptions-transport-ws": "^0.5.5-alpha.0",
"supertest": "^2.0.1",
"timeago.js": "^2.0.3",
"webpack": "^2.3.1"
@@ -3,7 +3,7 @@ import OffTopicTag from './components/OffTopicTag';
export default {
slots: {
commentBoxDetail: [OffTopicCheckbox],
commentInputDetailArea: [OffTopicCheckbox],
commentInfoBar: [OffTopicTag]
}
};
+1 -1
View File
@@ -2,6 +2,6 @@ import RespectButton from './containers/RespectButton';
export default {
slots: {
commentDetail: [RespectButton],
commentActions: [RespectButton],
}
};
+35 -14
View File
@@ -9,6 +9,10 @@
git-url-parse "^6.0.2"
shelljs "^0.7.0"
"@types/async@^2.0.31":
version "2.0.40"
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.40.tgz#ac02de68e66c004a61b7cb16df8b1db3a254cca9"
"@types/express-serve-static-core@*":
version "4.0.44"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.44.tgz#a1c3bd5d80e93c72fba91a03f5412c47f21d4ae7"
@@ -26,6 +30,14 @@
version "0.8.6"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.8.6.tgz#b34fb880493ba835b0c067024ee70130d6f9bb68"
"@types/graphql@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.0.tgz#fccf859f0d2817687f210737dc3be48a18b1d754"
"@types/isomorphic-fetch@0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.33.tgz#3ea1b86f8b73e6a7430d01d4dbd5b1f63fd72718"
"@types/mime@*":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-0.0.29.tgz#fbcfd330573b912ef59eeee14602bface630754b"
@@ -1474,6 +1486,10 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
change-emitter@^0.1.2:
version "0.1.6"
resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515"
character-parser@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0"
@@ -2982,7 +2998,7 @@ fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
fbjs@^0.8.4, fbjs@^0.8.9:
fbjs@^0.8.1, fbjs@^0.8.4, fbjs@^0.8.9:
version "0.8.12"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
dependencies:
@@ -3503,7 +3519,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6,
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
graphql-anywhere@^3.0.0:
graphql-anywhere@^3.0.0, graphql-anywhere@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-3.0.1.tgz#73531db861174c8f212eafb9f8e84944b38b4e5a"
@@ -3586,7 +3602,7 @@ graphql@^0.7.2:
dependencies:
iterall "1.0.2"
graphql@^0.9.1:
graphql@^0.9.1, graphql@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.9.3.tgz#71fc0fa331bffb9c20678485861cfb370803118e"
dependencies:
@@ -3727,7 +3743,7 @@ hoek@4.x.x:
version "4.1.1"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.1.1.tgz#9cc573ffba2b7b408fb5e9c2a13796be94cddce9"
hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0:
hoist-non-react-statics@^1.0.0, hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
@@ -4953,11 +4969,11 @@ lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@3.10.1:
lodash@3.10.1, lodash@^3.3.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
lodash@3.9.3, lodash@^3.3.1:
lodash@3.9.3:
version "3.9.3"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.9.3.tgz#0159e86832feffc6d61d852b12a953b99496bd32"
@@ -6919,6 +6935,15 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
recompose@^0.23.1:
version "0.23.1"
resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.23.1.tgz#577613e24a7ff56f9ca6b899190f8a9c0857fc20"
dependencies:
change-emitter "^0.1.2"
fbjs "^0.8.1"
hoist-non-react-statics "^1.0.0"
symbol-observable "^1.0.4"
redis-commands@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b"
@@ -6976,7 +7001,7 @@ redux-thunk@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
redux@^3.6.0:
redux@^3.4.0, redux@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"
dependencies:
@@ -7766,7 +7791,7 @@ svgo@^0.7.0:
version "0.0.21"
resolved "https://registry.yarnpkg.com/sylvester/-/sylvester-0.0.21.tgz#2987b1ce2bd2f38b0dce2a34388884bfa4400ea7"
symbol-observable@^1.0.2:
symbol-observable@^1.0.2, symbol-observable@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
@@ -8254,7 +8279,7 @@ whatwg-encoding@^1.0.1:
dependencies:
iconv-lite "0.4.13"
whatwg-fetch@>=0.10.0:
whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
@@ -8324,7 +8349,7 @@ word-wrap@1.2.1, word-wrap@^1.0.3:
version "1.2.1"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.1.tgz#248f459b465d179a17bc407c854d3151d07e45d8"
wordwrap@0.0.2:
wordwrap@0.0.2, wordwrap@~0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
@@ -8332,10 +8357,6 @@ wordwrap@^1.0.0, wordwrap@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"