mirror of
https://github.com/wassname/talk.git
synced 2026-07-08 02:25:49 +08:00
Merge branch 'comment-design-pass' of github.com:coralproject/talk into comment-design-pass
This commit is contained in:
@@ -9,7 +9,7 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
|
||||
|
||||
export const checkLogin = () => dispatch => {
|
||||
dispatch(checkLoginRequest());
|
||||
coralApi('/auth')
|
||||
return coralApi('/auth')
|
||||
.then(result => {
|
||||
const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length;
|
||||
dispatch(checkLoginSuccess(result.user, isAdmin));
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import {Card} from 'coral-ui';
|
||||
|
||||
const EmptyCard = props => (
|
||||
<Card style={{textAlign: 'center', maxWidth: 400, margin: '0 auto'}}>
|
||||
{props.children}
|
||||
</Card>
|
||||
);
|
||||
|
||||
EmptyCard.propTypes = {
|
||||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
export default EmptyCard;
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../../translations.json';
|
||||
import translations from 'coral-admin/src/translations.json';
|
||||
|
||||
import styles from './Community.css';
|
||||
import Table from './Table';
|
||||
import Loading from './Loading';
|
||||
import NoResults from './NoResults';
|
||||
import {Pager} from 'coral-ui';
|
||||
import EmptyCard from '../../components/EmptyCard';
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
@@ -54,13 +54,14 @@ const Community = ({isFetching, commenters, ...props}) => {
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
{ isFetching && <Loading /> }
|
||||
{ !hasResults && <NoResults /> }
|
||||
{ hasResults &&
|
||||
<Table
|
||||
headers={tableHeaders}
|
||||
data={commenters}
|
||||
onHeaderClickHandler={props.onHeaderClickHandler}
|
||||
/>
|
||||
{
|
||||
hasResults
|
||||
? <Table
|
||||
headers={tableHeaders}
|
||||
data={commenters}
|
||||
onHeaderClickHandler={props.onHeaderClickHandler}
|
||||
/>
|
||||
: <EmptyCard>{lang.t('community.no-results')}</EmptyCard>
|
||||
}
|
||||
<Pager
|
||||
totalPages={props.totalPages}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const NoResults = () => (
|
||||
<div>
|
||||
No users found with that user name or email address
|
||||
</div>
|
||||
);
|
||||
|
||||
export default NoResults;
|
||||
@@ -8,7 +8,11 @@ import {PermissionRequired} from '../components/PermissionRequired';
|
||||
class LayoutContainer extends Component {
|
||||
componentWillMount () {
|
||||
const {checkLogin} = this.props;
|
||||
checkLogin();
|
||||
checkLogin().then(() => {
|
||||
if (!this.props.auth.isAdmin) {
|
||||
location.href = '/admin/login';
|
||||
}
|
||||
});
|
||||
}
|
||||
render () {
|
||||
const {isAdmin, loggedIn, loadingUser} = this.props.auth;
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
|
||||
import Comment from './components/Comment';
|
||||
import EmptyCard from '../../components/EmptyCard';
|
||||
import {actionsMap} from './helpers/moderationQueueActionsMap';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from 'coral-admin/src/translations';
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const ModerationQueue = ({activeTab = 'premod', ...props}) => {
|
||||
const areComments = props.data[activeTab].length;
|
||||
return (
|
||||
<div id="moderationList">
|
||||
<ul>
|
||||
<ul style={{paddingLeft: 0}}>
|
||||
{
|
||||
props.data[activeTab].map((comment, i) => {
|
||||
areComments
|
||||
? props.data[activeTab].map((comment, i) => {
|
||||
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
|
||||
return <Comment
|
||||
key={i}
|
||||
@@ -22,6 +29,7 @@ const ModerationQueue = ({activeTab = 'premod', ...props}) => {
|
||||
currentAsset={props.currentAsset}
|
||||
/>;
|
||||
})
|
||||
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -29,7 +37,12 @@ const ModerationQueue = ({activeTab = 'premod', ...props}) => {
|
||||
};
|
||||
|
||||
ModerationQueue.propTypes = {
|
||||
data: PropTypes.object.isRequired
|
||||
data: PropTypes.object.isRequired,
|
||||
acceptComment: PropTypes.func.isRequired,
|
||||
rejectComment: PropTypes.func.isRequired,
|
||||
showBanUserDialog: PropTypes.func.isRequired,
|
||||
currentAsset: PropTypes.object,
|
||||
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired
|
||||
};
|
||||
|
||||
export default ModerationQueue;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, {PropTypes} from 'react';
|
||||
import timeago from 'timeago.js';
|
||||
import Linkify from 'react-linkify';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
@@ -17,7 +17,7 @@ const lang = new I18n(translations);
|
||||
|
||||
const Comment = ({actions = [], ...props}) => {
|
||||
const links = linkify.getMatches(props.comment.body);
|
||||
const actionSumaries = props.comment.action_summaries;
|
||||
const actionSummaries = props.comment.action_summaries;
|
||||
return (
|
||||
<li tabIndex={props.index}
|
||||
className={`mdl-card mdl-shadow--2dp ${styles.Comment} ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
|
||||
@@ -42,7 +42,7 @@ const Comment = ({actions = [], ...props}) => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{props.comment.user.banned === 'banned' ?
|
||||
{props.comment.user.status === 'banned' ?
|
||||
<span className={styles.banned}>
|
||||
<Icon name='error_outline'/>
|
||||
{lang.t('comment.banned_user')}
|
||||
@@ -62,11 +62,31 @@ const Comment = ({actions = [], ...props}) => {
|
||||
</Linkify>
|
||||
</p>
|
||||
</div>
|
||||
{actionSumaries && <FlagBox actionSumaries={actionSumaries} />}
|
||||
{actionSummaries && <FlagBox actionSummaries={actionSummaries} />}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
Comment.propTypes = {
|
||||
acceptComment: PropTypes.func.isRequired,
|
||||
rejectComment: PropTypes.func.isRequired,
|
||||
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
currentAsset: PropTypes.object,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
comment: PropTypes.shape({
|
||||
body: PropTypes.string.isRequired,
|
||||
action_summaries: PropTypes.array,
|
||||
created_at: PropTypes.string.isRequired,
|
||||
user: PropTypes.shape({
|
||||
status: PropTypes.string
|
||||
}),
|
||||
asset: PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
id: PropTypes.string
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
const linkStyles = {
|
||||
backgroundColor: 'rgb(255, 219, 135)',
|
||||
padding: '1px 2px'
|
||||
|
||||
@@ -5,7 +5,7 @@ const FlagBox = props => (
|
||||
<div className={styles.flagBox}>
|
||||
<h3>Flags:</h3>
|
||||
<ul>
|
||||
{props.actionSumaries.map((action, i) =>
|
||||
{props.actionSummaries.map((action, i) =>
|
||||
<li key={i}>{!action.reason ? <i>No reason provided</i> : action.reason} (<strong>{action.count}</strong>)</li>
|
||||
)}
|
||||
</ul>
|
||||
@@ -13,7 +13,7 @@ const FlagBox = props => (
|
||||
);
|
||||
|
||||
FlagBox.propTypes = {
|
||||
actionSumaries: PropTypes.array.isRequired
|
||||
actionSummaries: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default FlagBox;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {Link} from 'react-router';
|
||||
|
||||
import {Pager, Icon} from 'coral-ui';
|
||||
import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl';
|
||||
import EmptyCard from 'coral-admin/src/components/EmptyCard';
|
||||
|
||||
const limit = 25;
|
||||
|
||||
@@ -142,22 +143,25 @@ class Streams extends Component {
|
||||
<Radio value='asc'>{lang.t('streams.oldest')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
<DataTable className={styles.streamsTable} rows={assetsIds} onClick={this.goToModeration}>
|
||||
<TableHeader name="title" cellFormatter={this.renderTitle}>{lang.t('streams.article')}</TableHeader>
|
||||
<TableHeader name="publication_date" cellFormatter={this.renderDate}>
|
||||
{lang.t('streams.pubdate')}
|
||||
</TableHeader>
|
||||
<TableHeader name="closedAt" cellFormatter={this.renderStatus} className={styles.status}>
|
||||
{lang.t('streams.status')}
|
||||
</TableHeader>
|
||||
</DataTable>
|
||||
<Pager
|
||||
totalPages={Math.ceil((assets.count || 0) / limit)}
|
||||
page={this.state.page}
|
||||
onNewPageHandler={this.onPageClick}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
assetsIds.length
|
||||
? <div className={styles.mainContent}>
|
||||
<DataTable className={styles.streamsTable} rows={assetsIds} onClick={this.goToModeration}>
|
||||
<TableHeader name="title" cellFormatter={this.renderTitle}>{lang.t('streams.article')}</TableHeader>
|
||||
<TableHeader name="publication_date" cellFormatter={this.renderDate}>
|
||||
{lang.t('streams.pubdate')}
|
||||
</TableHeader>
|
||||
<TableHeader name="closedAt" cellFormatter={this.renderStatus} className={styles.status}>
|
||||
{lang.t('streams.status')}
|
||||
</TableHeader>
|
||||
</DataTable>
|
||||
<Pager
|
||||
totalPages={Math.ceil((assets.count || 0) / limit)}
|
||||
page={this.state.page}
|
||||
onNewPageHandler={this.onPageClick} />
|
||||
</div>
|
||||
: <EmptyCard>{lang.t('streams.empty_result')}</EmptyCard>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"admin": "Administrator",
|
||||
"moderator": "Moderator",
|
||||
"role": "Select role...",
|
||||
"no-results": "No users found with that user name or email address.",
|
||||
"no-results": "No users found with that user name or email address. They're hiding!",
|
||||
"status": "Status",
|
||||
"select-status": "Select status...",
|
||||
"active": "Active",
|
||||
@@ -32,6 +32,7 @@
|
||||
"prevcomment": "Go to the previous comment",
|
||||
"singleview": "Toggle single comment edit view",
|
||||
"thismenu": "Open this menu",
|
||||
"emptyqueue": "No more comments to moderate! You're all caught up. Go have some ☕️",
|
||||
"showshortcuts": "Show Shortcuts"
|
||||
},
|
||||
"comment": {
|
||||
@@ -113,6 +114,7 @@
|
||||
"comment_count": "Comments"
|
||||
},
|
||||
"streams": {
|
||||
"empty_result": "No assets match this search. Maybe try widening your search?",
|
||||
"search": "Search",
|
||||
"filter-streams": "Filter Streams",
|
||||
"stream-status": "Stream Status",
|
||||
@@ -152,6 +154,7 @@
|
||||
"flagged": "marcado",
|
||||
"shortcuts": "Atajos de teclado",
|
||||
"close": "Cerrar",
|
||||
"emptyqueue": "No se encontro ningún usuario. Están escondidos.",
|
||||
"showshortcuts": "Mostrar atajos"
|
||||
},
|
||||
"comment": {
|
||||
@@ -220,6 +223,7 @@
|
||||
"comment_count": "Comentarios"
|
||||
},
|
||||
"streams": {
|
||||
"empty_result": "No se encuentro articulo con esta busqueda. Tal vez extender la busqueda?",
|
||||
"search": "",
|
||||
"filter-streams": "",
|
||||
"stream-status": "",
|
||||
|
||||
@@ -177,7 +177,7 @@ class Comment extends React.Component {
|
||||
comment.replies &&
|
||||
<div className='coral-load-more-replies'>
|
||||
<LoadMore
|
||||
id={asset.id}
|
||||
assetId={asset.id}
|
||||
comments={comment.replies}
|
||||
parentId={comment.id}
|
||||
moreComments={comment.replyCount > comment.replies.length}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -98,6 +100,11 @@ class Embed extends Component {
|
||||
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();
|
||||
|
||||
return (
|
||||
<div style={expandForLogin}>
|
||||
<div className="commentStream">
|
||||
@@ -132,6 +139,8 @@ class Embed extends Component {
|
||||
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}
|
||||
@@ -147,6 +156,14 @@ class Embed extends Component {
|
||||
}
|
||||
{!loggedIn && <SignInContainer requireEmailConfirmation={asset.settings.requireEmailConfirmation} offset={signInOffset}/>}
|
||||
{loggedIn && user && <ChangeUsernameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
|
||||
<NewCount
|
||||
commentCount={asset.commentCount}
|
||||
countCache={countCache[asset.id]}
|
||||
loadMore={this.props.loadMore}
|
||||
firstCommentDate={firstCommentDate}
|
||||
assetId={asset.id}
|
||||
updateCountCache={this.props.updateCountCache}
|
||||
/>
|
||||
<Stream
|
||||
refetch={refetch}
|
||||
addNotification={this.props.addNotification}
|
||||
@@ -155,6 +172,8 @@ class Embed extends Component {
|
||||
currentUser={user}
|
||||
postLike={this.props.postLike}
|
||||
postFlag={this.props.postFlag}
|
||||
getCounts={this.props.getCounts}
|
||||
updateCountCache={this.props.updateCountCache}
|
||||
loadMore={this.props.loadMore}
|
||||
deleteAction={this.props.deleteAction}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
@@ -216,6 +235,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
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)
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ const loadMoreComments = (assetId, comments, loadMore, parentId) => {
|
||||
}
|
||||
|
||||
const cursor = parentId
|
||||
? comments[1].created_at
|
||||
? comments[0].created_at
|
||||
: comments[comments.length - 1].created_at;
|
||||
|
||||
loadMore({
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
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) => {
|
||||
e.preventDefault();
|
||||
updateCountCache(assetId, commentCount);
|
||||
loadMore({
|
||||
limit: 500,
|
||||
cursor: firstCommentDate,
|
||||
assetId,
|
||||
sort: 'CHRONOLOGICAL'
|
||||
}, true);
|
||||
};
|
||||
|
||||
const NewCount = (props) => {
|
||||
const newComments = props.commentCount - props.countCache;
|
||||
|
||||
return <div className='coral-new-comments'>
|
||||
{
|
||||
props.countCache && newComments > 0 &&
|
||||
<button onClick={onLoadMoreClick(props)} className='coral-load-more'>
|
||||
{newComments === 1
|
||||
? lang.t('newCount', newComments, lang.t('comment'))
|
||||
: lang.t('newCount', newComments, lang.t('comments'))}
|
||||
</button>
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
|
||||
NewCount.propTypes = {
|
||||
commentCount: PropTypes.number.isRequired,
|
||||
countCache: PropTypes.number,
|
||||
loadMore: PropTypes.func.isRequired,
|
||||
assetId: PropTypes.string.isRequired,
|
||||
firstCommentDate: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default NewCount;
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import Comment from './Comment';
|
||||
import {NEW_COMMENT_COUNT_POLL_INTERVAL} from 'coral-framework/constants/comments';
|
||||
|
||||
class Stream extends React.Component {
|
||||
|
||||
@@ -17,10 +18,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.commentCount);
|
||||
|
||||
// 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'
|
||||
}), NEW_COMMENT_COUNT_POLL_INTERVAL),
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.state.countPoll);
|
||||
}
|
||||
|
||||
setActiveReplyBox (reactKey) {
|
||||
if (!this.props.currentUser) {
|
||||
const offset = document.getElementById(`c_${reactKey}`).getBoundingClientRect().top - 75;
|
||||
|
||||
@@ -359,18 +359,26 @@ button.coral-load-more {
|
||||
text-align: center;
|
||||
color: #FFF;
|
||||
background-color: #2376D8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.coral-load-more:hover {
|
||||
background-color: #4399FF;
|
||||
}
|
||||
|
||||
.coral-load-more-replies {
|
||||
.coral-load-more-replies, .coral-new-comments {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.coral-load-more-replies button.coral-load-more {
|
||||
.coral-new-comments {
|
||||
position: relative;
|
||||
top: 1.8em;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.coral-load-more-replies button.coral-load-more, .coral-new-comments button.coral-load-more{
|
||||
width: initial;
|
||||
}
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export const ADDTL_COMMENTS_ON_LOAD_MORE = 10;
|
||||
export const NEW_COMMENT_COUNT_POLL_INTERVAL = 20000;
|
||||
|
||||
@@ -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,72 @@ 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}, newComments) => {
|
||||
return data.fetchMore({
|
||||
query: LOAD_MORE,
|
||||
variables: {
|
||||
limit,
|
||||
cursor,
|
||||
parent_id,
|
||||
asset_id,
|
||||
sort
|
||||
},
|
||||
updateQuery: (oldData, {fetchMoreResult:{data:{new_top_level_comments}}}) => {
|
||||
|
||||
let updatedAsset;
|
||||
|
||||
if (parent_id) {
|
||||
|
||||
// If loading more replies
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
comments: oldData.asset.comments.map((comment) =>
|
||||
comment.id === parent_id
|
||||
? {...comment, replies: [...comment.replies, ...new_top_level_comments]}
|
||||
: 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 queryStream = graphql(STREAM_QUERY, {
|
||||
options: () => ({
|
||||
variables: {
|
||||
@@ -25,40 +92,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),
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"button": "Submit",
|
||||
"error": "Usernames can contain letters, numbers and _ only"
|
||||
},
|
||||
"newCount": "View {0} more {1}",
|
||||
"comment": "comment",
|
||||
"comments": "comments",
|
||||
"error": {
|
||||
"emailNotVerified": "Email address {0} not verified.",
|
||||
"email": "Not a valid E-Mail",
|
||||
@@ -39,6 +42,9 @@
|
||||
"bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information",
|
||||
"editNameMsg": "",
|
||||
"loadMore": "Ver más",
|
||||
"newCount": "Ver {0} {1} más",
|
||||
"comment": "commentario",
|
||||
"comments": "commentarios",
|
||||
"error": {
|
||||
"emailNotVerified": "Dirección de correo electrónico {0} no verificada.",
|
||||
"email": "No es un email válido",
|
||||
|
||||
@@ -29,6 +29,8 @@ class CommentBox extends Component {
|
||||
commentPostedHandler,
|
||||
postItem,
|
||||
assetId,
|
||||
updateCountCache,
|
||||
countCache,
|
||||
parentId,
|
||||
addNotification,
|
||||
authorId
|
||||
@@ -44,14 +46,17 @@ class CommentBox extends Component {
|
||||
if (this.props.charCount && this.state.body.length > this.props.charCount) {
|
||||
return;
|
||||
}
|
||||
updateCountCache(assetId, countCache + 1);
|
||||
postItem(comment, 'comments')
|
||||
.then(({data}) => {
|
||||
const postedComment = data.createComment.comment;
|
||||
|
||||
if (postedComment.status === 'REJECTED') {
|
||||
addNotification('error', lang.t('comment-post-banned-word'));
|
||||
updateCountCache(assetId, countCache);
|
||||
} else if (postedComment.status === 'PREMOD') {
|
||||
addNotification('success', lang.t('comment-post-notif-premod'));
|
||||
updateCountCache(assetId, countCache);
|
||||
} else {
|
||||
addNotification('success', 'Your comment has been posted.');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"en":{
|
||||
"userNoComment": "This user has not yet left a comment.",
|
||||
"userNoComment": "You've never left a comment. Join the conversation!",
|
||||
"allComments": "All Comments",
|
||||
"profileSettings": "Profile Settings",
|
||||
"myCommentHistory": "My comment History",
|
||||
@@ -9,7 +9,7 @@
|
||||
"fromSettingsPage": "From the Settings Page you can see your comment history."
|
||||
},
|
||||
"es":{
|
||||
"userNoComment": "Aún no ha escrito ningún comentario.",
|
||||
"userNoComment": "No has dejado áun ningún comentario. ¡Unete a la conversación!",
|
||||
"allComments": "Todos los comentarios",
|
||||
"profileSettings": "Configuración del perfil",
|
||||
"myCommentHistory": "Mi historial de comentarios",
|
||||
|
||||
@@ -150,10 +150,8 @@ const createPublicComment = (context, commentInput) => {
|
||||
item_id: comment.id,
|
||||
item_type: 'COMMENTS',
|
||||
action_type: 'FLAG',
|
||||
metadata: {
|
||||
field: 'body',
|
||||
details: 'Matched suspect word filters.'
|
||||
}
|
||||
group_id: 'Matched suspect word filter',
|
||||
metadata: {}
|
||||
})
|
||||
.then(() => comment);
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ type RootQuery {
|
||||
# Comments returned based on a query.
|
||||
comments(query: CommentsQuery!): [Comment]
|
||||
|
||||
# Returne the count of comments satisfied by the query. Note that this edge is
|
||||
# Return the count of comments satisfied by the query. Note that this edge is
|
||||
# expensive as it is not batched. Requires the `ADMIN` role.
|
||||
commentCount(query: CommentCountQuery!): Int
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user