diff --git a/client/coral-admin/src/components/App.css b/client/coral-admin/src/components/App.css new file mode 100644 index 000000000..39c7cf942 --- /dev/null +++ b/client/coral-admin/src/components/App.css @@ -0,0 +1,11 @@ +:global { + html, body, #root, #root > div { + min-height: 100%; + } + + body { + margin: 0; + background-color: #FAFAFA; + font-family: 'Roboto', sans-serif; + } +} diff --git a/client/coral-admin/src/components/App.js b/client/coral-admin/src/components/App.js index 52535f91a..a7ab1e336 100644 --- a/client/coral-admin/src/components/App.js +++ b/client/coral-admin/src/components/App.js @@ -1,5 +1,6 @@ import React from 'react'; import ToastContainer from './ToastContainer'; +import './App.css'; import 'material-design-lite'; import AppRouter from '../AppRouter'; diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js index fc150b840..f0bb68c0c 100644 --- a/client/coral-admin/src/components/Header.js +++ b/client/coral-admin/src/components/Header.js @@ -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')} - )} { - 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} diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js index 75a274eb3..27394c6c9 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js @@ -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} > {queue.name}{' '} - + {queue.indicator && } ))} diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js index e47a162ef..c22843c8f 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js @@ -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, diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index c908404cb..e3f35081c 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -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 diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.css b/client/coral-embed-stream/src/tabs/profile/components/Comment.css index 9004a1e6c..c2bf841d5 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Comment.css +++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.css @@ -31,6 +31,7 @@ font-weight: bold; font-size: 12px; color: #757575; + cursor: pointer; } .commentSummary { diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.js b/client/coral-embed-stream/src/tabs/profile/components/Comment.js index 171172dab..badeb4262 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.js @@ -11,16 +11,6 @@ import { getTotalReactionsCount } from 'coral-framework/utils'; import t from 'coral-framework/services/i18n'; class Comment extends React.Component { - goToStory = () => { - this.props.navigate(this.props.comment.asset.url); - }; - - goToConversation = () => { - this.props.navigate( - `${this.props.comment.asset.url}?commentId=${this.props.comment.id}` - ); - }; - render() { const { comment, root } = this.props; const reactionCount = getTotalReactionsCount(comment.action_summaries); @@ -76,8 +66,8 @@ class Comment extends React.Component {
{t('common.story')}:{' '} {comment.asset.title ? comment.asset.title : comment.asset.url} @@ -87,7 +77,13 @@ class Comment extends React.Component {
  • - + {t('view_conversation')} diff --git a/client/coral-framework/components/Popup.js b/client/coral-framework/components/Popup.js index 53d8f7cd8..339ddb806 100644 --- a/client/coral-framework/components/Popup.js +++ b/client/coral-framework/components/Popup.js @@ -37,7 +37,8 @@ export default class Popup extends Component { this.onBlur(); }; - // Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari. + // Use `onunload` instead of `onbeforeunload` which is not supported in iOS + // Safari. this.ref.onunload = () => { this.onUnload(); @@ -46,10 +47,15 @@ export default class Popup extends Component { } this.resetCallbackInterval = setInterval(() => { - if (this.ref && this.ref.onload === null) { - clearInterval(this.resetCallbackInterval); - this.resetCallbackInterval = null; - this.setCallbacks(); + try { + if (this.ref && this.ref.onload === null) { + clearInterval(this.resetCallbackInterval); + this.resetCallbackInterval = null; + this.setCallbacks(); + } + } catch (err) { + // We could be getting a security exception here if the login page + // gets redirected to another domain to authenticate. } }, 50); diff --git a/client/coral-framework/hocs/withSetUsername.js b/client/coral-framework/hocs/withSetUsername.js index 05323efa1..3a8f65a22 100644 --- a/client/coral-framework/hocs/withSetUsername.js +++ b/client/coral-framework/hocs/withSetUsername.js @@ -59,6 +59,7 @@ const withSetUsername = hoistStatics(WrappedComponent => { } const changeSet = { success: false, loading: false, error }; this.setState(changeSet); + throw error; } }; diff --git a/graph/loaders/settings.js b/graph/loaders/settings.js index 8b07d712b..7010d7533 100644 --- a/graph/loaders/settings.js +++ b/graph/loaders/settings.js @@ -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() }); diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index c40aa49e0..acd55cb1a 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -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' ); diff --git a/middleware/staticTemplate.js b/middleware/staticTemplate.js index f16caaea9..d8137693e 100644 --- a/middleware/staticTemplate.js +++ b/middleware/staticTemplate.js @@ -94,9 +94,13 @@ const createResolveFactory = (() => { module.exports = async (req, res, next) => { try { - // Attach the custom css url. - const { customCssUrl } = await SettingsService.select('customCssUrl'); + // Attach the custom css url and organization name. + const { customCssUrl, organizationName } = await SettingsService.select( + 'customCssUrl', + 'organizationName' + ); res.locals.customCssUrl = customCssUrl; + res.locals.organizationName = organizationName; } catch (err) { console.warn(err); } diff --git a/models/schema/action.js b/models/schema/action.js index 1df7bd793..d2047faac 100644 --- a/models/schema/action.js +++ b/models/schema/action.js @@ -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; diff --git a/models/schema/comment.js b/models/schema/comment.js index a211884ff..7ae51ff41 100644 --- a/models/schema/comment.js +++ b/models/schema/comment.js @@ -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; }); diff --git a/models/schema/setting.js b/models/schema/setting.js index d75bed29e..d68ee7acd 100644 --- a/models/schema/setting.js +++ b/models/schema/setting.js @@ -12,6 +12,8 @@ const Setting = new Schema( id: { type: String, default: '1', + unique: 1, + index: true, }, moderation: { type: String, diff --git a/models/schema/user.js b/models/schema/user.js index ec9c018cc..a4ccfd185 100644 --- a/models/schema/user.js +++ b/models/schema/user.js @@ -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; diff --git a/package.json b/package.json index f6ff46a59..97f4e373d 100644 --- a/package.json +++ b/package.json @@ -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, @@ -219,6 +219,7 @@ "babel-plugin-dynamic-import-node": "^1.1.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", "browserstack-local": "^1.3.0", + "casual": "^1.5.19", "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "chai-datetime": "^1.5.0", diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js index 5aa3063b3..f1f25b261 100644 --- a/plugin-api/beta/server/getReactionConfig.js +++ b/plugin-api/beta/server/getReactionConfig.js @@ -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, diff --git a/plugins/talk-plugin-akismet/server/hooks.js b/plugins/talk-plugin-akismet/server/hooks.js index 80a233584..b06557783 100644 --- a/plugins/talk-plugin-akismet/server/hooks.js +++ b/plugins/talk-plugin-akismet/server/hooks.js @@ -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`); diff --git a/plugins/talk-plugin-local-auth/client/actions.js b/plugins/talk-plugin-local-auth/client/actions.js new file mode 100644 index 000000000..972e1f6fd --- /dev/null +++ b/plugins/talk-plugin-local-auth/client/actions.js @@ -0,0 +1,9 @@ +import * as actions from './constants'; + +export const startAttach = () => ({ + type: actions.START_ATTACH, +}); + +export const finishAttach = () => ({ + type: actions.FINISH_ATTACH, +}); diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js index dc7e0d974..a5bfd7096 100644 --- a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js +++ b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js @@ -45,6 +45,10 @@ class AddEmailAddressDialog extends React.Component { ), }; + componentDidMount() { + this.props.startAttach(); + } + onChange = e => { const { name, value } = e.target; this.setState( @@ -99,7 +103,13 @@ class AddEmailAddressDialog extends React.Component { }); }; - confirmChanges = async () => { + finish = () => { + this.props.finishAttach(); + }; + + confirmChanges = async e => { + e.preventDefault(); + if (!this.validate()) { this.showErrors(); return; @@ -113,7 +123,11 @@ class AddEmailAddressDialog extends React.Component { email: emailAddress, password: confirmPassword, }); - this.props.notify('success', 'Email Added!'); + + this.props.notify( + 'success', + t('talk-plugin-local-auth.add_email.added.alert') + ); this.goToNextStep(); } catch (err) { this.props.notify('error', getErrorMessages(err)); @@ -143,13 +157,13 @@ class AddEmailAddressDialog extends React.Component { )} {step === 1 && !settings.requireEmailConfirmation && ( - {}} /> + )} {step === 1 && settings.requireEmailConfirmation && ( {}} + done={this.finish} /> )} @@ -161,6 +175,8 @@ AddEmailAddressDialog.propTypes = { attachLocalAuth: PropTypes.func.isRequired, notify: PropTypes.func.isRequired, root: PropTypes.object.isRequired, + startAttach: PropTypes.func.isRequired, + finishAttach: PropTypes.func.isRequired, }; export default AddEmailAddressDialog; diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js b/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js index 8d3fc308f..de296c812 100644 --- a/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js +++ b/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js @@ -41,7 +41,7 @@ const AddEmailContent = ({
-
+
diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js index f29dd8d86..f671c065b 100644 --- a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js +++ b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js @@ -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 (
- + ×

@@ -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 />
diff --git a/views/dev/articles.ejs b/views/dev/articles.ejs index 0ce684029..1b07ed116 100644 --- a/views/dev/articles.ejs +++ b/views/dev/articles.ejs @@ -1,14 +1,40 @@ - -

- Asset list -

-<% assets.forEach(function (asset) { %> - <%= asset.url %>
-<% }) %> -

- (For dev use only. FYI, you can: ?skip=100&limit=25) -

- - + + All Assets + <%- include ../partials/dev %> + + + <%- include ../partials/dev-nav %> +
+
+

All Assets

+ <%= skip + 1 %> - <%= skip + assets.length %> of <%= count %> Assets +
+
+ <% if (skip === 0) { %> Create a random article<% } %> + <% assets.forEach(function (asset) { %> + +
+
<%= asset.title %>
+ Created <%= asset.created_at.toLocaleString('en-US') %> +
+ <%= asset.url %> +
+ <% }) %> +
+ <% if (count !== assets.length) { %> + + <% } %> +
+ diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs index 2027f6069..a1c708b55 100644 --- a/views/embed/stream.ejs +++ b/views/embed/stream.ejs @@ -5,6 +5,7 @@ <%- include ../partials/head %> + <%- include ../partials/custom-css %>
diff --git a/views/login.ejs b/views/login.ejs index c5a86c2fb..671b25c83 100644 --- a/views/login.ejs +++ b/views/login.ejs @@ -6,6 +6,7 @@ <%- include partials/head %> + <%- include partials/custom-css %>
diff --git a/views/partials/account.ejs b/views/partials/account.ejs index 79857baf0..ff2166816 100644 --- a/views/partials/account.ejs +++ b/views/partials/account.ejs @@ -1,3 +1,4 @@ -<%- include ./head %> +<%- include head %> +<%- include custom-css %> diff --git a/views/partials/custom-css.ejs b/views/partials/custom-css.ejs new file mode 100644 index 000000000..d453c37e1 --- /dev/null +++ b/views/partials/custom-css.ejs @@ -0,0 +1 @@ +<% if (locals.customCssUrl) { %><% } %> diff --git a/views/partials/dev-nav.ejs b/views/partials/dev-nav.ejs new file mode 100644 index 000000000..088c52c20 --- /dev/null +++ b/views/partials/dev-nav.ejs @@ -0,0 +1,8 @@ + diff --git a/views/partials/dev.ejs b/views/partials/dev.ejs new file mode 100644 index 000000000..691b548e5 --- /dev/null +++ b/views/partials/dev.ejs @@ -0,0 +1,5 @@ + + + + + diff --git a/views/partials/head.ejs b/views/partials/head.ejs index e848b3909..dc3df7103 100644 --- a/views/partials/head.ejs +++ b/views/partials/head.ejs @@ -18,9 +18,6 @@ -<%_ if (locals.customCssUrl) { _%> - -<%_ } _%> <%- include data %> diff --git a/yarn.lock b/yarn.lock index 5cf363b6e..f0da6d006 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1828,6 +1828,13 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" +casual@^1.5.19: + version "1.5.19" + resolved "https://registry.yarnpkg.com/casual/-/casual-1.5.19.tgz#66fac46f7ae463f468f5913eb139f9c41c58bbf2" + dependencies: + mersenne-twister "^1.0.1" + moment "^2.15.2" + center-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" @@ -7154,6 +7161,10 @@ merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" +mersenne-twister@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" + metascraper-author@^3.9.2: version "3.9.2" resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-3.9.2.tgz#ff2020ac428f59a875d655df3b0d4bea171fde19" @@ -7436,6 +7447,10 @@ moment@^2.10.3: version "2.19.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167" +moment@^2.15.2: + version "2.22.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" + mongodb-core@2.1.17: version "2.1.17" resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.17.tgz#a418b337a14a14990fb510b923dee6a813173df8"