Merge branch 'feature/fix-email-config-docs' of git+ssh://github.com/coralproject/talk into feature/fix-email-config-docs

This commit is contained in:
Kim Gardner
2017-11-09 11:20:07 +00:00
17 changed files with 302 additions and 49 deletions
@@ -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;
+2
View File
@@ -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',
}
};
+22
View File
@@ -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'
}
};
+16 -2
View File
@@ -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: {
+1
View File
@@ -49,6 +49,7 @@ module.exports = {
.click('@drawerOverlay')
.waitForElementVisible('@communitySection');
},
after: (client) => {
client.end();
}
+157
View File
@@ -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();
}
};