mirror of
https://github.com/wassname/talk.git
synced 2026-07-27 11:28:12 +08:00
Merge branch 'master' into admin-auth
This commit is contained in:
@@ -7,7 +7,6 @@ import styles from './Header.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { Logo } from './Logo';
|
||||
import { can } from 'coral-framework/services/perms';
|
||||
import ModerationIndicator from '../routes/Moderation/containers/Indicator';
|
||||
import CommunityIndicator from '../routes/Community/containers/Indicator';
|
||||
|
||||
const CoralHeader = ({
|
||||
@@ -32,7 +31,6 @@ const CoralHeader = ({
|
||||
activeClassName={styles.active}
|
||||
>
|
||||
{t('configure.moderate')}
|
||||
<ModerationIndicator root={root} data={data} />
|
||||
</IndexLink>
|
||||
)}
|
||||
<Link
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
.indicator {
|
||||
display: inline-block;
|
||||
background-color: #E46D59;
|
||||
border-radius: 10px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-top: -4px;
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
@@ -2,21 +2,20 @@ import { gql } from 'react-apollo';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import Header from '../components/Header';
|
||||
import CommunityIndicator from '../routes/Community/containers/Indicator';
|
||||
import ModerationIndicator from '../routes/Moderation/containers/Indicator';
|
||||
// TODO: eventually we will readd modqueue counts
|
||||
// import ModerationIndicator from '../routes/Moderation/containers/Indicator';
|
||||
import { getDefinitionName } from 'coral-framework/utils';
|
||||
|
||||
export default withQuery(
|
||||
gql`
|
||||
query TalkAdmin_Header($nullID: ID) {
|
||||
...${getDefinitionName(ModerationIndicator.fragments.root)}
|
||||
query TalkAdmin_Header {
|
||||
...${getDefinitionName(CommunityIndicator.fragments.root)}
|
||||
}
|
||||
${ModerationIndicator.fragments.root}
|
||||
${CommunityIndicator.fragments.root}
|
||||
`,
|
||||
{
|
||||
options: {
|
||||
variables: { nullID: null },
|
||||
// variables: { nullID: null },
|
||||
},
|
||||
}
|
||||
)(Header);
|
||||
|
||||
@@ -63,10 +63,6 @@ class Moderation extends Component {
|
||||
this.props.toggleStorySearch(true);
|
||||
};
|
||||
|
||||
getActiveTabCount = (props = this.props) => {
|
||||
return props.root[`${props.activeTab}Count`];
|
||||
};
|
||||
|
||||
moderate = accept => {
|
||||
const {
|
||||
acceptComment,
|
||||
@@ -139,12 +135,14 @@ class Moderation extends Component {
|
||||
|
||||
const comments = root[activeTab];
|
||||
|
||||
const activeTabCount = this.getActiveTabCount();
|
||||
const menuItems = Object.keys(queueConfig).map(queue => ({
|
||||
key: queue,
|
||||
name: queueConfig[queue].name,
|
||||
icon: queueConfig[queue].icon,
|
||||
count: root[`${queue}Count`],
|
||||
indicator:
|
||||
['premod', 'reported'].includes(queue) && root[queue].nodes.length > 0,
|
||||
// TODO: Eventually we'll reintroduce counting
|
||||
// count: root[`${props.queue}Count`]
|
||||
}));
|
||||
|
||||
const slotPassthrough = {
|
||||
@@ -189,7 +187,6 @@ class Moderation extends Component {
|
||||
loadMore={this.loadMore}
|
||||
commentBelongToQueue={this.props.commentBelongToQueue}
|
||||
isLoadingMore={this.state.isLoadingMore}
|
||||
commentCount={activeTabCount}
|
||||
currentUserId={this.props.currentUser.id}
|
||||
viewUserDetail={viewUserDetail}
|
||||
selectCommentId={props.selectCommentId}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import CountBadge from '../../../components/CountBadge';
|
||||
import Indicator from '../../../components/Indicator';
|
||||
import styles from './ModerationMenu.css';
|
||||
import { Icon } from 'coral-ui';
|
||||
import { Link } from 'react-router';
|
||||
@@ -32,7 +32,7 @@ const ModerationMenu = ({ asset = {}, items, getModPath, activeTab }) => {
|
||||
activeClassName={styles.active}
|
||||
>
|
||||
<Icon name={queue.icon} className={styles.tabIcon} /> {queue.name}{' '}
|
||||
<CountBadge count={queue.count} />
|
||||
{queue.indicator && <Indicator />}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -204,7 +204,7 @@ class ModerationQueue extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prev) {
|
||||
const { commentCount, selectedCommentId } = this.props;
|
||||
const { selectedCommentId, hasNextPage } = this.props;
|
||||
|
||||
const switchedToMultiMode = prev.singleView && !this.props.singleView;
|
||||
const switchedMode = prev.singleView !== this.props.singleView;
|
||||
@@ -212,7 +212,6 @@ class ModerationQueue extends React.Component {
|
||||
prev.selectedCommentId !== selectedCommentId && selectedCommentId;
|
||||
const moderatedLastComment =
|
||||
prev.comments.length > 0 && this.getCommentCountWithoutDagling() === 0;
|
||||
const hasMoreComment = commentCount > 0;
|
||||
|
||||
if (switchedToMultiMode) {
|
||||
// Reflow virtual list.
|
||||
@@ -223,7 +222,7 @@ class ModerationQueue extends React.Component {
|
||||
this.scrollToSelectedComment();
|
||||
}
|
||||
|
||||
if (moderatedLastComment && hasMoreComment) {
|
||||
if (moderatedLastComment && hasNextPage) {
|
||||
this.props.loadMore();
|
||||
}
|
||||
}
|
||||
@@ -240,10 +239,7 @@ class ModerationQueue extends React.Component {
|
||||
const index = view.findIndex(
|
||||
({ id }) => id === this.props.selectedCommentId
|
||||
);
|
||||
if (
|
||||
index === view.length - 1 &&
|
||||
this.getCommentCountWithoutDagling() !== this.props.commentCount
|
||||
) {
|
||||
if (index === view.length - 1 && this.props.hasNextPage) {
|
||||
await this.props.loadMore();
|
||||
this.selectDown();
|
||||
return;
|
||||
@@ -467,7 +463,6 @@ ModerationQueue.propTypes = {
|
||||
acceptComment: PropTypes.func.isRequired,
|
||||
commentBelongToQueue: PropTypes.func.isRequired,
|
||||
cleanUpQueue: PropTypes.func.isRequired,
|
||||
commentCount: PropTypes.number.isRequired,
|
||||
loadMore: PropTypes.func.isRequired,
|
||||
singleView: PropTypes.bool,
|
||||
isLoadingMore: PropTypes.bool,
|
||||
|
||||
@@ -314,11 +314,11 @@ class ModerationContainer extends Component {
|
||||
|
||||
const currentQueueConfig = Object.assign({}, this.props.queueConfig);
|
||||
|
||||
if (premodEnabled && root.newCount === 0) {
|
||||
if (premodEnabled && root.new.nodes.length === 0) {
|
||||
delete currentQueueConfig.new;
|
||||
}
|
||||
|
||||
if (!premodEnabled && root.premodCount === 0) {
|
||||
if (!premodEnabled && root.premod.nodes.length === 0) {
|
||||
delete currentQueueConfig.premod;
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ const COMMENT_RESET_SUBSCRIPTION = gql`
|
||||
|
||||
const LOAD_MORE_QUERY = gql`
|
||||
query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Cursor, $sortOrder: SORT_ORDER, $asset_id: ID, $tags:[String!], $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) {
|
||||
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags}) {
|
||||
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags, excludeDeleted: true}) {
|
||||
nodes {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
}
|
||||
@@ -456,7 +456,11 @@ const withModQueueQuery = withQuery(
|
||||
}
|
||||
`
|
||||
)}
|
||||
${Object.keys(queueConfig).map(
|
||||
${
|
||||
''
|
||||
/*
|
||||
TODO: eventually we'll reintroduce counting..
|
||||
Object.keys(queueConfig).map(
|
||||
queue => `
|
||||
${queue}Count: commentCount(query: {
|
||||
excludeDeleted: true,
|
||||
@@ -478,7 +482,8 @@ const withModQueueQuery = withQuery(
|
||||
asset_id: $asset_id,
|
||||
})
|
||||
`
|
||||
)}
|
||||
)*/
|
||||
}
|
||||
asset(id: $asset_id) @skip(if: $allAssets) {
|
||||
id
|
||||
title
|
||||
|
||||
@@ -59,6 +59,7 @@ const withSetUsername = hoistStatics(WrappedComponent => {
|
||||
}
|
||||
const changeSet = { success: false, loading: false, error };
|
||||
this.setState(changeSet);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -55,6 +55,18 @@ class SettingsLoader {
|
||||
// assembled Settings object.
|
||||
return zipObject(fields, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* get, like select, will retrieve the settings, but get will only return a
|
||||
* single setting.
|
||||
*
|
||||
* @param {String} field the field to get
|
||||
*/
|
||||
async get(field) {
|
||||
const value = await this._loader.load(field);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = () => ({ Settings: new SettingsLoader() });
|
||||
|
||||
@@ -57,8 +57,8 @@ const Comment = {
|
||||
asset({ asset_id }, _, { loaders: { Assets } }) {
|
||||
return Assets.getByID.load(asset_id);
|
||||
},
|
||||
async editing(comment, _, { loaders: { Settings } }) {
|
||||
const { editCommentWindowLength } = await Settings.select(
|
||||
editing: async (comment, _, { loaders: { Settings } }) => {
|
||||
const editCommentWindowLength = await Settings.get(
|
||||
'editCommentWindowLength'
|
||||
);
|
||||
|
||||
|
||||
+6
-13
@@ -9,7 +9,8 @@ const Action = new Schema(
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
unique: 1,
|
||||
index: 1,
|
||||
},
|
||||
action_type: {
|
||||
type: String,
|
||||
@@ -19,7 +20,10 @@ const Action = new Schema(
|
||||
type: String,
|
||||
enum: ITEM_TYPES,
|
||||
},
|
||||
item_id: String,
|
||||
item_id: {
|
||||
type: String,
|
||||
index: 1,
|
||||
},
|
||||
user_id: String,
|
||||
|
||||
// The element that summaries will additionally group on in addtion to their action_type, item_type, and
|
||||
@@ -37,15 +41,4 @@ const Action = new Schema(
|
||||
}
|
||||
);
|
||||
|
||||
// Create an index on the `item_id` field so that queries looking for
|
||||
// actions based on the item id can resolve faster.
|
||||
Action.index(
|
||||
{
|
||||
item_id: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = Action;
|
||||
|
||||
+55
-104
@@ -55,12 +55,16 @@ const Comment = new Schema(
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
body: {
|
||||
type: String,
|
||||
},
|
||||
body_history: [BodyHistoryItemSchema],
|
||||
asset_id: String,
|
||||
asset_id: {
|
||||
type: String,
|
||||
index: true,
|
||||
},
|
||||
author_id: String,
|
||||
status_history: [Status],
|
||||
status: {
|
||||
@@ -90,7 +94,6 @@ const Comment = new Schema(
|
||||
// deleted_at stores the date that the given comment was deleted.
|
||||
deleted_at: {
|
||||
type: Date,
|
||||
default: null,
|
||||
},
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
@@ -110,95 +113,67 @@ const Comment = new Schema(
|
||||
}
|
||||
);
|
||||
|
||||
// Add the indexes for the id of the comment.
|
||||
Comment.index(
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
background: false,
|
||||
}
|
||||
);
|
||||
|
||||
Comment.index(
|
||||
{
|
||||
status: 1,
|
||||
created_at: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
Comment.index(
|
||||
{
|
||||
status: 1,
|
||||
created_at: 1,
|
||||
asset_id: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Create a sparse index to search across.
|
||||
Comment.index(
|
||||
{
|
||||
created_at: 1,
|
||||
'action_counts.flag': 1,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
sparse: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Create a sparse index to search across.
|
||||
Comment.index(
|
||||
{
|
||||
'action_counts.flag': 1,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
sparse: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Add an index that is optimized for finding flagged comments.
|
||||
Comment.index(
|
||||
{
|
||||
asset_id: 1,
|
||||
created_at: 1,
|
||||
'action_counts.flag': 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Add an index for the reply sort.
|
||||
Comment.index(
|
||||
{
|
||||
asset_id: 1,
|
||||
deleted_at: 1,
|
||||
created_at: -1,
|
||||
reply_count: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
{ partialFilterExpression: { deleted_at: null } }
|
||||
);
|
||||
|
||||
// Add an index that is optimized for finding a user's comments.
|
||||
Comment.index(
|
||||
{
|
||||
author_id: 1,
|
||||
deleted_at: 1,
|
||||
status: 1,
|
||||
created_at: -1,
|
||||
},
|
||||
{ partialFilterExpression: { deleted_at: null } }
|
||||
);
|
||||
|
||||
Comment.index({
|
||||
asset_id: 1,
|
||||
created_at: -1,
|
||||
});
|
||||
|
||||
Comment.index({
|
||||
asset_id: 1,
|
||||
created_at: 1,
|
||||
});
|
||||
|
||||
Comment.index({
|
||||
author_id: 1,
|
||||
created_at: -1,
|
||||
});
|
||||
|
||||
Comment.index({
|
||||
asset_id: 1,
|
||||
status: 1,
|
||||
});
|
||||
|
||||
Comment.index({
|
||||
asset_id: 1,
|
||||
parent_id: 1,
|
||||
reply_count: -1,
|
||||
created_at: -1,
|
||||
});
|
||||
|
||||
Comment.index({
|
||||
asset_id: 1,
|
||||
reply_count: -1,
|
||||
created_at: -1,
|
||||
});
|
||||
|
||||
Comment.index(
|
||||
{
|
||||
'action_counts.flag': 1,
|
||||
status: 1,
|
||||
created_at: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
partialFilterExpression: {
|
||||
'action_counts.flag': { $exists: true, $gt: 0 },
|
||||
deleted_at: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -210,34 +185,10 @@ Comment.index(
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Optimize for tag searches/counts.
|
||||
Comment.index(
|
||||
{
|
||||
'tags.tag.name': 1,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
sparse: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Add an index that is optimized for sorting based on the created_at timestamp
|
||||
// but also good at locating comments that have a specific asset id.
|
||||
Comment.index(
|
||||
{
|
||||
asset_id: 1,
|
||||
created_at: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
Comment.virtual('edited').get(function() {
|
||||
return this.body_history.length > 1;
|
||||
});
|
||||
|
||||
@@ -12,6 +12,8 @@ const Setting = new Schema(
|
||||
id: {
|
||||
type: String,
|
||||
default: '1',
|
||||
unique: 1,
|
||||
index: true,
|
||||
},
|
||||
moderation: {
|
||||
type: String,
|
||||
|
||||
+19
-28
@@ -58,6 +58,7 @@ const User = new Schema(
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
|
||||
// This is sourced from the social provider or set manually during user setup
|
||||
@@ -107,6 +108,7 @@ const User = new Schema(
|
||||
status: {
|
||||
type: String,
|
||||
enum: USER_STATUS_USERNAME,
|
||||
index: true,
|
||||
},
|
||||
|
||||
// History stores the history of username status changes.
|
||||
@@ -135,6 +137,7 @@ const User = new Schema(
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
index: true,
|
||||
},
|
||||
history: [
|
||||
{
|
||||
@@ -226,41 +229,26 @@ User.index(
|
||||
}
|
||||
);
|
||||
|
||||
User.index(
|
||||
{
|
||||
lowercaseUsername: 1,
|
||||
'profiles.id': 1,
|
||||
created_at: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
User.index({
|
||||
lowercaseUsername: 1,
|
||||
'profiles.id': 1,
|
||||
created_at: -1,
|
||||
});
|
||||
|
||||
// This query is executed often, to count the number of flagged accounts with
|
||||
// usernames.
|
||||
User.index(
|
||||
{
|
||||
'action_counts.flag': 1,
|
||||
'status.username.status': 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
User.index({
|
||||
'action_counts.flag': 1,
|
||||
'status.username.status': 1,
|
||||
});
|
||||
|
||||
// Sorting users by created at is the default people search.
|
||||
User.index(
|
||||
{
|
||||
created_at: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
User.index({
|
||||
created_at: -1,
|
||||
});
|
||||
|
||||
/**
|
||||
* returns true if a commenter is staff
|
||||
* returns true if a commenter is staff.
|
||||
*/
|
||||
User.method('isStaff', function() {
|
||||
return this.role !== 'COMMENTER';
|
||||
@@ -330,6 +318,9 @@ User.virtual('hasVerifiedEmail').get(function() {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* system returns true when the user is a system user.
|
||||
*/
|
||||
User.virtual('system')
|
||||
.get(function() {
|
||||
return this._system;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talk",
|
||||
"version": "4.4.0",
|
||||
"version": "4.4.1",
|
||||
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
||||
"main": "app.js",
|
||||
"private": true,
|
||||
|
||||
@@ -11,10 +11,22 @@ function getReactionConfig(reaction) {
|
||||
|
||||
if (CREATE_MONGO_INDEXES) {
|
||||
// Create the index on the comment model based on the reaction config.
|
||||
Comment.collection.createIndex(
|
||||
Comment.collection.ensureIndex(
|
||||
{
|
||||
created_at: 1,
|
||||
[`action_counts.${sc(reaction)}`]: 1,
|
||||
asset_id: 1,
|
||||
[`action_counts.${sc(reaction)}`]: -1,
|
||||
created_at: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
Comment.collection.ensureIndex(
|
||||
{
|
||||
asset_id: 1,
|
||||
[`action_counts.${sc(reaction)}`]: -1,
|
||||
created_at: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
|
||||
@@ -71,7 +71,7 @@ module.exports = {
|
||||
permalink: asset.url,
|
||||
comment_type: 'comment',
|
||||
comment_content: input.body,
|
||||
is_test: true,
|
||||
is_test: false,
|
||||
});
|
||||
|
||||
debug(`comment analyzed as ${spam ? 'being' : 'not being'} spam`);
|
||||
|
||||
@@ -4,10 +4,74 @@ import styles from './ChangeEmailContentDialog.css';
|
||||
import InputField from './InputField';
|
||||
import { Button } from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import validate from 'coral-framework/helpers/validate';
|
||||
import errorMsj from 'coral-framework/helpers/error';
|
||||
|
||||
const initialState = {
|
||||
showError: false,
|
||||
formData: {
|
||||
confirmPassword: '',
|
||||
},
|
||||
errors: {},
|
||||
};
|
||||
|
||||
class ChangeEmailContentDialog extends React.Component {
|
||||
state = {
|
||||
showError: false,
|
||||
state = initialState;
|
||||
|
||||
clearForm = () => {
|
||||
this.setState(initialState);
|
||||
};
|
||||
|
||||
addError = err => {
|
||||
this.setState(({ errors }) => ({
|
||||
errors: { ...errors, ...err },
|
||||
}));
|
||||
};
|
||||
|
||||
removeError = errKey => {
|
||||
this.setState(state => {
|
||||
const { [errKey]: _, ...errors } = state.errors;
|
||||
return {
|
||||
errors,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
fieldValidation = (value, type, name) => {
|
||||
if (!value.length) {
|
||||
this.addError({
|
||||
[name]: t('talk-plugin-local-auth.change_password.required_field'),
|
||||
});
|
||||
} else if (!validate[type](value)) {
|
||||
this.addError({ [name]: errorMsj[type] });
|
||||
} else {
|
||||
this.removeError(name);
|
||||
}
|
||||
};
|
||||
|
||||
onChange = e => {
|
||||
const { name, value, type, dataset } = e.target;
|
||||
const validationType = dataset.validationType || type;
|
||||
|
||||
this.setState(
|
||||
state => ({
|
||||
formData: {
|
||||
...state.formData,
|
||||
[name]: value,
|
||||
},
|
||||
}),
|
||||
() => {
|
||||
this.fieldValidation(value, validationType, name);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
hasError = err => {
|
||||
return Object.keys(this.state.errors).indexOf(err) !== -1;
|
||||
};
|
||||
|
||||
getError = errorKey => {
|
||||
return this.state.errors[errorKey];
|
||||
};
|
||||
|
||||
showError = () => {
|
||||
@@ -16,24 +80,31 @@ class ChangeEmailContentDialog extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
cancel = () => {
|
||||
this.clearForm();
|
||||
this.props.closeDialog();
|
||||
};
|
||||
|
||||
confirmChanges = async e => {
|
||||
e.preventDefault();
|
||||
|
||||
const { confirmPassword = '' } = this.state.formData;
|
||||
|
||||
if (this.formHasError()) {
|
||||
this.showError();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.props.save();
|
||||
await this.props.save(confirmPassword);
|
||||
this.props.next();
|
||||
};
|
||||
|
||||
formHasError = () => this.props.hasError('confirmPassword');
|
||||
formHasError = () => this.hasError('confirmPassword');
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<span className={styles.close} onClick={this.props.cancel}>
|
||||
<span className={styles.close} onClick={this.cancel}>
|
||||
×
|
||||
</span>
|
||||
<h1 className={styles.title}>
|
||||
@@ -59,17 +130,17 @@ class ChangeEmailContentDialog extends React.Component {
|
||||
label={t('talk-plugin-local-auth.change_email.enter_password')}
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
onChange={this.props.onChange}
|
||||
defaultValue=""
|
||||
hasError={this.props.hasError('confirmPassword')}
|
||||
errorMsg={this.props.getError('confirmPassword')}
|
||||
onChange={this.onChange}
|
||||
value={this.state.formData.confirmPassword}
|
||||
hasError={this.hasError('confirmPassword')}
|
||||
errorMsg={this.getError('confirmPassword')}
|
||||
showError={this.state.showError}
|
||||
columnDisplay
|
||||
/>
|
||||
<div className={styles.bottomActions}>
|
||||
<Button
|
||||
className={styles.cancel}
|
||||
onClick={this.props.cancel}
|
||||
onClick={this.cancel}
|
||||
type="button"
|
||||
>
|
||||
{t('talk-plugin-local-auth.change_email.cancel')}
|
||||
@@ -86,14 +157,11 @@ class ChangeEmailContentDialog extends React.Component {
|
||||
}
|
||||
|
||||
ChangeEmailContentDialog.propTypes = {
|
||||
save: PropTypes.func,
|
||||
next: PropTypes.func,
|
||||
cancel: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
save: PropTypes.func,
|
||||
formData: PropTypes.object,
|
||||
email: PropTypes.string,
|
||||
hasError: PropTypes.func,
|
||||
getError: PropTypes.func,
|
||||
closeDialog: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ChangeEmailContentDialog;
|
||||
|
||||
@@ -138,8 +138,8 @@ class Profile extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
saveEmail = async () => {
|
||||
const { newEmail, confirmPassword } = this.state.formData;
|
||||
saveEmail = async confirmPassword => {
|
||||
const { newEmail } = this.state.formData;
|
||||
|
||||
try {
|
||||
await this.props.updateEmailAddress({
|
||||
@@ -202,12 +202,10 @@ class Profile extends React.Component {
|
||||
)}
|
||||
<ChangeEmailContentDialog
|
||||
save={this.saveEmail}
|
||||
onChange={this.onChange}
|
||||
formData={this.state.formData}
|
||||
email={email}
|
||||
enable={formData.newEmail && email !== formData.newEmail}
|
||||
hasError={this.hasError}
|
||||
getError={this.getError}
|
||||
closeDialog={this.closeDialog}
|
||||
/>
|
||||
</ConfirmChangesDialog>
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
const Setting = require('../models/setting');
|
||||
const { ErrSettingsNotInit } = require('../errors');
|
||||
const { dotize } = require('./utils');
|
||||
const { isEmpty, zipObject, uniq } = require('lodash');
|
||||
const { isEmpty, zipObject } = require('lodash');
|
||||
const DataLoader = require('dataloader');
|
||||
|
||||
const selector = { id: '1' };
|
||||
|
||||
async function loadFn(fields = []) {
|
||||
const model = await Setting.findOne(selector).select(uniq(fields));
|
||||
async function loadFn(/* fields = [] */) {
|
||||
// Originally, we used the projection operation, turns out this isn't that
|
||||
// fast. We should utilize the redis cache instead here.
|
||||
// const model = await Setting.findOne(selector).select(uniq(fields));
|
||||
|
||||
const model = await Setting.findOne(selector);
|
||||
if (!model) {
|
||||
throw new ErrSettingsNotInit();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user