mirror of
https://github.com/wassname/talk.git
synced 2026-07-03 21:49:36 +08:00
Merge branch 'party-fixes' of github.com:coralproject/talk into party-fixes
* 'party-fixes' of github.com:coralproject/talk: (33 commits) patched migration bugs Don't close popup when there is an error Update .nsprc fix optimisticResponse typo Remove deprecated useMongoClient Update lint-staged to fix dependency issue copy updates Update package.json Ready Set is_test to false Removing Added support for new indexes Remove unused variable 2 Remove unused variable Remove ModerationIndicator from container Show username already exists error No indicator on new queue Exclude deleted when loading more Remove modqueue counts on the frontend sepatate state ...
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"https://nodesecurity.io/advisories/594",
|
||||
"https://nodesecurity.io/advisories/603",
|
||||
"https://nodesecurity.io/advisories/611",
|
||||
"https://nodesecurity.io/advisories/612"
|
||||
"https://nodesecurity.io/advisories/612",
|
||||
"https://nodesecurity.io/advisories/654"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
.external {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.separator h5 {
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.slot > * {
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './External.css';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty';
|
||||
|
||||
const External = ({ slot }) => (
|
||||
<IfSlotIsNotEmpty slot={slot}>
|
||||
<div>
|
||||
<div className={styles.external}>
|
||||
<Slot fill={slot} className={styles.slot} />
|
||||
</div>
|
||||
<div className={styles.separator}>
|
||||
<h5>Or</h5>
|
||||
</div>
|
||||
</div>
|
||||
</IfSlotIsNotEmpty>
|
||||
);
|
||||
|
||||
External.propTypes = {
|
||||
slot: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default External;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import styles from './SignIn.css';
|
||||
import { Button, TextField, Alert } from 'coral-ui';
|
||||
import cn from 'classnames';
|
||||
import Recaptcha from 'coral-framework/components/Recaptcha';
|
||||
import External from './External';
|
||||
|
||||
class SignIn extends React.Component {
|
||||
recaptcha = null;
|
||||
@@ -33,48 +34,55 @@ class SignIn extends React.Component {
|
||||
render() {
|
||||
const { email, password, errorMessage, requireRecaptcha } = this.props;
|
||||
return (
|
||||
<form className="talk-admin-login-sign-in" onSubmit={this.handleSubmit}>
|
||||
{errorMessage && <Alert>{errorMessage}</Alert>}
|
||||
<TextField
|
||||
id="email"
|
||||
label="Email Address"
|
||||
value={email}
|
||||
onChange={this.handleEmailChange}
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
label="Password"
|
||||
value={password}
|
||||
onChange={this.handlePasswordChange}
|
||||
type="password"
|
||||
/>
|
||||
{requireRecaptcha && (
|
||||
<div className={styles.recaptcha}>
|
||||
<Recaptcha
|
||||
ref={this.handleRecaptchaRef}
|
||||
onVerify={this.props.onRecaptchaVerify}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className={cn(styles.signInButton, 'talk-admin-login-sign-in-button')}
|
||||
type="submit"
|
||||
cStyle="black"
|
||||
full
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
<p className={styles.forgotPasswordCTA}>
|
||||
Forgot your password?{' '}
|
||||
<a
|
||||
href="#"
|
||||
className={styles.forgotPasswordLink}
|
||||
onClick={this.handleForgotPasswordLink}
|
||||
<div className="talk-admin-login-sign-in">
|
||||
<External slot="authExternalAdminSignIn" />
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{errorMessage && <Alert>{errorMessage}</Alert>}
|
||||
<TextField
|
||||
id="email"
|
||||
label="Email Address"
|
||||
value={email}
|
||||
onChange={this.handleEmailChange}
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
label="Password"
|
||||
value={password}
|
||||
onChange={this.handlePasswordChange}
|
||||
type="password"
|
||||
/>
|
||||
{requireRecaptcha && (
|
||||
<div className={styles.recaptcha}>
|
||||
<Recaptcha
|
||||
ref={this.handleRecaptchaRef}
|
||||
onVerify={this.props.onRecaptchaVerify}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className={cn(
|
||||
styles.signInButton,
|
||||
'talk-admin-login-sign-in-button'
|
||||
)}
|
||||
type="submit"
|
||||
cStyle="black"
|
||||
full
|
||||
>
|
||||
Request a new one.
|
||||
</a>
|
||||
</p>
|
||||
</form>
|
||||
Sign In
|
||||
</Button>
|
||||
<p className={styles.forgotPasswordCTA}>
|
||||
{/* TODO: translate */}
|
||||
Forgot your password?{' '}
|
||||
<a
|
||||
href="#"
|
||||
className={styles.forgotPasswordLink}
|
||||
onClick={this.handleForgotPasswordLink}
|
||||
>
|
||||
Request a new one.
|
||||
</a>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withSignIn } from 'coral-framework/hocs';
|
||||
import { withSignIn, withPopupAuthHandler } from 'coral-framework/hocs';
|
||||
import { compose } from 'recompose';
|
||||
import SignIn from '../components/SignIn';
|
||||
|
||||
@@ -55,4 +55,4 @@ SignInContainer.propTypes = {
|
||||
requireRecaptcha: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default compose(withSignIn)(SignInContainer);
|
||||
export default compose(withSignIn, withPopupAuthHandler)(SignInContainer);
|
||||
|
||||
@@ -82,6 +82,20 @@ class StreamSettings extends React.Component {
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateDisableCommenting = () => {
|
||||
const updater = {
|
||||
disableCommenting: {
|
||||
$set: !this.props.settings.disableCommenting,
|
||||
},
|
||||
};
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateDisableCommentingMessage = value => {
|
||||
const updater = { disableCommentingMessage: { $set: value } };
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateAutoClose = () => {
|
||||
const updater = {
|
||||
autoCloseStream: { $set: !this.props.settings.autoCloseStream },
|
||||
@@ -192,6 +206,25 @@ class StreamSettings extends React.Component {
|
||||
|
||||
{t('configure.edit_comment_timeframe_text_post')}
|
||||
</ConfigureCard>
|
||||
<ConfigureCard
|
||||
checked={settings.disableCommenting}
|
||||
onCheckbox={this.updateDisableCommenting}
|
||||
title={t('configure.disable_commenting_title')}
|
||||
>
|
||||
<p>{t('configure.disable_commenting_desc')}</p>
|
||||
<div
|
||||
className={cn(
|
||||
styles.configSettingDisableCommenting,
|
||||
settings.disableCommenting ? null : styles.hidden
|
||||
)}
|
||||
>
|
||||
<MarkdownEditor
|
||||
className={styles.descriptionBox}
|
||||
onChange={this.updateDisableCommentingMessage}
|
||||
value={settings.disableCommentingMessage}
|
||||
/>
|
||||
</div>
|
||||
</ConfigureCard>
|
||||
<ConfigureCard
|
||||
checked={settings.autoCloseStream}
|
||||
onCheckbox={this.updateAutoClose}
|
||||
|
||||
@@ -39,6 +39,8 @@ export default compose(
|
||||
autoCloseStream
|
||||
closedTimeout
|
||||
closedMessage
|
||||
disableCommenting
|
||||
disableCommentingMessage
|
||||
${getSlotFragmentSpreads(slots, 'settings')}
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,32 +3,36 @@ import { getStaticConfiguration } from 'coral-framework/services/staticConfigura
|
||||
import { createPostMessage } from 'coral-framework/services/postMessage';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
try {
|
||||
const staticConfig = getStaticConfiguration();
|
||||
const { STATIC_ORIGIN: origin } = staticConfig;
|
||||
const postMessage = createPostMessage(origin);
|
||||
const staticConfig = getStaticConfiguration();
|
||||
const { STATIC_ORIGIN: origin } = staticConfig;
|
||||
const postMessage = createPostMessage(origin);
|
||||
|
||||
// Get the auth element and parse it as JSON by decoding it.
|
||||
const auth = document.getElementById('auth');
|
||||
const doc = document.implementation.createHTMLDocument('');
|
||||
doc.body.innerHTML = auth.innerText;
|
||||
// Get the auth element and parse it as JSON by decoding it.
|
||||
const auth = document.getElementById('auth');
|
||||
const doc = document.implementation.createHTMLDocument('');
|
||||
doc.body.innerHTML = auth.innerText;
|
||||
|
||||
// Auth state is contained within the node.
|
||||
const { err, data } = JSON.parse(doc.body.textContent);
|
||||
if (err) {
|
||||
// TODO: send back the error message.
|
||||
console.error(err);
|
||||
// Auth state is contained within the node.
|
||||
const { err, data } = JSON.parse(doc.body.textContent);
|
||||
if (err) {
|
||||
const errDiv = document.createElement('div');
|
||||
if (err.message) {
|
||||
errDiv.innerText = `${err.name}: ${err.message}`;
|
||||
} else {
|
||||
// The data will contain a user and a token.
|
||||
const { user, token } = data;
|
||||
|
||||
// Send the state back.
|
||||
postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token });
|
||||
errDiv.innerText = JSON.stringify(err);
|
||||
}
|
||||
} finally {
|
||||
// Always close the window.
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 50);
|
||||
document.body.appendChild(errDiv);
|
||||
throw err;
|
||||
}
|
||||
|
||||
// The data will contain a user and a token.
|
||||
const { user, token } = data;
|
||||
|
||||
// Send the state back.
|
||||
postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token });
|
||||
|
||||
// Close the window when all went well.
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
@@ -745,10 +745,21 @@ export default class Comment extends React.Component {
|
||||
|
||||
const id = `c_${comment.id}`;
|
||||
|
||||
// props that are passed down the slots.
|
||||
const slotPassthrough = {
|
||||
action: 'deleted',
|
||||
comment,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={rootClassName} id={id}>
|
||||
{isCommentDeleted(comment) ? (
|
||||
<CommentTombstone action="deleted" />
|
||||
<Slot
|
||||
fill="commentTombstone"
|
||||
defaultComponent={CommentTombstone}
|
||||
size={1}
|
||||
passthrough={slotPassthrough}
|
||||
/>
|
||||
) : (
|
||||
<div>
|
||||
{this.renderComment()}
|
||||
|
||||
@@ -39,6 +39,7 @@ class CommentTombstone extends React.Component {
|
||||
|
||||
CommentTombstone.propTypes = {
|
||||
action: PropTypes.string,
|
||||
comment: PropTypes.object,
|
||||
onUndo: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import StreamError from './StreamError';
|
||||
import Comment from '../containers/Comment';
|
||||
import BannedAccount from '../../../components/BannedAccount';
|
||||
import ChangeUsername from '../containers/ChangeUsername';
|
||||
import Markdown from 'coral-framework/components/Markdown';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import InfoBox from './InfoBox';
|
||||
import { can } from 'coral-framework/services/perms';
|
||||
@@ -181,7 +182,9 @@ class Stream extends React.Component {
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
activeReplyBox={activeReplyBox}
|
||||
notify={notify}
|
||||
disableReply={asset.isClosed}
|
||||
disableReply={
|
||||
asset.isClosed || asset.settings.disableCommenting
|
||||
}
|
||||
postComment={postComment}
|
||||
currentUser={currentUser}
|
||||
postFlag={postFlag}
|
||||
@@ -215,7 +218,7 @@ class Stream extends React.Component {
|
||||
currentUser,
|
||||
} = this.props;
|
||||
const { keepCommentBox } = this.state;
|
||||
const open = !asset.isClosed;
|
||||
const open = !(asset.isClosed || asset.settings.disableCommenting);
|
||||
|
||||
const banned = get(currentUser, 'status.banned.status');
|
||||
const suspensionUntil = get(currentUser, 'status.suspension.until');
|
||||
@@ -293,7 +296,13 @@ class Stream extends React.Component {
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p>{asset.settings.closedMessage}</p>
|
||||
<div>
|
||||
{asset.isClosed ? (
|
||||
<p>{asset.settings.closedMessage}</p>
|
||||
) : (
|
||||
<Markdown content={asset.settings.disableCommentingMessage} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Slot fill="stream" passthrough={slotPassthrough} />
|
||||
|
||||
@@ -24,6 +24,7 @@ const slots = [
|
||||
'commentAuthorName',
|
||||
'commentAuthorTags',
|
||||
'commentTimestamp',
|
||||
'commentTombstone',
|
||||
'commentContent',
|
||||
];
|
||||
|
||||
|
||||
@@ -434,6 +434,8 @@ const fragments = {
|
||||
questionBoxIcon
|
||||
closedTimeout
|
||||
closedMessage
|
||||
disableCommenting
|
||||
disableCommentingMessage
|
||||
charCountEnable
|
||||
charCount
|
||||
requireEmailConfirmation
|
||||
|
||||
@@ -116,7 +116,7 @@ export const withRemoveTag = withMutation(
|
||||
asset_id: assetId,
|
||||
item_type: itemType,
|
||||
},
|
||||
o3timisticResponse: {
|
||||
optimisticResponse: {
|
||||
removeTag: {
|
||||
__typename: 'ModifyTagResponse',
|
||||
errors: null,
|
||||
|
||||
@@ -59,6 +59,7 @@ const withSetUsername = hoistStatics(WrappedComponent => {
|
||||
}
|
||||
const changeSet = { success: false, loading: false, error };
|
||||
this.setState(changeSet);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -56,10 +56,10 @@ export function createPostMessage(origin, scope = 'client') {
|
||||
// Send the message.
|
||||
target.postMessage(msg, origin);
|
||||
},
|
||||
subscribe: (handler, target = window) => {
|
||||
subscribe(handler, target = window) {
|
||||
// If this handler is already attached to the target, detach it.
|
||||
if (has(listeners, [target, handler])) {
|
||||
this.unsubscribeFromMessages(handler, target);
|
||||
this.unsubscribe(handler, target);
|
||||
}
|
||||
|
||||
// Wrap the listener with a origin check.
|
||||
@@ -71,7 +71,7 @@ export function createPostMessage(origin, scope = 'client') {
|
||||
// Attach the listener to the target.
|
||||
target.addEventListener('message', listener);
|
||||
},
|
||||
unsubscribe: (handler, target = window) => {
|
||||
unsubscribe(handler, target = window) {
|
||||
if (!has(listeners, [target, handler])) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -273,3 +273,23 @@ export function translateError(error) {
|
||||
}
|
||||
return error.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* handlePopupAuth will optionally open a popup with the requested uri if the
|
||||
* window is not already a popup.
|
||||
*
|
||||
* @param {String} uri the url to open the window? to
|
||||
* @param {String} title the title of the new window? to open
|
||||
* @param {String} features the features to use when opening a window?
|
||||
*/
|
||||
export function handlePopupAuth(
|
||||
uri,
|
||||
title = 'Login', // TODO: translate
|
||||
features = 'menubar=0,resizable=0,width=500,height=550,top=200,left=500'
|
||||
) {
|
||||
if (window.opener) {
|
||||
window.location = uri;
|
||||
} else {
|
||||
window.open(uri, title, features);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ You won't have to use this to build plugins, but it's helpful to find where to e
|
||||
* `commentReactions`
|
||||
* `commentActions`
|
||||
* `commentInputArea`
|
||||
* `commentTombstone`
|
||||
|
||||
* `draftArea`
|
||||
* `streamSettings`
|
||||
|
||||
@@ -161,6 +161,24 @@ class ErrAssetCommentingClosed extends TalkError {
|
||||
}
|
||||
}
|
||||
|
||||
// ErrCommentingDisabled is returned when a comment or action is attempted while
|
||||
// commenting has been disabled site-wide.
|
||||
class ErrCommentingDisabled extends TalkError {
|
||||
constructor(message = null) {
|
||||
super(
|
||||
'asset commenting is closed',
|
||||
{
|
||||
status: 400,
|
||||
translation_key: 'COMMENTING_DISABLED',
|
||||
},
|
||||
{
|
||||
// Include the closedMessage in the metadata piece of the error.
|
||||
message,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ErrAuthentication is returned when there is an error authenticating and the
|
||||
* message is provided.
|
||||
@@ -387,6 +405,7 @@ module.exports = {
|
||||
ErrAuthentication,
|
||||
ErrCannotIgnoreStaff,
|
||||
ErrCommentTooShort,
|
||||
ErrCommentingDisabled,
|
||||
ErrContainsProfanity,
|
||||
ErrEditWindowHasEnded,
|
||||
ErrEmailAlreadyVerified,
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
|
||||
@@ -837,6 +837,13 @@ type Settings {
|
||||
# closed.
|
||||
closedMessage: String
|
||||
|
||||
# disableCommenting will disable commenting site-wide.
|
||||
disableCommenting: Boolean
|
||||
|
||||
# disableCommentingMessage will be shown above the comment stream while
|
||||
# commenting is disabled site-wide.
|
||||
disableCommentingMessage: String
|
||||
|
||||
# editCommentWindowLength is the length of time (in milliseconds) after a
|
||||
# comment is posted that it can still be edited by the author.
|
||||
editCommentWindowLength: Int
|
||||
@@ -1300,6 +1307,13 @@ input UpdateSettingsInput {
|
||||
# closed.
|
||||
closedMessage: String
|
||||
|
||||
# disableCommenting will disable commenting site-wide.
|
||||
disableCommenting: Boolean
|
||||
|
||||
# disableCommentingMessage will be shown above the comment stream while
|
||||
# commenting is disabled site-wide.
|
||||
disableCommentingMessage: String
|
||||
|
||||
# charCountEnable is true when the character count restriction is enabled.
|
||||
charCountEnable: Boolean
|
||||
|
||||
|
||||
@@ -121,6 +121,8 @@ de:
|
||||
custom_css_url_desc: "URL eines CSS-Stylesheets zum Überschreiben des Standard-Designs"
|
||||
days: Tage
|
||||
description: "Als Administrator können Sie die Einstellungen für den Kommentarbereich dieses Artikels anpassen:"
|
||||
disable_commenting_title: "Kommentieren global deaktivieren"
|
||||
disable_commenting_desc: "Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist."
|
||||
domain_list_text: "Geben Sie Domains an, für die diese Talk-Instanz freigegeben werden soll, z.B. für lokale Test- oder Produktionsumgebungen (Bsp.: localhost:3000 staging.domain.com domain.com)."
|
||||
domain_list_title: "Zugelassene Domains"
|
||||
edit_comment_timeframe_heading: "Zeitlimit zur Bearbeitung von Kommentaren"
|
||||
@@ -223,6 +225,7 @@ de:
|
||||
LOGIN_MAXIMUM_EXCEEDED: "Sie haben zu häufig erfolglos versucht, sich anzumelden. Bitte warten Sie."
|
||||
PASSWORD_REQUIRED: "Passwort ist erforderlich"
|
||||
COMMENTING_CLOSED: "Kommentarbereich ist bereits geschlossen"
|
||||
COMMENTING_DISABLED: "Die Kommentarfunktion ist derzeit abgeschaltet"
|
||||
NOT_FOUND: "Ressource nicht gefunden"
|
||||
ALREADY_EXISTS: "Ressource existiert bereits"
|
||||
INVALID_ASSET_URL: "Asset-URL ist ungültig"
|
||||
|
||||
@@ -124,6 +124,8 @@ en:
|
||||
custom_css_url_desc: "URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external."
|
||||
days: Days
|
||||
description: "Change the comment settings on this story."
|
||||
disable_commenting_title: "Deactivate commenting site-wide"
|
||||
disable_commenting_desc: "Write a message that will be displayed while commenting is deactivated."
|
||||
domain_list_text: "Enter the domains you would like to permit for Talk e.g. your local staging and production environments (ex. localhost:3000 staging.domain.com domain.com)."
|
||||
domain_list_title: "Permitted Domains"
|
||||
edit_info: "Edit Info"
|
||||
@@ -247,6 +249,7 @@ en:
|
||||
LOGIN_MAXIMUM_EXCEEDED: "You have made too many unsuccessful password attempts. Please wait."
|
||||
PASSWORD_REQUIRED: "Must input a password"
|
||||
COMMENTING_CLOSED: "Commenting is already closed"
|
||||
COMMENTING_DISABLED: "Commenting is currently disabled on this site"
|
||||
NOT_FOUND: "Resource not found"
|
||||
ALREADY_EXISTS: "Resource already exists"
|
||||
INVALID_ASSET_URL: "Assert URL is invalid"
|
||||
|
||||
+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,
|
||||
@@ -66,6 +68,14 @@ const Setting = new Schema(
|
||||
type: String,
|
||||
default: 'Expired',
|
||||
},
|
||||
disableCommenting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disableCommentingMessage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
wordlist: {
|
||||
banned: {
|
||||
type: Array,
|
||||
|
||||
+29
-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;
|
||||
@@ -348,6 +339,11 @@ User.virtual('banned')
|
||||
})
|
||||
.set(function(status) {
|
||||
this.status.banned.status = status;
|
||||
|
||||
if (!this.status.banned.history) {
|
||||
this.status.banned.history = [];
|
||||
}
|
||||
|
||||
this.status.banned.history.push({
|
||||
status,
|
||||
created_at: new Date(),
|
||||
@@ -366,6 +362,11 @@ User.virtual('suspended')
|
||||
})
|
||||
.set(function(until) {
|
||||
this.status.suspension.until = until;
|
||||
|
||||
if (!this.status.suspension.history) {
|
||||
this.status.suspension.history = [];
|
||||
}
|
||||
|
||||
this.status.suspension.history.push({
|
||||
until,
|
||||
created_at: new Date(),
|
||||
|
||||
+3
-3
@@ -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,
|
||||
@@ -149,7 +149,7 @@
|
||||
"metascraper-title": "^3.9.2",
|
||||
"minimist": "^1.2.0",
|
||||
"moment": "^2.18.1",
|
||||
"mongoose": "^4.12.3",
|
||||
"mongoose": "^5.1.1",
|
||||
"ms": "^2.0.0",
|
||||
"murmurhash-js": "^1.0.0",
|
||||
"name-all-modules-plugin": "^1.0.1",
|
||||
@@ -231,7 +231,7 @@
|
||||
"husky": "^0.14.3",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest-junit": "^3.6.0",
|
||||
"lint-staged": "^7.0.0",
|
||||
"lint-staged": "^7.1.0",
|
||||
"mocha": "^3.1.2",
|
||||
"mocha-junit-reporter": "^1.12.1",
|
||||
"nightwatch": "^0.9.16",
|
||||
|
||||
@@ -9,4 +9,5 @@ export {
|
||||
getDefinitionName,
|
||||
getShallowChanges,
|
||||
createDefaultResponseFragments,
|
||||
handlePopupAuth,
|
||||
} from 'coral-framework/utils';
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { handlePopupAuth } from 'plugin-api/beta/client/utils';
|
||||
|
||||
export const loginWithFacebook = () => (dispatch, _, { rest }) => {
|
||||
window.location = `${rest.uri}/auth/facebook`;
|
||||
handlePopupAuth(`${rest.uri}/auth/facebook`);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import translations from './translations.yml';
|
||||
export default {
|
||||
translations,
|
||||
slots: {
|
||||
authExternalAdminSignIn: [SignIn],
|
||||
authExternalSignIn: [SignIn],
|
||||
authExternalSignUp: [SignUp],
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { handlePopupAuth } from 'plugin-api/beta/client/utils';
|
||||
|
||||
export const loginWithGoogle = () => (dispatch, _, { rest }) => {
|
||||
window.location = `${rest.uri}/auth/google`;
|
||||
handlePopupAuth(`${rest.uri}/auth/google`);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import translations from './translations.yml';
|
||||
export default {
|
||||
translations,
|
||||
slots: {
|
||||
authExternalAdminSignIn: [SignIn],
|
||||
authExternalSignIn: [SignIn],
|
||||
authExternalSignUp: [SignUp],
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -225,11 +223,21 @@ class Profile extends React.Component {
|
||||
disabled={!usernameCanBeUpdated}
|
||||
columnDisplay
|
||||
>
|
||||
<span className={styles.bottomText}>
|
||||
{t(
|
||||
'talk-plugin-local-auth.change_username.change_username_note'
|
||||
<div className={styles.bottomText}>
|
||||
<span>
|
||||
{t(
|
||||
'talk-plugin-local-auth.change_username.change_username_note'
|
||||
)}
|
||||
</span>
|
||||
{!usernameCanBeUpdated && (
|
||||
<b>
|
||||
{' '}
|
||||
{t(
|
||||
'talk-plugin-local-auth.change_username.is_not_eligible'
|
||||
)}
|
||||
</b>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</InputField>
|
||||
<InputField
|
||||
icon="email"
|
||||
|
||||
@@ -19,7 +19,8 @@ en:
|
||||
changed_password_msg: "Changed Password - Your password has been successfully changed"
|
||||
forgot_password_sent: "Forgot Password - We sent you an email to recover your password"
|
||||
change_username:
|
||||
change_username_note: "Usernames can only be changed once every 14 days. Your username is not currently eligible to be updated."
|
||||
change_username_note: "Usernames can only be changed once every 14 days."
|
||||
is_not_eligible: "You cannot currently change your username."
|
||||
save: "Save"
|
||||
edit_profile: "Edit Profile"
|
||||
cancel: "Cancel"
|
||||
@@ -81,7 +82,8 @@ es:
|
||||
changed_password_msg: "Contraseña Actualizada - Tu contraseña ha sido exitosamente actualizada"
|
||||
forgot_password_sent: "Contraseña Olvidada - Te enviamos un email para recuperar tu contraseña"
|
||||
change_username:
|
||||
change_username_note: "El usuario puede ser cambiado cada 14 días"
|
||||
change_username_note: "El usuario puede ser cambiado cada 14 días."
|
||||
is_not_eligible: "Ahora mismo no se puede cambiar su nombre de usuario."
|
||||
save: "Guardar"
|
||||
edit_profile: "Editar Perfil"
|
||||
cancel: "Cancelar"
|
||||
|
||||
+22
-27
@@ -2,7 +2,7 @@ const ActionModel = require('../models/action');
|
||||
const CommentModel = require('../models/comment');
|
||||
const UserModel = require('../models/user');
|
||||
const _ = require('lodash');
|
||||
const errors = require('../errors');
|
||||
const { ErrAlreadyExists } = require('../errors');
|
||||
|
||||
const incrActionCounts = async (action, value) => {
|
||||
const ACTION_TYPE = action.action_type.toLowerCase();
|
||||
@@ -41,35 +41,30 @@ const incrActionCounts = async (action, value) => {
|
||||
* @param {object} update the update operation for the mongo findOneAndUpdate op
|
||||
* @param {object} options the options operation for the mongo findOneAndUpdate op
|
||||
*/
|
||||
const findOnlyOneAndUpdate = async (query, update, options = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
ActionModel.findOneAndUpdate(
|
||||
query,
|
||||
update,
|
||||
Object.assign({}, options, {
|
||||
// Use raw result to get `updatedExisting`.
|
||||
passRawResult: true,
|
||||
const findOnlyOneAndUpdate = async (query, update, options = {}) => {
|
||||
const raw = await ActionModel.findOneAndUpdate(
|
||||
query,
|
||||
update,
|
||||
Object.assign({}, options, {
|
||||
// Use raw result to get `updatedExisting`.
|
||||
rawResult: true,
|
||||
|
||||
// Ensure that if it's new, we return the new object created.
|
||||
new: true,
|
||||
// Ensure that if it's new, we return the new object created.
|
||||
new: true,
|
||||
|
||||
// Perform an upsert in the event that this doesn't exist.
|
||||
upsert: true,
|
||||
// Perform an upsert in the event that this doesn't exist.
|
||||
upsert: true,
|
||||
|
||||
// Set the default values if not provided based on the mongoose models.
|
||||
setDefaultsOnInsert: true,
|
||||
}),
|
||||
(err, doc, raw) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
if (raw.lastErrorObject.updatedExisting) {
|
||||
return reject(new errors.ErrAlreadyExists(raw.value));
|
||||
}
|
||||
return resolve(raw.value);
|
||||
}
|
||||
);
|
||||
});
|
||||
// Set the default values if not provided based on the mongoose models.
|
||||
setDefaultsOnInsert: true,
|
||||
})
|
||||
);
|
||||
if (raw.lastErrorObject.updatedExisting) {
|
||||
throw new ErrAlreadyExists(raw.value);
|
||||
}
|
||||
|
||||
return raw.value;
|
||||
};
|
||||
|
||||
module.exports = class ActionsService {
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
wordlist,
|
||||
commentLength,
|
||||
assetClosed,
|
||||
commentingDisabled,
|
||||
karma,
|
||||
staff,
|
||||
links,
|
||||
@@ -36,6 +37,7 @@ const applyStatus = status => () => ({ status });
|
||||
const phases = [
|
||||
commentLength,
|
||||
assetClosed,
|
||||
commentingDisabled,
|
||||
wordlist,
|
||||
staff,
|
||||
links,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const { ErrCommentingDisabled } = require('../../../errors');
|
||||
|
||||
// This phase checks to see if commenting is site-wide disabled.
|
||||
module.exports = (ctx, comment, { asset }) => {
|
||||
// Check to see if the asset has closed commenting...
|
||||
if (asset.settings.disableCommenting) {
|
||||
throw new ErrCommentingDisabled(asset.settings.disableCommentingMessage);
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
module.exports.wordlist = require('./wordlist');
|
||||
module.exports.commentLength = require('./commentLength');
|
||||
module.exports.assetClosed = require('./assetClosed');
|
||||
module.exports.commentingDisabled = require('./commentingDisabled');
|
||||
module.exports.karma = require('./karma');
|
||||
module.exports.staff = require('./staff');
|
||||
module.exports.links = require('./links');
|
||||
|
||||
@@ -45,7 +45,6 @@ if (WEBPACK) {
|
||||
// Connect to the Mongo instance.
|
||||
mongoose
|
||||
.connect(MONGO_URL, {
|
||||
useMongoClient: true,
|
||||
config: {
|
||||
autoIndex: CREATE_MONGO_INDEXES,
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -179,6 +179,54 @@ describe('graph.mutations.createComment', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('assets while commenting is disabled', () => {
|
||||
[
|
||||
{
|
||||
disabled: false,
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
disabled: true,
|
||||
error: 'COMMENTING_DISABLED',
|
||||
},
|
||||
].forEach(({ disabled, error }) => {
|
||||
describe(`commentingDisabled=${disabled}`, () => {
|
||||
beforeEach(() =>
|
||||
AssetModel.create({
|
||||
id: '123',
|
||||
settings: { disableCommenting: disabled },
|
||||
})
|
||||
);
|
||||
|
||||
it(
|
||||
error ? 'does not create the comment' : 'creates the comment',
|
||||
() => {
|
||||
const context = new Context({ user: new UserModel({}) });
|
||||
|
||||
return graphql(schema, query, {}, context).then(
|
||||
({ data, errors }) => {
|
||||
expect(errors).to.be.undefined;
|
||||
if (error) {
|
||||
expect(data.createComment).to.have.property('comment').null;
|
||||
expect(data.createComment).to.have.property('errors').not
|
||||
.null;
|
||||
expect(data.createComment.errors[0]).to.have.property(
|
||||
'translation_key',
|
||||
error
|
||||
);
|
||||
} else {
|
||||
expect(data.createComment).to.have.property('comment').not
|
||||
.null;
|
||||
expect(data.createComment).to.have.property('errors').null;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('comments made with different asset moderation settings', () => {
|
||||
[
|
||||
{ moderation: 'PRE', status: 'PREMOD' },
|
||||
|
||||
@@ -1644,6 +1644,10 @@ bson@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.4.tgz#93c10d39eaa5b58415cbc4052f3e53e562b0b72c"
|
||||
|
||||
bson@~1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.6.tgz#444db59ddd4c24f0cb063aabdc5c8c7b0ceca912"
|
||||
|
||||
buffer-crc32@^0.2.1, buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
@@ -1652,10 +1656,6 @@ buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
|
||||
buffer-shims@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
|
||||
|
||||
buffer-xor@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
|
||||
@@ -3353,10 +3353,6 @@ es6-map@^0.1.3:
|
||||
es6-symbol "~3.1.1"
|
||||
event-emitter "~0.3.5"
|
||||
|
||||
es6-promise@3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4"
|
||||
|
||||
es6-promise@^4.0.5:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a"
|
||||
@@ -4890,10 +4886,6 @@ home-or-tmp@^2.0.0:
|
||||
os-homedir "^1.0.0"
|
||||
os-tmpdir "^1.0.1"
|
||||
|
||||
hooks-fixed@2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hooks-fixed/-/hooks-fixed-2.0.2.tgz#20076daa07e77d8a6106883ce3f1722e051140b0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
|
||||
@@ -6399,9 +6391,9 @@ jxLoader@*:
|
||||
promised-io "*"
|
||||
walker "1.x"
|
||||
|
||||
kareem@1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/kareem/-/kareem-1.5.0.tgz#e3e4101d9dcfde299769daf4b4db64d895d17448"
|
||||
kareem@2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.0.7.tgz#8d260366a4df4236ceccec318fcf10c17c5beb22"
|
||||
|
||||
keymaster@^1.6.2:
|
||||
version "1.6.2"
|
||||
@@ -6528,9 +6520,9 @@ linkifyjs@^2.1.5:
|
||||
react ">=0.14.0"
|
||||
react-dom ">=0.14.0"
|
||||
|
||||
lint-staged@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-7.0.0.tgz#57926c63201e7bd38ca0576d74391efa699b4a9d"
|
||||
lint-staged@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-7.1.0.tgz#1514a5b71b8d9492ca0c3d2a44769cbcbc8bcc79"
|
||||
dependencies:
|
||||
app-root-path "^2.0.1"
|
||||
chalk "^2.3.1"
|
||||
@@ -6541,6 +6533,7 @@ lint-staged@^7.0.0:
|
||||
execa "^0.9.0"
|
||||
find-parent-dir "^0.3.0"
|
||||
is-glob "^4.0.0"
|
||||
is-windows "^1.0.2"
|
||||
jest-validate "^22.4.0"
|
||||
listr "^0.13.0"
|
||||
lodash "^4.17.5"
|
||||
@@ -6550,8 +6543,9 @@ lint-staged@^7.0.0:
|
||||
p-map "^1.1.1"
|
||||
path-is-inside "^1.0.2"
|
||||
pify "^3.0.0"
|
||||
please-upgrade-node "^3.0.1"
|
||||
staged-git-files "1.1.0"
|
||||
please-upgrade-node "^3.0.2"
|
||||
staged-git-files "1.1.1"
|
||||
string-argv "^0.0.2"
|
||||
stringify-object "^3.2.2"
|
||||
|
||||
listr-silent-renderer@^1.1.1:
|
||||
@@ -7451,36 +7445,36 @@ 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"
|
||||
mongodb-core@3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-3.0.8.tgz#8d401f4eab6056c0d874a3d5844a4844f761d4d7"
|
||||
dependencies:
|
||||
bson "~1.0.4"
|
||||
require_optional "~1.0.0"
|
||||
require_optional "^1.0.1"
|
||||
|
||||
mongodb@2.2.33:
|
||||
version "2.2.33"
|
||||
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.33.tgz#b537c471d34a6651b48f36fdbf29750340e08b50"
|
||||
mongodb@3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.0.8.tgz#2c1daecac9a0ec2de2f2aea4dc97d76ae70f8951"
|
||||
dependencies:
|
||||
es6-promise "3.2.1"
|
||||
mongodb-core "2.1.17"
|
||||
readable-stream "2.2.7"
|
||||
mongodb-core "3.0.8"
|
||||
|
||||
mongoose@^4.12.3:
|
||||
version "4.13.7"
|
||||
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.13.7.tgz#f760c770e6c8cdf34a6fe8b7443882b5fced1032"
|
||||
mongoose-legacy-pluralize@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4"
|
||||
|
||||
mongoose@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.1.1.tgz#a7e925607e76032e5ef20b3035a357bc8581b45e"
|
||||
dependencies:
|
||||
async "2.1.4"
|
||||
bson "~1.0.4"
|
||||
hooks-fixed "2.0.2"
|
||||
kareem "1.5.0"
|
||||
bson "~1.0.5"
|
||||
kareem "2.0.7"
|
||||
lodash.get "4.4.2"
|
||||
mongodb "2.2.33"
|
||||
mpath "0.3.0"
|
||||
mpromise "0.5.5"
|
||||
mquery "2.3.3"
|
||||
mongodb "3.0.8"
|
||||
mongoose-legacy-pluralize "1.0.2"
|
||||
mpath "0.4.1"
|
||||
mquery "3.0.0"
|
||||
ms "2.0.0"
|
||||
muri "1.3.0"
|
||||
regexp-clone "0.0.1"
|
||||
sliced "1.0.1"
|
||||
|
||||
@@ -7509,17 +7503,13 @@ move-concurrently@^1.0.1:
|
||||
rimraf "^2.5.4"
|
||||
run-queue "^1.0.3"
|
||||
|
||||
mpath@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.3.0.tgz#7a58f789e9b5fd3c94520634157960f26bd5ef44"
|
||||
mpath@0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.4.1.tgz#ed10388430380bf7bbb5be1391e5d6969cb08e89"
|
||||
|
||||
mpromise@0.5.5:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mpromise/-/mpromise-0.5.5.tgz#f5b24259d763acc2257b0a0c8c6d866fd51732e6"
|
||||
|
||||
mquery@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/mquery/-/mquery-2.3.3.tgz#221412e5d4e7290ca5582dd16ea8f190a506b518"
|
||||
mquery@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.0.0.tgz#e5f387dbabc0b9b69859e550e810faabe0ceabb0"
|
||||
dependencies:
|
||||
bluebird "3.5.0"
|
||||
debug "2.6.9"
|
||||
@@ -7538,10 +7528,6 @@ ms@^2.0.0, ms@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||
|
||||
muri@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/muri/-/muri-1.3.0.tgz#aeccf3db64c56aa7c5b34e00f95b7878527a4721"
|
||||
|
||||
murmurhash-js@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51"
|
||||
@@ -8556,9 +8542,11 @@ platform@1.3.4:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd"
|
||||
|
||||
please-upgrade-node@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.0.1.tgz#0a681f2c18915e5433a5ca2cd94e0b8206a782db"
|
||||
please-upgrade-node@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.0.2.tgz#7b9eaeca35aa4a43d6ebdfd10616c042f9a83acc"
|
||||
dependencies:
|
||||
semver-compare "^1.0.0"
|
||||
|
||||
pluralize@^1.2.1:
|
||||
version "1.2.1"
|
||||
@@ -9717,18 +9705,6 @@ readable-stream@2, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stre
|
||||
string_decoder "~1.0.3"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@2.2.7:
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.7.tgz#07057acbe2467b22042d36f98c5ad507054e95b1"
|
||||
dependencies:
|
||||
buffer-shims "~1.0.0"
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~1.0.6"
|
||||
string_decoder "~1.0.0"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readdirp@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
|
||||
@@ -10034,7 +10010,7 @@ require-uncached@^1.0.3:
|
||||
caller-path "^0.1.0"
|
||||
resolve-from "^1.0.0"
|
||||
|
||||
require_optional@~1.0.0:
|
||||
require_optional@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e"
|
||||
dependencies:
|
||||
@@ -10285,6 +10261,10 @@ selenium-standalone@^6.11.0:
|
||||
which "^1.2.12"
|
||||
yauzl "^2.5.0"
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
|
||||
semver-diff@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||
@@ -10762,9 +10742,9 @@ stack-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
|
||||
|
||||
staged-git-files@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.0.tgz#1a9bb131c1885601023c7aaddd3d54c22142c526"
|
||||
staged-git-files@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.1.tgz#37c2218ef0d6d26178b1310719309a16a59f8f7b"
|
||||
|
||||
static-extend@^0.1.1:
|
||||
version "0.1.2"
|
||||
@@ -10841,6 +10821,10 @@ strict-uri-encode@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
|
||||
string-argv@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736"
|
||||
|
||||
string-hash@^1.1.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
|
||||
@@ -10875,7 +10859,7 @@ string.prototype.padend@^3.0.0:
|
||||
es-abstract "^1.4.3"
|
||||
function-bind "^1.0.2"
|
||||
|
||||
string_decoder@^1.0.0, string_decoder@~1.0.0, string_decoder@~1.0.3:
|
||||
string_decoder@^1.0.0, string_decoder@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user