mirror of
https://github.com/wassname/talk.git
synced 2026-07-20 12:40:47 +08:00
Merge branch 'next' into user-status-refactor
This commit is contained in:
@@ -7,11 +7,11 @@ import {Icon} from 'coral-ui';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const ApproveButton = ({active, minimal, onClick}) => {
|
||||
const ApproveButton = ({active, minimal, onClick, className}) => {
|
||||
const text = active ? t('modqueue.approved') : t('modqueue.approve');
|
||||
return (
|
||||
<button
|
||||
className={cn(styles.root, {[styles.minimal]: minimal, [styles.active]: active})}
|
||||
className={cn(styles.root, {[styles.minimal]: minimal, [styles.active]: active}, className)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon name={'done'} className={styles.icon} />
|
||||
@@ -21,6 +21,7 @@ const ApproveButton = ({active, minimal, onClick}) => {
|
||||
};
|
||||
|
||||
ApproveButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
minimal: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
|
||||
@@ -15,7 +15,15 @@ function generateRegExp(phrases) {
|
||||
.join('[\\s"?!.]+')
|
||||
).join('|');
|
||||
|
||||
return new RegExp(`(^|[^\\w])(${inner})(?=[^\\w]|$)`, 'iu');
|
||||
const pattern = `(^|[^\\w])(${inner})(?=[^\\w]|$)`;
|
||||
try {
|
||||
return new RegExp(pattern, 'iu');
|
||||
}
|
||||
catch (_err) {
|
||||
|
||||
// IE does not support unicode support, so we'll create one without.
|
||||
return new RegExp(pattern, 'i');
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a regular expression detecting `suspectWords` and `bannedWords` phrases.
|
||||
|
||||
@@ -7,11 +7,11 @@ import {Icon} from 'coral-ui';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const RejectButton = ({active, minimal, onClick}) => {
|
||||
const RejectButton = ({active, minimal, onClick, className}) => {
|
||||
const text = active ? t('modqueue.rejected') : t('modqueue.reject');
|
||||
return (
|
||||
<button
|
||||
className={cn(styles.root, {[styles.minimal]: minimal, [styles.active]: active})}
|
||||
className={cn(styles.root, {[styles.minimal]: minimal, [styles.active]: active}, className)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon name={'close'} className={styles.icon} />
|
||||
@@ -21,6 +21,7 @@ const RejectButton = ({active, minimal, onClick}) => {
|
||||
};
|
||||
|
||||
RejectButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
minimal: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
|
||||
@@ -76,9 +76,9 @@ const CoralHeader = ({
|
||||
}
|
||||
<div className={styles.rightPanel}>
|
||||
<ul>
|
||||
<li className={styles.settings}>
|
||||
<li className={cn(styles.settings, 'talk-admin-header-settings')}>
|
||||
<div>
|
||||
<IconButton name="settings" id="menu-settings"/>
|
||||
<IconButton name="settings" id="menu-settings" className="talk-admin-header-settings-button"/>
|
||||
<Menu target="menu-settings" align="right">
|
||||
<MenuItem onClick={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
|
||||
<MenuItem>
|
||||
@@ -91,7 +91,7 @@ const CoralHeader = ({
|
||||
Report a bug or give feedback
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleLogout}>
|
||||
<MenuItem onClick={handleLogout} className="talk-admin-header-sign-out">
|
||||
{t('configure.sign_out')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import EmptyCard from 'coral-admin/src/components/EmptyCard';
|
||||
import LoadMore from '../../../components/LoadMore';
|
||||
@@ -23,7 +24,7 @@ class FlaggedAccounts extends React.Component {
|
||||
const hasResults = users.nodes && !!users.nodes.length;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={cn('talk-adnin-community-flagged-accounts', styles.container)}>
|
||||
<div className={styles.mainFlaggedContent}>
|
||||
{
|
||||
hasResults
|
||||
@@ -70,4 +71,15 @@ class FlaggedAccounts extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
FlaggedAccounts.propTypes = {
|
||||
users: PropTypes.object,
|
||||
loadMore: PropTypes.func,
|
||||
showBanUserDialog: PropTypes.func,
|
||||
showSuspendUserDialog: PropTypes.func,
|
||||
showRejectUsernameDialog: PropTypes.func,
|
||||
approveUser: PropTypes.func,
|
||||
me: PropTypes.object,
|
||||
viewUserDetail: PropTypes.func,
|
||||
};
|
||||
|
||||
export default FlaggedAccounts;
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import React from 'react';
|
||||
import styles from './FlaggedUser.css';
|
||||
|
||||
// TODO: Should not rely on plugin.
|
||||
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 ActionsMenu from 'coral-admin/src/components/ActionsMenu';
|
||||
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
|
||||
import ApproveButton from 'coral-admin/src/components/ApproveButton';
|
||||
import RejectButton from 'coral-admin/src/components/RejectButton';
|
||||
|
||||
import cn from 'classnames';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
// TODO: Should work with custom flags too.
|
||||
const shortReasons = {
|
||||
[username.other]: t('community.other'),
|
||||
[username.spam]: t('community.spam_ads'),
|
||||
@@ -21,7 +17,6 @@ const shortReasons = {
|
||||
[username.impersonating]: t('community.impersonating'),
|
||||
};
|
||||
|
||||
// Render a single user for the list
|
||||
class User extends React.Component {
|
||||
|
||||
showSuspenUserDialog = () => this.props.showSuspendUserDialog({
|
||||
@@ -50,12 +45,10 @@ class User extends React.Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<li
|
||||
tabIndex={0}
|
||||
className={cn(className, styles.root, {[styles.rootSelected]: selected})}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<li tabIndex={0}
|
||||
className={cn(className, styles.root, {[styles.rootSelected]: selected})} >
|
||||
<div className={cn('talk-admin-community-flagged-user', styles.container)}>
|
||||
<div className={cn('talk-admin-community-flagged-user-header', styles.header)}>
|
||||
<div className={styles.author}>
|
||||
<button
|
||||
onClick={this.viewAuthorDetail}
|
||||
@@ -78,8 +71,7 @@ class User extends React.Component {
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
<div className={cn('talk-admin-community-flagged-user-body', styles.body)}>
|
||||
<div className={styles.flagged}>
|
||||
<div className={styles.flaggedByCount}>
|
||||
<i className={cn('material-icons', styles.flagIcon)}>flag</i>
|
||||
@@ -127,9 +119,11 @@ class User extends React.Component {
|
||||
<div className={styles.sideActions}>
|
||||
<div className={styles.actions}>
|
||||
<ApproveButton
|
||||
className="talk-admin-flagged-user-approve-button"
|
||||
onClick={this.approveUser}
|
||||
/>
|
||||
<RejectButton
|
||||
className="talk-admin-flagged-user-reject-button"
|
||||
onClick={this.showRejectUsernameDialog}
|
||||
/>
|
||||
</div>
|
||||
@@ -141,4 +135,16 @@ class User extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
User.propTypes = {
|
||||
showSuspendUserDialog: PropTypes.func,
|
||||
showBanUserDialog: PropTypes.func,
|
||||
viewUserDetail: PropTypes.func,
|
||||
showRejectUsernameDialog: PropTypes.func,
|
||||
approveUser: PropTypes.func,
|
||||
user: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
selected: PropTypes.bool,
|
||||
me: PropTypes.object,
|
||||
};
|
||||
|
||||
export default User;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import cn from 'classnames';
|
||||
import {Dialog, Button} from 'coral-ui';
|
||||
import styles from './RejectUsernameDialog.css';
|
||||
|
||||
@@ -29,12 +29,6 @@ class RejectUsernameDialog extends Component {
|
||||
|
||||
state = {email: '', stage: 0}
|
||||
|
||||
static propTypes = {
|
||||
stage: PropTypes.number,
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
rejectUsername: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({email: t('reject_username.email_message_reject'), about: t('reject_username.username')});
|
||||
}
|
||||
@@ -76,7 +70,7 @@ class RejectUsernameDialog extends Component {
|
||||
const {stage} = this.state;
|
||||
|
||||
return <Dialog
|
||||
className={styles.suspendDialog}
|
||||
className={cn(styles.suspendDialog, 'talk-reject-username-dialog')}
|
||||
id="rejectUsernameDialog"
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
@@ -96,15 +90,18 @@ class RejectUsernameDialog extends Component {
|
||||
<div className={styles.emailContainer}>
|
||||
<textarea
|
||||
rows={5}
|
||||
className={styles.emailInput}
|
||||
className={cn(styles.emailInput, 'talk-reject-username-dialog-suspension-message')}
|
||||
value={this.state.email}
|
||||
onChange={this.onEmailChange}/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className={styles.modalButtons}>
|
||||
<div className={cn(styles.modalButtons, 'talk-reject-username-dialog-buttons')}>
|
||||
{Object.keys(stages[stage].options).map((key, i) => (
|
||||
<Button key={i} onClick={this.onActionClick(stage, i)}>
|
||||
<Button
|
||||
key={i}
|
||||
className={cn('talk-reject-username-dialog-button', `talk-reject-username-dialog-button-${key}`)}
|
||||
onClick={this.onActionClick(stage, i)} >
|
||||
{t(stages[stage].options[key], t('reject_username.username'))}
|
||||
</Button>
|
||||
))}
|
||||
@@ -114,4 +111,12 @@ class RejectUsernameDialog extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
RejectUsernameDialog.propTypes = {
|
||||
stage: PropTypes.number,
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
rejectUsername: PropTypes.func.isRequired,
|
||||
user: PropTypes.object,
|
||||
open: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default RejectUsernameDialog;
|
||||
|
||||
@@ -213,12 +213,14 @@ class Stream extends React.Component {
|
||||
const open = !asset.isClosed;
|
||||
|
||||
const banned = user && user.status === 'BANNED';
|
||||
const pending = user && user.status === 'PENDING';
|
||||
|
||||
const temporarilySuspended =
|
||||
user &&
|
||||
user.suspension.until &&
|
||||
new Date(user.suspension.until) > new Date();
|
||||
|
||||
const showCommentBox = loggedIn && ((!banned && !temporarilySuspended && !highlightedComment) || keepCommentBox);
|
||||
const showCommentBox = loggedIn && ((!banned && !pending & !temporarilySuspended && !highlightedComment) || keepCommentBox);
|
||||
const slotProps = {data};
|
||||
const slotQueryData = {root, asset};
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
.editNameInput {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px #d8d8d8;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.alert {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import styles from './SuspendAccount.css';
|
||||
import {Button} from 'coral-ui';
|
||||
@@ -66,17 +67,16 @@ class SuspendedAccount extends Component {
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
className={styles.editNameInput}
|
||||
className={cn(styles.editNameInput, 'talk-suspended-account-username-input')}
|
||||
value={username}
|
||||
placeholder={t('framework.edit_name.label')}
|
||||
id='username'
|
||||
onChange={(e) => this.setState({username: e.target.value})}
|
||||
rows={3}/><br/>
|
||||
<Button
|
||||
onClick={this.onSubmitClick}>
|
||||
{
|
||||
t('framework.edit_name.button')
|
||||
}
|
||||
className="talk-suspended-account-submit-button"
|
||||
onClick={this.onSubmitClick} >
|
||||
{t('framework.edit_name.button')}
|
||||
</Button>
|
||||
</div> : null
|
||||
}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import RestrictedMessageBox from './RestrictedMessageBox';
|
||||
|
||||
export default ({children, restricted, message = t('framework.content_not_available'), restrictedComp}) => {
|
||||
const RestrictedContent = ({children, restricted, message = t('framework.content_not_available'), restrictedComp}) => {
|
||||
if (restricted) {
|
||||
return restrictedComp ? restrictedComp : <RestrictedMessageBox message={message} />;
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<div className="talk-restricted-content">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
RestrictedContent.propTypes = {
|
||||
children: PropTypes.node,
|
||||
restricted: PropTypes.bool,
|
||||
message: PropTypes.string,
|
||||
restrictedComp: PropTypes.node,
|
||||
};
|
||||
|
||||
export default RestrictedContent;
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import styles from './RestrictedMessageBox.css';
|
||||
|
||||
export default ({children}) => <div className={styles.message}>{children}</div>;
|
||||
const RestrictedMessageBox = ({children}) =>
|
||||
<div className={cn(styles.message, 'talk-restricted-message-box')}>{children}</div>;
|
||||
|
||||
RestrictedMessageBox.propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default RestrictedMessageBox;
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 {getErrorMessages} from 'coral-framework/utils';
|
||||
|
||||
@@ -90,10 +91,9 @@ export default class FlagButton extends Component {
|
||||
let action = {
|
||||
item_id,
|
||||
item_type: itemType,
|
||||
reason: null,
|
||||
message
|
||||
};
|
||||
if (reason === 'COMMENT_NOAGREE') {
|
||||
if (reason === REASONS.comment.noagree) {
|
||||
postDontAgree(action)
|
||||
.then(({data}) => {
|
||||
if (itemType === 'COMMENTS') {
|
||||
@@ -122,7 +122,7 @@ export default class FlagButton extends Component {
|
||||
onPopupOptionClick = (sets) => (e) => {
|
||||
|
||||
// If flagging a user, indicate that this is referencing the username rather than the bio
|
||||
if(sets === 'itemType' && e.target.value === 'users') {
|
||||
if (sets === 'itemType' && e.target.value === 'users') {
|
||||
this.setState({field: 'username'});
|
||||
}
|
||||
|
||||
|
||||
+16
-2
@@ -1,12 +1,26 @@
|
||||
const DataLoader = require('dataloader');
|
||||
const TagsService = require('../../services/tags');
|
||||
const plugins = require('../../services/plugins');
|
||||
const debug = require('debug')('talk:graph:loaders:tags');
|
||||
const PLUGIN_TAGS = plugins.get('server', 'tags').reduce((acc, {plugin, tags}) => {
|
||||
debug(`added plugin '${plugin.name}'`);
|
||||
|
||||
acc = acc.concat(tags);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Get all the tags for the context for the dataloader.
|
||||
*/
|
||||
const genAll = (context, queries) => {
|
||||
return Promise.all(queries.map(({id, item_type, asset_id}) => {
|
||||
return TagsService.getAll({id, item_type, asset_id});
|
||||
return Promise.all(queries.map(async ({id, item_type, asset_id}) => {
|
||||
let tags = await TagsService.getAll({id, item_type, asset_id});
|
||||
|
||||
// Merge in the global plugin tags as well.
|
||||
tags = tags.concat(PLUGIN_TAGS);
|
||||
|
||||
return tags;
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
+56
-29
@@ -2,25 +2,47 @@ const ActionsService = require('../../services/actions');
|
||||
const errors = require('../../errors');
|
||||
const {CREATE_ACTION, DELETE_ACTION} = require('../../perms/constants');
|
||||
|
||||
/**
|
||||
* getActionItem will return the item that is associated with the given action.
|
||||
* If it does not exist, it will throw an error.
|
||||
*
|
||||
* @param {Object} ctx the graphql context for the request
|
||||
* @param {Object} action the action being performed
|
||||
* @return {Promise} resolves to the referenced item
|
||||
*/
|
||||
const getActionItem = async ({loaders: {Comments, Users}}, {item_id, item_type}) => {
|
||||
if (item_type === 'COMMENTS') {
|
||||
const comment = await Comments.get.load(item_id);
|
||||
if (!comment) {
|
||||
throw errors.ErrNotFound;
|
||||
}
|
||||
|
||||
return comment;
|
||||
} else if (item_type === 'USERS') {
|
||||
const user = await Users.getByID.load(item_id);
|
||||
if (!user) {
|
||||
throw errors.ErrNotFound;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an action on a item. If the item is a user flag, sets the user's status to
|
||||
* pending.
|
||||
* @param {Object} user the user performing the request
|
||||
* @param {String} item_id id of the item to add the action to
|
||||
* @param {String} item_type type of the item
|
||||
* @param {String} action_type type of the action
|
||||
* @return {Promise} resolves to the action created
|
||||
*
|
||||
* @param {Object} ctx the graphql context for the request
|
||||
* @param {Object} action the action being created
|
||||
* @return {Promise} resolves to the action created
|
||||
*/
|
||||
const createAction = async ({user = {}, pubsub, loaders: {Comments}}, {item_id, item_type, action_type, group_id, metadata = {}}) => {
|
||||
const createAction = async (ctx, {item_id, item_type, action_type, group_id, metadata = {}}) => {
|
||||
const {user = {}, pubsub} = ctx;
|
||||
|
||||
let comment;
|
||||
if (item_type === 'COMMENTS') {
|
||||
comment = await Comments.get.load(item_id);
|
||||
if (!comment) {
|
||||
throw new Error('Comment not found');
|
||||
}
|
||||
}
|
||||
// Gets the item referenced by the action.
|
||||
const item = await getActionItem(ctx, {item_id, item_type});
|
||||
|
||||
// Create the action itself.
|
||||
let action = await ActionsService.create({
|
||||
item_id,
|
||||
item_type,
|
||||
@@ -30,8 +52,13 @@ const createAction = async ({user = {}, pubsub, loaders: {Comments}}, {item_id,
|
||||
metadata
|
||||
});
|
||||
|
||||
if (comment) {
|
||||
pubsub.publish('commentFlagged', comment);
|
||||
// If the action is a flag.
|
||||
if (action_type === 'FLAG') {
|
||||
if (item_type === 'COMMENTS') {
|
||||
|
||||
// Push that the comment was flagged, don't wait for it to finish.
|
||||
pubsub.publish('commentFlagged', item);
|
||||
}
|
||||
}
|
||||
|
||||
return action;
|
||||
@@ -39,28 +66,28 @@ const createAction = async ({user = {}, pubsub, loaders: {Comments}}, {item_id,
|
||||
|
||||
/**
|
||||
* Deletes an action based on the user id if the user owns that action.
|
||||
*
|
||||
* @param {Object} user the user performing the request
|
||||
* @param {String} id the id of the action to delete
|
||||
* @return {Promise} resolves to the deleted action, or null if not found.
|
||||
*/
|
||||
const deleteAction = ({user}, {id}) => {
|
||||
return ActionsService.delete({id, user_id: user.id});
|
||||
};
|
||||
const deleteAction = ({user}, {id}) => ActionsService.delete({id, user_id: user.id});
|
||||
|
||||
module.exports = (context) => {
|
||||
if (context.user && context.user.can(CREATE_ACTION, DELETE_ACTION)) {
|
||||
return {
|
||||
Action: {
|
||||
create: (action) => createAction(context, action),
|
||||
delete: (action) => deleteAction(context, action)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
module.exports = (ctx) => {
|
||||
const mutators = {
|
||||
Action: {
|
||||
create: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
delete: () => Promise.reject(errors.ErrNotAuthorized)
|
||||
}
|
||||
};
|
||||
|
||||
if (ctx.user && ctx.user.can(CREATE_ACTION)) {
|
||||
mutators.Action.create = (action) => createAction(ctx, action);
|
||||
}
|
||||
|
||||
if (ctx.user && ctx.user.can(DELETE_ACTION)) {
|
||||
mutators.Action.delete = (action) => deleteAction(ctx, action);
|
||||
}
|
||||
|
||||
return mutators;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
const errors = require('../../errors');
|
||||
|
||||
const ActionModel = require('../../models/action');
|
||||
const AssetsService = require('../../services/assets');
|
||||
const ActionsService = require('../../services/actions');
|
||||
const TagsService = require('../../services/tags');
|
||||
const CommentsService = require('../../services/comments');
|
||||
const KarmaService = require('../../services/karma');
|
||||
const tlds = require('tlds');
|
||||
const merge = require('lodash/merge');
|
||||
const linkify = require('linkify-it')()
|
||||
.tlds(tlds);
|
||||
const linkify = require('linkify-it')().tlds(require('tlds'));
|
||||
const Wordlist = require('../../services/wordlist');
|
||||
const {
|
||||
CREATE_COMMENT,
|
||||
@@ -17,21 +14,8 @@ const {
|
||||
ADD_COMMENT_TAG,
|
||||
EDIT_COMMENT
|
||||
} = require('../../perms/constants');
|
||||
|
||||
const {
|
||||
DISABLE_AUTOFLAG_SUSPECT_WORDS
|
||||
} = require('../../config');
|
||||
|
||||
const debug = require('debug')('talk:graph:mutators:tags');
|
||||
const plugins = require('../../services/plugins');
|
||||
|
||||
const pluginTags = plugins.get('server', 'tags').reduce((acc, {plugin, tags}) => {
|
||||
debug(`added plugin '${plugin.name}'`);
|
||||
|
||||
acc = acc.concat(tags);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
const debug = require('debug')('talk:graph:mutators:comment');
|
||||
const {DISABLE_AUTOFLAG_SUSPECT_WORDS} = require('../../config');
|
||||
|
||||
const resolveTagsForComment = async ({user, loaders: {Tags}}, {asset_id, tags = []}) => {
|
||||
const item_type = 'COMMENTS';
|
||||
@@ -48,8 +32,6 @@ const resolveTagsForComment = async ({user, loaders: {Tags}}, {asset_id, tags =
|
||||
globalTags = [];
|
||||
}
|
||||
|
||||
globalTags = globalTags.concat(pluginTags);
|
||||
|
||||
// Merge in the tags for the given comment.
|
||||
tags = tags.map((name) => {
|
||||
|
||||
@@ -285,14 +267,14 @@ const moderationPhases = [
|
||||
actions: [{
|
||||
action_type: 'FLAG',
|
||||
user_id: null,
|
||||
group_id: 'Matched suspect word filter',
|
||||
group_id: 'SUSPECT_WORD',
|
||||
metadata: {}
|
||||
}],
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// This phase checks to see if the comment's length exeeds maximum.
|
||||
// This phase checks to see if the comment's length exceeds maximum.
|
||||
(context, comment, {assetSettings: {charCountEnable, charCount}}) => {
|
||||
|
||||
// Reject if the comment is too long
|
||||
@@ -320,7 +302,7 @@ const moderationPhases = [
|
||||
|
||||
// Add the flag related to Trust to the comment.
|
||||
return {
|
||||
status:'SYSTEM_WITHHELD',
|
||||
status: 'SYSTEM_WITHHELD',
|
||||
actions: [{
|
||||
action_type: 'FLAG',
|
||||
user_id: null,
|
||||
@@ -362,7 +344,7 @@ const moderationPhases = [
|
||||
}
|
||||
},
|
||||
|
||||
// This phase checks to see if the comment was already perscribed a status.
|
||||
// This phase checks to see if the comment was already prescribed a status.
|
||||
(context, comment) => {
|
||||
|
||||
// If the status was already defined, don't redefine it. It's only defined
|
||||
|
||||
@@ -11,7 +11,7 @@ const modify = async ({user, loaders: {Tags}}, operation, {name, id, item_type,
|
||||
const tags = await Tags.getAll.load({id, item_type, asset_id});
|
||||
|
||||
// Resolve the TagLink that should be used to insert to the user. This will
|
||||
// addtionally return with an ownership property that can be used to determine
|
||||
// additionally return with an ownership property that can be used to determine
|
||||
// that the user who adds this tag must also be the owner of the resource.
|
||||
let {tagLink, ownership} = TagsService.resolveLink(user, tags, {name, item_type});
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
const DontAgreeAction = {
|
||||
|
||||
// Stored in the metadata, extract and return.
|
||||
reason({metadata: {reason}}) {
|
||||
return reason;
|
||||
}
|
||||
};
|
||||
const DontAgreeAction = {};
|
||||
|
||||
module.exports = DontAgreeAction;
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
const DontAgreeActionSummary = {
|
||||
reason({group_id}) {
|
||||
return group_id;
|
||||
}
|
||||
};
|
||||
const DontAgreeActionSummary = {};
|
||||
|
||||
module.exports = DontAgreeActionSummary;
|
||||
|
||||
@@ -13,8 +13,8 @@ const RootMutation = {
|
||||
createFlag: async (_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) => ({
|
||||
flag: Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}}),
|
||||
}),
|
||||
createDontAgree: async (_, {dontagree: {item_id, item_type, reason, message}}, {mutators: {Action}}) => ({
|
||||
dontagree: await Action.create({item_id, item_type, action_type: 'DONTAGREE', group_id: reason, metadata: {message}}),
|
||||
createDontAgree: async (_, {dontagree: {item_id, item_type, message}}, {mutators: {Action}}) => ({
|
||||
dontagree: await Action.create({item_id, item_type, action_type: 'DONTAGREE', metadata: {message}}),
|
||||
}),
|
||||
deleteAction: async (_, {id}, {mutators: {Action}}) => {
|
||||
await Action.delete({id});
|
||||
|
||||
+31
-10
@@ -601,6 +601,36 @@ type FlagAssetActionSummary implements AssetActionSummary {
|
||||
actionableItemCount: Int
|
||||
}
|
||||
|
||||
enum FLAG_REASON {
|
||||
|
||||
# The current user thinks that the flagged username is offensive.
|
||||
USERNAME_OFFENSIVE
|
||||
|
||||
# The current user does not like the flagged username.
|
||||
USERNAME_NOLIKE
|
||||
|
||||
# The current user thinks that the flagged username is being used to
|
||||
# impersonate another user.
|
||||
USERNAME_IMPERSONATING
|
||||
|
||||
# The current user thinks that the flagged username is spam.
|
||||
USERNAME_SPAM
|
||||
|
||||
# The current user thinks that the flagged username is wrong for another
|
||||
# reason.
|
||||
USERNAME_OTHER
|
||||
|
||||
# The current user thinks that the flagged comment is offensive.
|
||||
COMMENT_OFFENSIVE
|
||||
|
||||
# The current user thinks that the flagged comment is spam.
|
||||
COMMENT_SPAM
|
||||
|
||||
# The current user thinks that the flagged comment is wrong for another
|
||||
# reason.
|
||||
COMMENT_OTHER
|
||||
}
|
||||
|
||||
# A FLAG action that contains flag metadata.
|
||||
type FlagAction implements Action {
|
||||
|
||||
@@ -629,9 +659,6 @@ type DontAgreeAction implements Action {
|
||||
# The ID of the DontAgree Action.
|
||||
id: ID!
|
||||
|
||||
# The reason for which the DontAgree Action was created.
|
||||
reason: String
|
||||
|
||||
# An optional message sent with the flagging action by the user.
|
||||
message: String
|
||||
|
||||
@@ -664,9 +691,6 @@ type DontAgreeActionSummary implements ActionSummary {
|
||||
# The total count of flags with this reason.
|
||||
count: Int!
|
||||
|
||||
# The reason for which the Flag Action was created.
|
||||
reason: String
|
||||
|
||||
# The don't agree action by the current user against the parent entity with this reason.
|
||||
current_user: DontAgreeAction
|
||||
}
|
||||
@@ -1023,7 +1047,7 @@ input CreateFlagInput {
|
||||
item_type: ACTION_ITEM_TYPE!
|
||||
|
||||
# The reason for flagging the item.
|
||||
reason: String!
|
||||
reason: FLAG_REASON
|
||||
|
||||
# An optional message sent with the flagging action by the user.
|
||||
message: String
|
||||
@@ -1062,9 +1086,6 @@ input CreateDontAgreeInput {
|
||||
# The type of the item for which we are to create the don't agree.
|
||||
item_type: ACTION_ITEM_TYPE!
|
||||
|
||||
# The reason for not agreeing with the item.
|
||||
reason: String
|
||||
|
||||
# An optional message sent with the don't agree action by the user.
|
||||
message: String
|
||||
}
|
||||
|
||||
@@ -254,6 +254,24 @@ en:
|
||||
loading_results: "Loading Results"
|
||||
marketing: "This looks like an ad/marketing"
|
||||
moderate_this_stream: "Moderate this stream"
|
||||
flags:
|
||||
reasons:
|
||||
user:
|
||||
username_offensive: "Offensive"
|
||||
username_nolike: "Dislike"
|
||||
username_impersonating: "Impersonation"
|
||||
username_spam: "Spam"
|
||||
username_other: "Other"
|
||||
comment:
|
||||
comment_offensive: "Offensive"
|
||||
comment_spam: "Spam"
|
||||
comment_noagree: "Disagree"
|
||||
comment_other: "Other"
|
||||
suspect_word: "Suspect Word"
|
||||
banned_word: "Banned Word"
|
||||
body_count: "Body exceeds max length"
|
||||
trust: "Trust"
|
||||
links: "Link"
|
||||
modqueue:
|
||||
account: "account flags"
|
||||
actions: Actions
|
||||
|
||||
@@ -249,6 +249,19 @@ es:
|
||||
loading_results: "Cargando Resultados"
|
||||
marketing: "Esto parece una propaganda"
|
||||
moderate_this_stream: "Moderar este hilo de comentarios"
|
||||
flags:
|
||||
reasons:
|
||||
user:
|
||||
username_offensive: "Es ofensivo"
|
||||
username_nolike: "No le gusta"
|
||||
username_impersonating: "Está suplantando identidad"
|
||||
username_spam: "Contiene spam"
|
||||
username_other: "Otra razón"
|
||||
comment:
|
||||
comment_offensive: "Es ofensivo"
|
||||
comment_spam: "Contiene spam"
|
||||
comment_noagree: "No está de acuerdo"
|
||||
comment_other: "Otra razón"
|
||||
modqueue:
|
||||
account: "reportes de cuentas"
|
||||
actions: Acciones
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
const ActionModel = require('../models/action');
|
||||
|
||||
const mapping = {
|
||||
COMMENTS: {
|
||||
'Comment contains toxic language': 'TOXIC_COMMENT',
|
||||
'Matched suspect word filter': 'SUSPECT_WORD',
|
||||
'other': 'COMMENT_OTHER',
|
||||
'Other': 'COMMENT_OTHER',
|
||||
'This looks like an ad/marketing': 'COMMENT_SPAM',
|
||||
'This comment is offensive': 'COMMENT_OFFENSIVE',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
async up() {
|
||||
|
||||
// Setup the batch operation.
|
||||
const batch = ActionModel.collection.initializeUnorderedBulkOp();
|
||||
|
||||
for (const item_type in mapping) {
|
||||
const mappings = mapping[item_type];
|
||||
|
||||
for (const OLD_GROUP_ID in mappings) {
|
||||
const NEW_GROUP_ID = mappings[OLD_GROUP_ID];
|
||||
|
||||
// OLD
|
||||
// {
|
||||
// group_id: <OLD_GROUP_ID>
|
||||
// }
|
||||
// NEW
|
||||
// {
|
||||
// group_id: <NEW_GROUP_ID>
|
||||
// }
|
||||
|
||||
batch.find({
|
||||
group_id: OLD_GROUP_ID,
|
||||
item_type,
|
||||
}).update({
|
||||
$set: {
|
||||
group_id: NEW_GROUP_ID,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the batch update operation.
|
||||
await batch.execute();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
const ActionModel = require('../models/action');
|
||||
|
||||
module.exports = {
|
||||
async up() {
|
||||
|
||||
// This will update all the old flags that are 'COMMENT_NOAGREE' to change
|
||||
// them to DONTAGREE actions instead.
|
||||
return ActionModel.update({
|
||||
action_type: 'FLAG',
|
||||
group_id: 'COMMENT_NOAGREE',
|
||||
}, {
|
||||
$set: {
|
||||
action_type: 'DONTAGREE',
|
||||
group_id: null,
|
||||
},
|
||||
}, {
|
||||
multi: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"talk": {
|
||||
"migration": {
|
||||
"minVersion": 1496771633
|
||||
"minVersion": 1507322128
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -57,27 +57,23 @@ module.exports = {
|
||||
hooks: {
|
||||
RootMutation: {
|
||||
addTag: {
|
||||
async post(obj, {tag: {name, id, item_type}}, {user, mutators: {Comment}, pubsub}, info, result) {
|
||||
async post(obj, {tag: {name, id, item_type}}, {user, mutators: {Comment}, pubsub}, _info) {
|
||||
if (name === 'FEATURED' && item_type === 'COMMENTS') {
|
||||
const comment = await Comment.setStatus({id: id, status: 'ACCEPTED'});
|
||||
if (comment) {
|
||||
pubsub.publish('commentFeatured', {comment, user});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
removeTag: {
|
||||
async post(obj, {tag: {name, id, item_type}}, {user, loaders: {Comments}, pubsub}, info, result) {
|
||||
async post(obj, {tag: {name, id, item_type}}, {user, loaders: {Comments}, pubsub}, _info) {
|
||||
if (name === 'FEATURED' && item_type === 'COMMENTS') {
|
||||
const comment = await Comments.get.load(id);
|
||||
if (comment) {
|
||||
pubsub.publish('commentUnfeatured', {comment, user});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ class FlagDetails extends Component {
|
||||
<ul className={styles.info}>
|
||||
{reasons.map((reason) =>
|
||||
<li key={reason} className={styles.lessDetail}>
|
||||
{reason} {summaries[reason].userFlagged && `(${summaries[reason].count})`}
|
||||
{t(`flags.reasons.comment.${reason.toLowerCase()}`)} {summaries[reason].userFlagged && `(${summaries[reason].count})`}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import styles from './UserFlagDetails.css';
|
||||
|
||||
class UserFlagDetails extends Component {
|
||||
@@ -25,7 +26,7 @@ class UserFlagDetails extends Component {
|
||||
{Object.keys(summaries)
|
||||
.map((reason) => (
|
||||
<li key={reason}>
|
||||
{reason} ({summaries[reason].count})
|
||||
{t(`flags.reasons.comment.${reason.toLowerCase()}`)} ({summaries[reason].count})
|
||||
<ul className={styles.subDetail}>
|
||||
{summaries[reason].actions.map((action) =>
|
||||
<li key={action.user.id}>
|
||||
|
||||
@@ -4,12 +4,20 @@ en:
|
||||
Are you sure? The language in this comment might violate our community guidelines.
|
||||
You can edit the comment or submit it for moderator review.
|
||||
talk-plugin-toxic-comments:
|
||||
unlikely: Unlikely
|
||||
highly_likely: Highly Likely
|
||||
possibly: Possibly
|
||||
likely: Likely
|
||||
toxic_comment: Toxic Comment
|
||||
unlikely: "Unlikely"
|
||||
highly_likely: "Highly Likely"
|
||||
possibly: "Possibly"
|
||||
likely: "Likely"
|
||||
toxic_comment: "Toxic Comment"
|
||||
still_toxic: |
|
||||
This edited comment might still violate our community guidelines.
|
||||
Our moderation team will review your comment shortly.
|
||||
flags:
|
||||
reasons:
|
||||
comment:
|
||||
toxic_comment: "Highly likely to be Toxic"
|
||||
es:
|
||||
flags:
|
||||
reasons:
|
||||
comment:
|
||||
toxic_comment: "Muy probable que sea tóxico"
|
||||
@@ -23,5 +23,7 @@ module.exports = {
|
||||
'moderationContainer': '.talk-admin-moderation-container',
|
||||
'drawerButton': '.mdl-layout__drawer-button',
|
||||
'drawerOverlay': 'div.mdl-layout__obfuscator.is-visible',
|
||||
'settingsButton': '.talk-admin-header-settings-button',
|
||||
'signOutButton': '.talk-admin-header-sign-out',
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
commands: [{
|
||||
url: function() {
|
||||
return `${this.api.launchUrl}/admin/community`;
|
||||
},
|
||||
ready() {
|
||||
return this
|
||||
.waitForElementVisible('body');
|
||||
},
|
||||
}],
|
||||
elements: {
|
||||
container: '.talk-admin-community',
|
||||
flaggedAccountsContainer: '.talk-adnin-community-flagged-accounts',
|
||||
flaggedUser:'.talk-admin-community-flagged-user',
|
||||
flaggedUserApproveButton: '.talk-admin-flagged-user-approve-button',
|
||||
flaggedUserRejectButton: '.talk-admin-flagged-user-reject-button',
|
||||
usernameDialog: '.talk-reject-username-dialog',
|
||||
usernameDialogButtons: '.talk-reject-username-dialog-buttons',
|
||||
usernameDialogSuspend: '.talk-reject-username-dialog-button-k',
|
||||
usernameDialogSuspensionMessage: '.talk-reject-username-dialog-suspension-message'
|
||||
}
|
||||
};
|
||||
@@ -48,10 +48,24 @@ module.exports = {
|
||||
signInButton: '#coralSignInButton',
|
||||
commentBoxTextarea: '#commentText',
|
||||
commentBoxPostButton: '.talk-plugin-commentbox-button',
|
||||
firstComment: '.talk-stream-comment.talk-stream-comment-level-0',
|
||||
firstCommentContent: '.talk-stream-comment.talk-stream-comment-level-0 .talk-stream-comment-content',
|
||||
respectButton: '.talk-stream-comment.talk-stream-comment-level-0 .talk-stream-comment-footer .talk-plugin-respect-button'
|
||||
flagButton: '.talk-stream-comment.talk-stream-comment-level-0 .talk-plugin-flags-button',
|
||||
respectButton: '.talk-stream-comment.talk-stream-comment-level-0 .talk-stream-comment-footer .talk-plugin-respect-button',
|
||||
restrictedMessageBox: '.talk-restricted-message-box',
|
||||
suspendedAccountInput: '.talk-suspended-account-username-input',
|
||||
suspendedAccountSubmitButton: '.talk-suspended-account-submit-button',
|
||||
},
|
||||
sections: {
|
||||
flag: {
|
||||
selector: '.talk-plugin-flags-popup',
|
||||
elements: {
|
||||
offensiveUsernameRadio: '.talk-plugin-flags-popup-radio#USERNAME_OFFENSIVE',
|
||||
flagUsernameRadio: '.talk-plugin-flags-popup-radio#USERS',
|
||||
continueButton: '.talk-plugin-flags-popup-button',
|
||||
popUpText: '.talk-plugin-flags-popup-text',
|
||||
}
|
||||
},
|
||||
profile: {
|
||||
selector: '.talk-embed-stream-profile-tab-pane',
|
||||
elements: {
|
||||
@@ -59,7 +73,7 @@ module.exports = {
|
||||
myCommentHistory: '.talk-my-profile-comment-history',
|
||||
myCommentHistoryReactions: '.talk-my-profile-comment-history .comment-summary .comment-summary-reactions',
|
||||
myCommentHistoryReactionCount: '.talk-my-profile-comment-history .comment-summary .comment-summary-reactions .comment-summary-reaction-count',
|
||||
myCommentHistoryComment: '.talk-my-profile-comment-history .my-comment-body'
|
||||
myCommentHistoryComment: '.talk-my-profile-comment-history .my-comment-body',
|
||||
},
|
||||
},
|
||||
comments: {
|
||||
|
||||
@@ -49,6 +49,7 @@ module.exports = {
|
||||
.click('@drawerOverlay')
|
||||
.waitForElementVisible('@communitySection');
|
||||
},
|
||||
|
||||
after: (client) => {
|
||||
client.end();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
module.exports = {
|
||||
'admin logs in': (client) => {
|
||||
const adminPage = client.page.admin();
|
||||
const {testData: {admin}} = client.globals;
|
||||
|
||||
adminPage
|
||||
.navigate()
|
||||
.waitForElementVisible('@loginLayout')
|
||||
.waitForElementVisible('@signInForm')
|
||||
.setValue('@emailInput', admin.email)
|
||||
.setValue('@passwordInput', admin.password)
|
||||
.waitForElementVisible('@signInButton')
|
||||
.click('@signInButton');
|
||||
|
||||
client.pause(3000);
|
||||
|
||||
adminPage
|
||||
.waitForElementVisible('@moderationContainer');
|
||||
},
|
||||
'admin flags user\'s username as offensive': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
const flagSection = client.page.embedStream().section.embed.section.flag;
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementVisible('@firstComment')
|
||||
.waitForElementVisible('@flagButton')
|
||||
.click('@flagButton');
|
||||
|
||||
flagSection
|
||||
.waitForElementVisible('@flagUsernameRadio')
|
||||
.click('@flagUsernameRadio')
|
||||
.waitForElementVisible('@continueButton')
|
||||
.click('@continueButton')
|
||||
.waitForElementVisible('@offensiveUsernameRadio')
|
||||
.click('@offensiveUsernameRadio')
|
||||
.click('@continueButton')
|
||||
.waitForElementVisible('@popUpText')
|
||||
.click('@continueButton');
|
||||
},
|
||||
'admin goes to Reported Usernames': (client) => {
|
||||
const community = client.page.adminCommunity();
|
||||
|
||||
community
|
||||
.navigate();
|
||||
|
||||
community
|
||||
.waitForElementVisible('@container')
|
||||
.waitForElementVisible('@flaggedAccountsContainer')
|
||||
.waitForElementVisible('@flaggedUser');
|
||||
},
|
||||
'admin rejects the user flag': (client) => {
|
||||
const community = client.page.adminCommunity();
|
||||
|
||||
community
|
||||
.waitForElementVisible('@flaggedUserRejectButton')
|
||||
.click('@flaggedUserRejectButton');
|
||||
},
|
||||
'admin suspends the user': (client) => {
|
||||
const community = client.page.adminCommunity();
|
||||
|
||||
community
|
||||
.waitForElementVisible('@usernameDialog')
|
||||
.waitForElementVisible('@usernameDialogButtons')
|
||||
.waitForElementVisible('@usernameDialogSuspend')
|
||||
.click('@usernameDialogSuspend')
|
||||
.waitForElementVisible('@usernameDialogSuspensionMessage')
|
||||
.click('@usernameDialogSuspend')
|
||||
.waitForElementNotPresent('@flaggedUser');
|
||||
},
|
||||
'admin logs out': (client) => {
|
||||
const admin = client.page.admin();
|
||||
|
||||
admin
|
||||
.waitForElementVisible('@settingsButton')
|
||||
.click('@settingsButton')
|
||||
.waitForElementVisible('@signOutButton')
|
||||
.click('@signOutButton');
|
||||
},
|
||||
'user logs in': (client) => {
|
||||
const {testData: {user}} = client.globals;
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementVisible('@signInButton')
|
||||
.click('@signInButton');
|
||||
|
||||
client.pause(3000);
|
||||
|
||||
// Focusing on the Login PopUp
|
||||
client.windowHandles((result) => {
|
||||
const handle = result.value[1];
|
||||
client.switchWindow(handle);
|
||||
});
|
||||
|
||||
const login = client.page.login();
|
||||
|
||||
login
|
||||
.setValue('@emailInput', user.email)
|
||||
.setValue('@passwordInput', user.password)
|
||||
.waitForElementVisible('@signIn')
|
||||
.waitForElementVisible('@loginButton')
|
||||
.click('@loginButton');
|
||||
|
||||
// Focusing on the Embed Window
|
||||
client.windowHandles((result) => {
|
||||
const handle = result.value[0];
|
||||
client.switchWindow(handle);
|
||||
});
|
||||
},
|
||||
'user account is suspended, should see restricted message box': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementVisible('@restrictedMessageBox');
|
||||
},
|
||||
|
||||
'user picks another username': (client) => {
|
||||
const {testData: {user}} = client.globals;
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementVisible('@suspendedAccountInput')
|
||||
.setValue('@suspendedAccountInput', `${user.username}-alternative`)
|
||||
.waitForElementVisible('@suspendedAccountSubmitButton')
|
||||
.click('@suspendedAccountSubmitButton');
|
||||
},
|
||||
'user should not be able to comment': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementNotPresent('@commentBoxTextarea')
|
||||
.waitForElementNotPresent('@commentBoxPostButton');
|
||||
},
|
||||
after: (client) => {
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user