mirror of
https://github.com/wassname/talk.git
synced 2026-07-04 19:08:33 +08:00
Merge branch 'master' into events
This commit is contained in:
+56
-8
@@ -8,6 +8,7 @@ const util = require('./util');
|
||||
const program = require('commander');
|
||||
const inquirer = require('inquirer');
|
||||
const { graphql } = require('graphql');
|
||||
const helpers = require('../services/migration/helpers');
|
||||
const { stripIndent } = require('common-tags');
|
||||
const Table = require('cli-table');
|
||||
|
||||
@@ -28,11 +29,23 @@ const CommentModel = require('../models/comment');
|
||||
const ActionModel = require('../models/action');
|
||||
const USER_ROLES = require('../models/enum/user_roles');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const databaseVerifications = require('./verifications/database');
|
||||
|
||||
// Register the shutdown criteria.
|
||||
util.onshutdown([() => mongoose.disconnect()]);
|
||||
|
||||
/**
|
||||
* transforms a specific action to a removal action on the target model.
|
||||
*/
|
||||
const actionDecrTransformer = ({ item_id, action_type, group_id }) => ({
|
||||
query: { id: item_id },
|
||||
update: {
|
||||
$inc: {
|
||||
[`action_counts.${action_type.toLowerCase()}`]: -1,
|
||||
[`action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}`]: -1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Deletes a user and cleans up their associated verifications.
|
||||
*/
|
||||
@@ -63,8 +76,27 @@ async function deleteUser(userID) {
|
||||
return util.shutdown();
|
||||
}
|
||||
|
||||
const { transformSingleWithCursor } = helpers({
|
||||
queryBatchSize: 10000,
|
||||
updateBatchSize: 10000,
|
||||
});
|
||||
|
||||
console.warn("Removing user's actions");
|
||||
|
||||
// Remove all actions against comments.
|
||||
await transformSingleWithCursor(
|
||||
ActionModel.collection.find({ user_id: user.id, item_type: 'COMMENTS' }),
|
||||
actionDecrTransformer,
|
||||
CommentModel
|
||||
);
|
||||
|
||||
// Remove all actions against users.
|
||||
await transformSingleWithCursor(
|
||||
ActionModel.collection.find({ user_id: user.id, item_type: 'USERS' }),
|
||||
actionDecrTransformer,
|
||||
UserModel
|
||||
);
|
||||
|
||||
// Remove all the user's actions.
|
||||
await ActionModel.where({ user_id: user.id })
|
||||
.setOptions({ multi: true })
|
||||
@@ -72,18 +104,34 @@ async function deleteUser(userID) {
|
||||
|
||||
console.warn("Removing user's comments");
|
||||
|
||||
// Removes all the user's reply counts on each of the comments that they
|
||||
// have commented on.
|
||||
await transformSingleWithCursor(
|
||||
CommentModel.collection.aggregate([
|
||||
{ $match: { author_id: user.id } },
|
||||
{
|
||||
$group: {
|
||||
_id: '$parent_id',
|
||||
count: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
]),
|
||||
({ _id: parent_id, count }) => ({
|
||||
query: { id: parent_id },
|
||||
update: {
|
||||
$inc: {
|
||||
reply_count: -1 * count,
|
||||
},
|
||||
},
|
||||
}),
|
||||
CommentModel
|
||||
);
|
||||
|
||||
// Remove all the user's comments.
|
||||
await CommentModel.where({ author_id: user.id })
|
||||
.setOptions({ multi: true })
|
||||
.remove();
|
||||
|
||||
console.warn('Updating the database indexes');
|
||||
|
||||
// Update the counts that might have changed.
|
||||
for (const verification of databaseVerifications) {
|
||||
await verification({ fix: true, limit: Infinity, batch: 1000 });
|
||||
}
|
||||
|
||||
console.warn('Removing the user');
|
||||
|
||||
// Remove the user.
|
||||
|
||||
@@ -5,16 +5,11 @@
|
||||
background: white;
|
||||
padding: 10px 12px;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
line-height: 24px;
|
||||
font-size: 17px;
|
||||
height: 47px;
|
||||
border-radius: 3px;
|
||||
text-transform: capitalize;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.03), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.09);
|
||||
width: 129px;
|
||||
transform: scale(.8);
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
margin: 0 0 .5em;
|
||||
|
||||
&:not(:disabled):hover {
|
||||
box-shadow: none;
|
||||
|
||||
@@ -5,16 +5,11 @@
|
||||
background: white;
|
||||
padding: 10px 11px;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
line-height: 24px;
|
||||
font-size: 17px;
|
||||
height: 47px;
|
||||
border-radius: 3px;
|
||||
text-transform: capitalize;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.03), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.09);
|
||||
width: 129px;
|
||||
transform: scale(.8);
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
margin: 0 0 .5em;
|
||||
|
||||
&:not(:disabled):hover {
|
||||
color: white;
|
||||
|
||||
@@ -81,6 +81,8 @@
|
||||
}
|
||||
|
||||
.sideActions {
|
||||
/* IE 11 needs this... */
|
||||
width: 105px;
|
||||
}
|
||||
|
||||
.editedMarker {
|
||||
|
||||
@@ -2,8 +2,8 @@ export const NEXT_STEP = 'NEXT_STEP';
|
||||
export const GO_TO_STEP = 'GO_TO_STEP';
|
||||
export const PREVIOUS_STEP = 'PREVIOUS_STEP';
|
||||
export const ADD_ERROR = 'ADD_ERROR';
|
||||
export const HAS_ERROR = 'HAS_ERROR';
|
||||
export const SHOW_ERRORS = 'SHOW_ERRORS';
|
||||
export const HAS_ERROR = 'HAS_ERROR';
|
||||
export const SHOW_ERRORS = 'SHOW_ERRORS';
|
||||
export const CLEAR_ERRORS = 'CLEAR_ERRORS';
|
||||
export const INSTALL_REQUEST = 'INSTALL_REQUEST';
|
||||
export const INSTALL_SUCCESS = 'INSTALL_SUCCESS';
|
||||
|
||||
@@ -3,7 +3,7 @@ import styles from './FlaggedUser.css';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { username } from 'talk-plugin-flags/helpers/flagReasons';
|
||||
import { username } from 'coral-framework/graphql/flagReasons';
|
||||
import ApproveButton from 'coral-admin/src/components/ApproveButton';
|
||||
import RejectButton from 'coral-admin/src/components/RejectButton';
|
||||
import { isFlaggedUserDangling } from '../utils';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Router, Route } from 'react-router';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Embed from './containers/Embed';
|
||||
import { LoginContainer } from 'coral-sign-in/containers/LoginContainer';
|
||||
import { LoginContainer } from './containers/LoginContainer';
|
||||
|
||||
const routes = (
|
||||
<div>
|
||||
|
||||
@@ -70,3 +70,17 @@ export const removeCommentClassName = idx => ({
|
||||
export const setActiveTab = tab => dispatch => {
|
||||
dispatch({ type: actions.SET_ACTIVE_TAB, tab });
|
||||
};
|
||||
|
||||
export const addCommentBoxTag = tag => ({
|
||||
type: actions.ADD_COMMENT_BOX_TAG,
|
||||
tag,
|
||||
});
|
||||
|
||||
export const removeCommentBoxTag = idx => ({
|
||||
type: actions.REMOVE_COMMENT_BOX_TAG,
|
||||
idx,
|
||||
});
|
||||
|
||||
export const clearCommentBoxTags = () => ({
|
||||
type: actions.CLEAR_COMMENT_BOX_TAGS,
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import AutomaticAssetClosure from '../containers/AutomaticAssetClosure';
|
||||
|
||||
import ExtendableTabPanel from '../containers/ExtendableTabPanel';
|
||||
import { Tab, TabPane } from 'coral-ui';
|
||||
import ProfileContainer from 'coral-settings/containers/ProfileContainer';
|
||||
import ProfileContainer from '../tabs/profile/containers/ProfileContainer';
|
||||
import Popup from 'coral-framework/components/Popup';
|
||||
import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty';
|
||||
import cn from 'classnames';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const prefix = 'TALK_EMBED_STREAM';
|
||||
|
||||
export const SET_ACTIVE_REPLY_BOX = 'SET_ACTIVE_REPLY_BOX';
|
||||
export const ADDTL_COMMENTS_ON_LOAD_MORE = 10;
|
||||
export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS';
|
||||
@@ -7,3 +9,6 @@ export const REMOVE_COMMENT_CLASSNAME = 'REMOVE_COMMENT_CLASSNAME';
|
||||
export const THREADING_LEVEL = process.env.TALK_THREADING_LEVEL;
|
||||
export const SET_ACTIVE_TAB = 'CORAL_STREAM_SET_ACTIVE_TAB';
|
||||
export const SET_SORT = 'CORAL_STREAM_SET_SORT';
|
||||
export const ADD_COMMENT_BOX_TAG = `${prefix}_COMMENT_BOX_ADD_TAG`;
|
||||
export const REMOVE_COMMENT_BOX_TAG = `${prefix}_COMMENT_BOX_REMOVE_TAG`;
|
||||
export const CLEAR_COMMENT_BOX_TAGS = `${prefix}_COMMENT_BOX_CLEAR_TAGS`;
|
||||
|
||||
@@ -93,9 +93,6 @@ export default {
|
||||
name
|
||||
created_at
|
||||
}
|
||||
assigned_by {
|
||||
id
|
||||
}
|
||||
}
|
||||
user {
|
||||
id
|
||||
|
||||
@@ -4,12 +4,10 @@ import embed from './embed';
|
||||
import config from './config';
|
||||
import configure from './configure';
|
||||
import stream from './stream';
|
||||
import { reducer as commentBox } from '../../../talk-plugin-commentbox';
|
||||
|
||||
export default {
|
||||
auth,
|
||||
asset,
|
||||
commentBox,
|
||||
embed,
|
||||
config,
|
||||
configure,
|
||||
|
||||
@@ -25,6 +25,7 @@ const initialState = {
|
||||
previousTab: '',
|
||||
sortBy: 'CREATED_AT',
|
||||
sortOrder: 'DESC',
|
||||
commentBoxTags: [],
|
||||
};
|
||||
|
||||
export default function stream(state = initialState, action) {
|
||||
@@ -74,6 +75,24 @@ export default function stream(state = initialState, action) {
|
||||
sortOrder: action.sortOrder ? action.sortOrder : state.sortOrder,
|
||||
sortBy: action.sortBy ? action.sortBy : state.sortBy,
|
||||
};
|
||||
case actions.ADD_COMMENT_BOX_TAG:
|
||||
return {
|
||||
...state,
|
||||
commentBoxTags: [...state.commentBoxTags, action.tag],
|
||||
};
|
||||
case actions.REMOVE_COMMENT_BOX_TAG:
|
||||
return {
|
||||
...state,
|
||||
commentBoxTags: [
|
||||
...state.commentBoxTags.slice(0, action.idx),
|
||||
...state.commentBoxTags.slice(action.idx + 1),
|
||||
],
|
||||
};
|
||||
case actions.CLEAR_COMMENT_BOX_TAGS:
|
||||
return {
|
||||
...state,
|
||||
commentBoxTags: [],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon } from '../coral-ui';
|
||||
import { Icon } from 'coral-ui';
|
||||
import styles from './Comment.css';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import CommentTimestamp from 'coral-framework/components/CommentTimestamp';
|
||||
+1
-1
@@ -8,7 +8,7 @@ import cn from 'classnames';
|
||||
import { link } from 'coral-framework/services/pym';
|
||||
import NotLoggedIn from '../components/NotLoggedIn';
|
||||
import { Spinner } from 'coral-ui';
|
||||
import CommentHistory from 'talk-plugin-history/CommentHistory';
|
||||
import CommentHistory from '../components/CommentHistory';
|
||||
|
||||
// TODO: Auth logic needs refactoring.
|
||||
import {
|
||||
@@ -0,0 +1,17 @@
|
||||
import React, { Component } from 'react';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import RestrictedMessageBox from 'coral-framework/components/RestrictedMessageBox';
|
||||
|
||||
class ChangeUsername extends Component {
|
||||
render() {
|
||||
return (
|
||||
<RestrictedMessageBox>
|
||||
<div className="talk-change-username">
|
||||
<span>{t('framework.changed_name.msg')}</span>
|
||||
</div>
|
||||
</RestrictedMessageBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangeUsername;
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import TagLabel from 'talk-plugin-tag-label/TagLabel';
|
||||
import TagLabel from './TagLabel';
|
||||
import CommentTimestamp from 'coral-framework/components/CommentTimestamp';
|
||||
import { ReplyBox, ReplyButton } from 'talk-plugin-replies';
|
||||
import { FlagComment } from 'talk-plugin-flags';
|
||||
import ReplyButton from './ReplyButton';
|
||||
import ReplyBox from './ReplyBox';
|
||||
import FlagComment from './FlagComment';
|
||||
import { can } from 'coral-framework/services/perms';
|
||||
import { TransitionGroup } from 'react-transition-group';
|
||||
import cn from 'classnames';
|
||||
@@ -79,6 +80,11 @@ const ActionButton = ({ children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
ActionButton.propTypes = {
|
||||
// id of currently opened ReplyBox. tracked in Stream.js
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
// Determine whether the comment with id is in the part of the comments tree.
|
||||
function containsCommentId(props, id) {
|
||||
if (props.comment.id === id) {
|
||||
@@ -154,6 +160,7 @@ export default class Comment extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
// id of currently opened ReplyBox. tracked in Stream.js
|
||||
children: PropTypes.node,
|
||||
activeReplyBox: PropTypes.string.isRequired,
|
||||
disableReply: PropTypes.bool,
|
||||
setActiveReplyBox: PropTypes.func.isRequired,
|
||||
@@ -172,6 +179,12 @@ export default class Comment extends React.Component {
|
||||
}),
|
||||
charCountEnable: PropTypes.bool.isRequired,
|
||||
maxCharCount: PropTypes.number,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
loadMore: PropTypes.func,
|
||||
postDontAgree: PropTypes.func,
|
||||
animateEnter: PropTypes.bool,
|
||||
commentClassNames: PropTypes.array,
|
||||
comment: PropTypes.shape({
|
||||
depth: PropTypes.number,
|
||||
action_summaries: PropTypes.array.isRequired,
|
||||
|
||||
+3
-2
@@ -4,8 +4,9 @@ import { Button } from 'coral-ui';
|
||||
import cn from 'classnames';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
|
||||
import { name } from './CommentBox';
|
||||
import styles from './styles.css';
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
import { name } from '../containers/CommentBox';
|
||||
import styles from './CommentForm.css';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { notifyForNewCommentStatus } from 'talk-plugin-commentbox/CommentBox';
|
||||
import { CommentForm } from 'talk-plugin-commentbox/CommentForm';
|
||||
|
||||
import { notifyForNewCommentStatus } from '../helpers';
|
||||
import { CommentForm } from './CommentForm';
|
||||
import styles from './Comment.css';
|
||||
import { CountdownSeconds } from './CountdownSeconds';
|
||||
import { getEditableUntilDate } from './util';
|
||||
@@ -43,6 +44,8 @@ export class EditableCommentContent extends React.Component {
|
||||
stopEditing: PropTypes.func,
|
||||
};
|
||||
|
||||
unmounted = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.editWindowExpiryTimeout = null;
|
||||
@@ -62,6 +65,7 @@ export class EditableCommentContent extends React.Component {
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.unmounted = true;
|
||||
if (this.editWindowExpiryTimeout) {
|
||||
this.editWindowExpiryTimeout = clearTimeout(this.editWindowExpiryTimeout);
|
||||
}
|
||||
@@ -86,7 +90,9 @@ export class EditableCommentContent extends React.Component {
|
||||
let response;
|
||||
try {
|
||||
response = await editComment({ body: this.state.body });
|
||||
this.setState({ loadingState: 'success' });
|
||||
if (!this.unmounted) {
|
||||
this.setState({ loadingState: 'success' });
|
||||
}
|
||||
const status = response.data.editComment.comment.status;
|
||||
notifyForNewCommentStatus(this.props.notify, status);
|
||||
if (typeof stopEditing === 'function') {
|
||||
|
||||
+3
-2
@@ -6,11 +6,12 @@ import { can } from 'coral-framework/services/perms';
|
||||
import { PopupMenu, Button } from 'coral-ui';
|
||||
import ClickOutside from 'coral-framework/components/ClickOutside';
|
||||
import cn from 'classnames';
|
||||
import styles from './styles.css';
|
||||
import * as REASONS from '../helpers/flagReasons';
|
||||
import styles from './FlagButton.css';
|
||||
import * as REASONS from 'coral-framework/graphql/flagReasons';
|
||||
|
||||
import { getErrorMessages, forEachError } from 'coral-framework/utils';
|
||||
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
const name = 'talk-plugin-flags';
|
||||
|
||||
export default class FlagButton extends Component {
|
||||
+1
-1
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import FlagButton from './FlagButton';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { username, comment } from '../helpers/flagReasons';
|
||||
import { username, comment } from 'coral-framework/graphql/flagReasons';
|
||||
|
||||
const FlagComment = props => (
|
||||
<FlagButton {...props} getPopupMenu={getPopupMenu} />
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Markdown from 'coral-framework/components/Markdown';
|
||||
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
const packagename = 'talk-plugin-infobox';
|
||||
|
||||
const InfoBox = ({ enable, content }) => (
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './styles.css';
|
||||
import styles from './ModerationLink.css';
|
||||
import cn from 'classnames';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import CommentBox from '../talk-plugin-commentbox/CommentBox';
|
||||
import CommentBox from '../containers/CommentBox';
|
||||
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
const name = 'talk-plugin-replies';
|
||||
|
||||
class ReplyBox extends Component {
|
||||
+1
@@ -6,6 +6,7 @@ import t from 'coral-framework/services/i18n';
|
||||
import cn from 'classnames';
|
||||
import styles from './ReplyButton.css';
|
||||
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
const name = 'talk-plugin-replies';
|
||||
|
||||
const ReplyButton = ({ onClick }) => {
|
||||
@@ -5,12 +5,12 @@ import Comment from '../containers/Comment';
|
||||
import BannedAccount from '../../../components/BannedAccount';
|
||||
import ChangeUsername from '../containers/ChangeUsername';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import InfoBox from 'talk-plugin-infobox/InfoBox';
|
||||
import InfoBox from './InfoBox';
|
||||
import { can } from 'coral-framework/services/perms';
|
||||
import { ModerationLink } from 'talk-plugin-moderation';
|
||||
import ModerationLink from './ModerationLink';
|
||||
import RestrictedMessageBox from 'coral-framework/components/RestrictedMessageBox';
|
||||
import t, { timeago } from 'coral-framework/services/i18n';
|
||||
import CommentBox from 'talk-plugin-commentbox/CommentBox';
|
||||
import CommentBox from '../containers/CommentBox';
|
||||
import QuestionBox from '../../../components/QuestionBox';
|
||||
import { isCommentActive } from 'coral-framework/utils';
|
||||
import { Tab, TabCount, TabPane } from 'coral-ui';
|
||||
@@ -25,6 +25,7 @@ import AllCommentsPane from './AllCommentsPane';
|
||||
import ExtendableTabPanel from '../../../containers/ExtendableTabPanel';
|
||||
|
||||
import styles from './Stream.css';
|
||||
import ChangedUsername from './ChangedUsername';
|
||||
|
||||
class Stream extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -237,6 +238,7 @@ class Stream extends React.Component {
|
||||
const banned = get(user, 'status.banned.status');
|
||||
const suspensionUntil = get(user, 'status.suspension.until');
|
||||
const rejectedUsername = get(user, 'status.username.status') === 'REJECTED';
|
||||
const changedUsername = get(user, 'status.username.status') === 'CHANGED';
|
||||
|
||||
const temporarilySuspended =
|
||||
user && suspensionUntil && new Date(suspensionUntil) > new Date();
|
||||
@@ -246,6 +248,7 @@ class Stream extends React.Component {
|
||||
((!banned &&
|
||||
!temporarilySuspended &&
|
||||
!rejectedUsername &&
|
||||
!changedUsername &&
|
||||
!highlightedComment) ||
|
||||
keepCommentBox);
|
||||
const slotProps = { data };
|
||||
@@ -285,6 +288,7 @@ class Stream extends React.Component {
|
||||
)}
|
||||
</RestrictedMessageBox>
|
||||
)}
|
||||
{changedUsername && <ChangedUsername />}
|
||||
{!banned && rejectedUsername && <ChangeUsername user={user} />}
|
||||
{banned && <BannedAccount />}
|
||||
{showCommentBox && (
|
||||
|
||||
+2
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
|
||||
const TagLabel = ({ children }) => (
|
||||
<div className="talk-plugin-tag-label">{children}</div>
|
||||
);
|
||||
+9
-27
@@ -6,32 +6,12 @@ import { can } from 'coral-framework/services/perms';
|
||||
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import { connect } from 'react-redux';
|
||||
import { CommentForm } from './CommentForm';
|
||||
import { CommentForm } from '../components/CommentForm';
|
||||
import { notifyForNewCommentStatus } from '../helpers';
|
||||
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
export const name = 'talk-plugin-commentbox';
|
||||
|
||||
const notifyReasons = ['LINKS', 'TRUST'];
|
||||
|
||||
function shouldNotify(actions = []) {
|
||||
return actions.some(
|
||||
({ __typename, reason }) =>
|
||||
__typename === 'FlagAction' && notifyReasons.includes(reason)
|
||||
);
|
||||
}
|
||||
|
||||
// Given a newly posted comment's status, show a notification to the user
|
||||
// if needed
|
||||
export const notifyForNewCommentStatus = (notify, comment, actions) => {
|
||||
if (comment.status === 'REJECTED') {
|
||||
notify('error', t('comment_box.comment_post_banned_word'));
|
||||
} else if (
|
||||
comment.status === 'PREMOD' ||
|
||||
(comment.status === 'SYSTEM_WITHHELD' && shouldNotify(actions))
|
||||
) {
|
||||
notify('success', t('comment_box.comment_post_notif_premod'));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Container for posting a new Comment
|
||||
*/
|
||||
@@ -70,7 +50,7 @@ class CommentBox extends React.Component {
|
||||
asset_id: assetId,
|
||||
parent_id: parentId,
|
||||
body: this.state.body,
|
||||
...this.props.commentBox,
|
||||
tags: this.props.tags,
|
||||
};
|
||||
|
||||
// Execute preSubmit Hooks
|
||||
@@ -86,7 +66,7 @@ class CommentBox extends React.Component {
|
||||
// Execute postSubmit Hooks
|
||||
this.state.hooks.postSubmit.forEach(hook => hook(data));
|
||||
|
||||
notifyForNewCommentStatus(notify, postedComment, actions);
|
||||
notifyForNewCommentStatus(notify, postedComment.status, actions);
|
||||
|
||||
if (commentPostedHandler) {
|
||||
commentPostedHandler();
|
||||
@@ -202,9 +182,11 @@ CommentBox.propTypes = {
|
||||
isReply: PropTypes.bool.isRequired,
|
||||
canPost: PropTypes.bool,
|
||||
notify: PropTypes.func.isRequired,
|
||||
commentBox: PropTypes.object,
|
||||
tags: PropTypes.array,
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ commentBox }) => ({ commentBox });
|
||||
const mapStateToProps = state => ({
|
||||
tags: state.stream.commentBoxTags,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(CommentBox);
|
||||
@@ -0,0 +1,23 @@
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const notifyReasons = ['LINKS', 'TRUST'];
|
||||
|
||||
function shouldNotify(actions = []) {
|
||||
return actions.some(
|
||||
({ __typename, reason }) =>
|
||||
__typename === 'FlagAction' && notifyReasons.includes(reason)
|
||||
);
|
||||
}
|
||||
|
||||
// Given a newly posted or edited comment's status, show a notification to the user
|
||||
// if needed
|
||||
export const notifyForNewCommentStatus = (notify, status, actions) => {
|
||||
if (status === 'REJECTED') {
|
||||
notify('error', t('comment_box.comment_post_banned_word'));
|
||||
} else if (
|
||||
status === 'PREMOD' ||
|
||||
(status === 'SYSTEM_WITHHELD' && shouldNotify(actions))
|
||||
) {
|
||||
notify('success', t('comment_box.comment_post_notif_premod'));
|
||||
}
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
export const addTag = tag => ({
|
||||
type: 'ADD_TAG',
|
||||
tag,
|
||||
});
|
||||
|
||||
export const removeTag = idx => ({
|
||||
type: 'REMOVE_TAG',
|
||||
idx,
|
||||
});
|
||||
|
||||
export const clearTags = () => ({
|
||||
type: 'CLEAR_TAGS',
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
export const ADD_TAG = 'ADD_TAG';
|
||||
export const REMOVE_TAG = 'REMOVE_TAG';
|
||||
export const CLEAR_TAGS = 'CLEAR_TAGS';
|
||||
@@ -1 +0,0 @@
|
||||
export { default as reducer } from './reducer';
|
||||
@@ -1,27 +0,0 @@
|
||||
import { ADD_TAG, REMOVE_TAG, CLEAR_TAGS } from './constants';
|
||||
|
||||
const initialState = {
|
||||
tags: [],
|
||||
};
|
||||
|
||||
export default function commentBox(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ADD_TAG:
|
||||
return {
|
||||
...state,
|
||||
tags: [...state.tags, action.tag],
|
||||
};
|
||||
case REMOVE_TAG:
|
||||
return {
|
||||
...state,
|
||||
tags: [
|
||||
...state.tags.slice(0, action.idx),
|
||||
...state.tags.slice(action.idx + 1),
|
||||
],
|
||||
};
|
||||
case CLEAR_TAGS:
|
||||
return initialState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default as FlagComment } from './components/FlagComment';
|
||||
@@ -1 +0,0 @@
|
||||
export { default as ModerationLink } from './ModerationLink';
|
||||
@@ -1,4 +0,0 @@
|
||||
import ReplyBox from './ReplyBox';
|
||||
import ReplyButton from './ReplyButton';
|
||||
|
||||
export { ReplyBox, ReplyButton };
|
||||
+3
-1
@@ -65,7 +65,7 @@ en:
|
||||
notify_approved: '{0} approved username {1}'
|
||||
notify_rejected: '{0} rejected username {1}'
|
||||
notify_flagged: '{0} reported username {1}'
|
||||
notify_changed: 'user {0} changed his username to {1}'
|
||||
notify_changed: 'user {0} changed their username to {1}'
|
||||
community:
|
||||
account_creation_date: "Account Creation Date"
|
||||
active: Active
|
||||
@@ -254,6 +254,8 @@ en:
|
||||
error: "Usernames can contain letters numbers and _ only"
|
||||
label: "New Username"
|
||||
msg: "Your account is currently suspended because your username has been deemed inappropriate. To restore your account please enter a new username. Please contact us if you have any questions."
|
||||
changed_name:
|
||||
msg: "Your username change is under review by our moderation team."
|
||||
my_comments: "My Comments"
|
||||
my_profile: "My profile"
|
||||
new_count: "View {0} more {1}"
|
||||
|
||||
@@ -15,7 +15,9 @@ module.exports = (user, perm) => {
|
||||
case types.EDIT_COMMENT:
|
||||
// Anyone can do these things if they aren't suspended, banned, or blocked
|
||||
// as they're editing their username.
|
||||
return !['UNSET', 'REJECTED'].includes(user.status.username.status);
|
||||
return !['UNSET', 'REJECTED', 'CHANGED'].includes(
|
||||
user.status.username.status
|
||||
);
|
||||
|
||||
case types.ADD_COMMENT_TAG:
|
||||
case types.REMOVE_COMMENT_TAG:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { addTag, removeTag } from 'talk-plugin-commentbox/actions';
|
||||
export {
|
||||
addCommentClassName,
|
||||
removeCommentClassName,
|
||||
addCommentBoxTag as addTag,
|
||||
removeCommentBoxTag as removeTag,
|
||||
} from 'coral-embed-stream/src/actions/stream';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const commentBoxTagsSelector = state => state.commentBox.tags;
|
||||
export const commentBoxTagsSelector = state => state.stream.commentBoxTags;
|
||||
export const commentClassNamesSelector = state =>
|
||||
state.stream.commentClassNames;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { ReplyButton } from 'talk-plugin-replies';
|
||||
import ReplyButton from 'coral-embed-stream/src/tabs/stream/components/ReplyButton';
|
||||
import styles from './FakeComment.css';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import { CommentTimestamp } from 'plugin-api/beta/client/components';
|
||||
|
||||
@@ -15,13 +15,13 @@ module.exports = {
|
||||
client.end();
|
||||
},
|
||||
|
||||
'admin logs in': client => {
|
||||
'Admin logs in': client => {
|
||||
const adminPage = client.page.admin();
|
||||
const { testData: { admin } } = client.globals;
|
||||
|
||||
adminPage.navigateAndLogin(admin);
|
||||
},
|
||||
'admin flags users username as offensive': client => {
|
||||
'Admin flags users username as offensive': client => {
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const comments = embedStream.navigate().ready();
|
||||
@@ -42,7 +42,7 @@ module.exports = {
|
||||
.waitForElementVisible('@popUpText')
|
||||
.click('@continueButton');
|
||||
},
|
||||
'admin goes to Reported Usernames': client => {
|
||||
'Admin goes to Reported Usernames': client => {
|
||||
const adminPage = client.page.admin();
|
||||
|
||||
const community = adminPage
|
||||
@@ -54,14 +54,14 @@ module.exports = {
|
||||
.waitForElementVisible('@flaggedAccountsContainer')
|
||||
.waitForElementVisible('@flaggedUser');
|
||||
},
|
||||
'admin rejects the user flag': client => {
|
||||
'Admin rejects the user flag': client => {
|
||||
const community = client.page.admin().section.community;
|
||||
|
||||
community
|
||||
.waitForElementVisible('@flaggedUserRejectButton')
|
||||
.click('@flaggedUserRejectButton');
|
||||
},
|
||||
'admin suspends the user': client => {
|
||||
'Admin suspends the user': client => {
|
||||
const community = client.page.admin().section.community;
|
||||
const usernameDialog = client.page.admin().section.usernameDialog;
|
||||
|
||||
@@ -76,7 +76,7 @@ module.exports = {
|
||||
|
||||
community.waitForElementNotPresent('@flaggedUser');
|
||||
},
|
||||
'admin logs out': client => {
|
||||
'Admin logs out': client => {
|
||||
client.page.admin().logout();
|
||||
},
|
||||
'user logs in': client => {
|
||||
@@ -114,6 +114,48 @@ module.exports = {
|
||||
.click('@changeUsernameSubmitButton')
|
||||
.waitForElementNotPresent('@changeUsernameInput');
|
||||
},
|
||||
'user should not be able to comment still': client => {
|
||||
const embedStream = client.page.embedStream();
|
||||
const comments = embedStream.section.comments;
|
||||
|
||||
comments
|
||||
.waitForElementNotPresent('@commentBoxTextarea')
|
||||
.waitForElementNotPresent('@commentBoxPostButton');
|
||||
},
|
||||
'user logs out': client => {
|
||||
const embedStream = client.page.embedStream();
|
||||
const comments = embedStream.section.comments;
|
||||
|
||||
comments.logout();
|
||||
},
|
||||
'Admin accepts the user flag': client => {
|
||||
const adminPage = client.page.admin();
|
||||
const { testData: { admin } } = client.globals;
|
||||
|
||||
adminPage.navigateAndLogin(admin);
|
||||
|
||||
const community = adminPage
|
||||
.navigate()
|
||||
.ready()
|
||||
.goToCommunity();
|
||||
|
||||
community
|
||||
.waitForElementVisible('@flaggedAccountsContainer')
|
||||
.waitForElementVisible('@flaggedUser')
|
||||
.waitForElementVisible('@flaggedUserApproveButton')
|
||||
.click('@flaggedUserApproveButton');
|
||||
|
||||
client.page.admin().logout();
|
||||
},
|
||||
'user logs in to check comment': client => {
|
||||
const { testData: { user } } = client.globals;
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
embedStream
|
||||
.navigate()
|
||||
.ready()
|
||||
.openLoginPopup(popup => popup.login(user));
|
||||
},
|
||||
'user should be able to comment': client => {
|
||||
const embedStream = client.page.embedStream();
|
||||
const comments = embedStream.section.comments;
|
||||
|
||||
@@ -270,6 +270,49 @@ describe('graph.mutations.createComment', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('user with different username statuses', () => {
|
||||
beforeEach(() => AssetModel.create({ id: '123' }));
|
||||
|
||||
[
|
||||
{ status: 'UNSET', error: true },
|
||||
{ status: 'SET', error: false },
|
||||
{ status: 'APPROVED', error: false },
|
||||
{ status: 'REJECTED', error: true },
|
||||
{ status: 'CHANGED', error: true },
|
||||
].forEach(({ status, error }) => {
|
||||
describe(`user.status.username.status=${status}`, () => {
|
||||
it(`${error ? 'can not' : 'can'} create a comment`, async () => {
|
||||
const context = new Context({
|
||||
user: new UserModel({ status: { username: { status } } }),
|
||||
});
|
||||
|
||||
const { data, errors } = await graphql(schema, query, {}, context);
|
||||
|
||||
if (errors) {
|
||||
console.error(errors);
|
||||
}
|
||||
expect(errors).to.be.undefined;
|
||||
|
||||
if (error) {
|
||||
expect(data.createComment).to.have.property('errors').not.null;
|
||||
expect(data.createComment).to.have.property('comment').null;
|
||||
expect(data.createComment.errors).to.have.length(1);
|
||||
expect(data.createComment.errors[0]).to.have.property(
|
||||
'translation_key',
|
||||
'NOT_AUTHORIZED'
|
||||
);
|
||||
} else {
|
||||
if (data.createComment.errors) {
|
||||
console.error(data.createComment.errors);
|
||||
}
|
||||
expect(data.createComment).to.have.property('errors').null;
|
||||
expect(data.createComment).to.have.property('comment').not.null;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('users with different roles', () => {
|
||||
beforeEach(() => AssetModel.create({ id: '123' }));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user