mirror of
https://github.com/wassname/talk.git
synced 2026-07-20 12:40:47 +08:00
Merge branch 'master' into staff-badge-bug
This commit is contained in:
@@ -19,5 +19,8 @@ plugins/*
|
||||
!plugins/talk-plugin-sort-most-liked
|
||||
!plugins/talk-plugin-sort-most-loved
|
||||
!plugins/talk-plugin-sort-most-respected
|
||||
!plugins/talk-plugin-author-menu
|
||||
!plugins/talk-plugin-member-since
|
||||
!plugins/talk-plugin-ignore-user
|
||||
|
||||
node_modules
|
||||
|
||||
@@ -35,5 +35,8 @@ plugins/*
|
||||
!plugins/talk-plugin-sort-most-liked
|
||||
!plugins/talk-plugin-sort-most-loved
|
||||
!plugins/talk-plugin-sort-most-respected
|
||||
!plugins/talk-plugin-author-menu
|
||||
!plugins/talk-plugin-member-since
|
||||
!plugins/talk-plugin-ignore-user
|
||||
|
||||
**/node_modules/*
|
||||
|
||||
@@ -69,15 +69,15 @@ function getCommentQueues(comment, queueConfig) {
|
||||
const queues = [];
|
||||
Object.keys(queueConfig).forEach((key) => {
|
||||
const {action_type, statuses, tags} = queueConfig[key];
|
||||
let addToQueues = false;
|
||||
if (statuses && statuses.indexOf(comment.status) >= 0) {
|
||||
addToQueues = true;
|
||||
let addToQueues = true;
|
||||
if (statuses && statuses.indexOf(comment.status) === -1) {
|
||||
addToQueues = false;
|
||||
}
|
||||
if (tags && comment.tags && comment.tags.some((tagLink) => tags.indexOf(tagLink.tag.name) >= 0)) {
|
||||
addToQueues = true;
|
||||
if (tags && (!comment.tags || !comment.tags.some((tagLink) => tags.indexOf(tagLink.tag.name) >= 0))) {
|
||||
addToQueues = false;
|
||||
}
|
||||
if (action_type && comment.actions && comment.actions.some((a) => a.__typename.toLowerCase() === `${action_type}action`)) {
|
||||
addToQueues = true;
|
||||
if (action_type && (!comment.actions || !comment.actions.some((a) => a.__typename.toLowerCase() === `${action_type.toLowerCase()}action`))) {
|
||||
addToQueues = false;
|
||||
}
|
||||
if (addToQueues) {
|
||||
queues.push(key);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import LoadMore from './LoadMore';
|
||||
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
|
||||
import NewCount from './NewCount';
|
||||
import {TransitionGroup} from 'react-transition-group';
|
||||
import {forEachError} from 'coral-framework/utils';
|
||||
@@ -126,7 +125,6 @@ class AllCommentsPane extends React.Component {
|
||||
root,
|
||||
comments,
|
||||
commentClassNames,
|
||||
ignoreUser,
|
||||
setActiveReplyBox,
|
||||
activeReplyBox,
|
||||
notify,
|
||||
@@ -139,7 +137,6 @@ class AllCommentsPane extends React.Component {
|
||||
loadNewReplies,
|
||||
deleteAction,
|
||||
showSignInDialog,
|
||||
commentIsIgnored,
|
||||
charCountEnable,
|
||||
maxCharCount,
|
||||
editComment,
|
||||
@@ -157,9 +154,8 @@ class AllCommentsPane extends React.Component {
|
||||
/>
|
||||
<TransitionGroup component='div' className="embed__stream">
|
||||
{view.map((comment) => {
|
||||
return commentIsIgnored(comment)
|
||||
? <IgnoredCommentTombstone key={comment.id} />
|
||||
: <Comment
|
||||
return (
|
||||
<Comment
|
||||
commentClassNames={commentClassNames}
|
||||
data={data}
|
||||
root={root}
|
||||
@@ -173,8 +169,6 @@ class AllCommentsPane extends React.Component {
|
||||
currentUser={currentUser}
|
||||
postFlag={postFlag}
|
||||
postDontAgree={postDontAgree}
|
||||
ignoreUser={ignoreUser}
|
||||
commentIsIgnored={commentIsIgnored}
|
||||
loadMore={loadNewReplies}
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
@@ -184,7 +178,8 @@ class AllCommentsPane extends React.Component {
|
||||
maxCharCount={maxCharCount}
|
||||
editComment={editComment}
|
||||
emit={emit}
|
||||
/>;
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TransitionGroup>
|
||||
<LoadMore
|
||||
|
||||
@@ -66,17 +66,6 @@
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.topRight .popover {
|
||||
margin-top: 1em;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.topRight .link.active,
|
||||
.topRight .active .link {
|
||||
padding-bottom: 0.125em;
|
||||
border-bottom: 2px solid currentColor;
|
||||
}
|
||||
|
||||
.editCommentForm {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -175,4 +164,4 @@
|
||||
|
||||
.username {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import mapValues from 'lodash/mapValues';
|
||||
import LoadMore from './LoadMore';
|
||||
import {getEditableUntilDate} from './util';
|
||||
import {findCommentWithId} from '../graphql/utils';
|
||||
import {TopRightMenu} from './TopRightMenu';
|
||||
import CommentContent from './CommentContent';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
|
||||
@@ -184,12 +183,6 @@ export default class Comment extends React.Component {
|
||||
})
|
||||
}).isRequired,
|
||||
|
||||
// given a comment, return whether it should be rendered as ignored
|
||||
commentIsIgnored: PropTypes.func,
|
||||
|
||||
// dispatch action to ignore another user
|
||||
ignoreUser: PropTypes.func,
|
||||
|
||||
// edit a comment, passed (id, asset_id, { body })
|
||||
editComment: PropTypes.func,
|
||||
|
||||
@@ -216,9 +209,18 @@ export default class Comment extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
commentIsIgnored(comment) {
|
||||
const me = this.props.root.me;
|
||||
return (
|
||||
me &&
|
||||
me.ignoredUsers &&
|
||||
me.ignoredUsers.find((u) => u.id === comment.user.id)
|
||||
);
|
||||
}
|
||||
|
||||
hasIgnoredReplies() {
|
||||
return this.props.comment.replies &&
|
||||
this.props.comment.replies.nodes.some((reply) => this.props.commentIsIgnored(reply));
|
||||
this.props.comment.replies.nodes.some((reply) => this.commentIsIgnored(reply));
|
||||
}
|
||||
|
||||
loadNewReplies = () => {
|
||||
@@ -317,7 +319,6 @@ export default class Comment extends React.Component {
|
||||
comment,
|
||||
postFlag,
|
||||
parentId,
|
||||
ignoreUser,
|
||||
highlighted,
|
||||
postComment,
|
||||
currentUser,
|
||||
@@ -332,12 +333,15 @@ export default class Comment extends React.Component {
|
||||
charCountEnable,
|
||||
showSignInDialog,
|
||||
liveUpdates,
|
||||
commentIsIgnored,
|
||||
animateEnter,
|
||||
emit,
|
||||
commentClassNames = []
|
||||
} = this.props;
|
||||
|
||||
if (this.commentIsIgnored(comment)) {
|
||||
return <IgnoredCommentTombstone />;
|
||||
}
|
||||
|
||||
const view = this.getVisibileReplies();
|
||||
|
||||
// Inactive comments can be viewed by moderators and admins (e.g. using permalinks).
|
||||
@@ -480,16 +484,6 @@ export default class Comment extends React.Component {
|
||||
}
|
||||
</span>
|
||||
}
|
||||
{ isActive && (currentUser && (comment.user.id !== currentUser.id)) &&
|
||||
|
||||
/* TopRightMenu allows currentUser to ignore other users' comments */
|
||||
<span className={cn(styles.topRight, styles.topRightMenu)}>
|
||||
<TopRightMenu
|
||||
comment={comment}
|
||||
ignoreUser={ignoreUser}
|
||||
notify={notify} />
|
||||
</span>
|
||||
}
|
||||
{ !isActive &&
|
||||
<InactiveCommentLabel status={comment.status}/>
|
||||
}
|
||||
@@ -581,9 +575,8 @@ export default class Comment extends React.Component {
|
||||
|
||||
<TransitionGroup>
|
||||
{view.map((reply) => {
|
||||
return commentIsIgnored(reply)
|
||||
? <IgnoredCommentTombstone key={reply.id} />
|
||||
: <CommentContainer
|
||||
return (
|
||||
<CommentContainer
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
@@ -600,17 +593,16 @@ export default class Comment extends React.Component {
|
||||
postFlag={postFlag}
|
||||
deleteAction={deleteAction}
|
||||
loadMore={loadMore}
|
||||
ignoreUser={ignoreUser}
|
||||
charCountEnable={charCountEnable}
|
||||
maxCharCount={maxCharCount}
|
||||
showSignInDialog={showSignInDialog}
|
||||
commentIsIgnored={commentIsIgnored}
|
||||
liveUpdates={liveUpdates}
|
||||
reactKey={reply.id}
|
||||
key={reply.id}
|
||||
comment={reply}
|
||||
emit={emit}
|
||||
/>;
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TransitionGroup>
|
||||
<div className="talk-load-more-replies">
|
||||
|
||||
@@ -40,15 +40,6 @@ class Stream extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
commentIsIgnored = (comment) => {
|
||||
const me = this.props.root.me;
|
||||
return (
|
||||
me &&
|
||||
me.ignoredUsers &&
|
||||
me.ignoredUsers.find((u) => u.id === comment.user.id)
|
||||
);
|
||||
};
|
||||
|
||||
renderHighlightedComment() {
|
||||
const {
|
||||
data,
|
||||
@@ -64,7 +55,6 @@ class Stream extends React.Component {
|
||||
postDontAgree,
|
||||
deleteAction,
|
||||
showSignInDialog,
|
||||
ignoreUser,
|
||||
loadNewReplies,
|
||||
auth: {user},
|
||||
emit,
|
||||
@@ -90,7 +80,6 @@ class Stream extends React.Component {
|
||||
data={data}
|
||||
root={root}
|
||||
commentClassNames={commentClassNames}
|
||||
ignoreUser={ignoreUser}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
activeReplyBox={activeReplyBox}
|
||||
notify={notify}
|
||||
@@ -106,7 +95,6 @@ class Stream extends React.Component {
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
key={topLevelComment.id}
|
||||
commentIsIgnored={this.commentIsIgnored}
|
||||
comment={topLevelComment}
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount}
|
||||
@@ -133,7 +121,6 @@ class Stream extends React.Component {
|
||||
postDontAgree,
|
||||
deleteAction,
|
||||
showSignInDialog,
|
||||
ignoreUser,
|
||||
activeStreamTab,
|
||||
setActiveStreamTab,
|
||||
loadNewReplies,
|
||||
@@ -183,7 +170,6 @@ class Stream extends React.Component {
|
||||
root={root}
|
||||
comments={comments}
|
||||
commentClassNames={commentClassNames}
|
||||
ignoreUser={ignoreUser}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
activeReplyBox={activeReplyBox}
|
||||
notify={notify}
|
||||
@@ -197,7 +183,6 @@ class Stream extends React.Component {
|
||||
loadNewReplies={loadNewReplies}
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
commentIsIgnored={this.commentIsIgnored}
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount}
|
||||
editComment={editComment}
|
||||
@@ -322,9 +307,6 @@ Stream.propTypes = {
|
||||
notify: PropTypes.func.isRequired,
|
||||
postComment: PropTypes.func.isRequired,
|
||||
|
||||
// dispatch action to ignore another user
|
||||
ignoreUser: React.PropTypes.func,
|
||||
|
||||
// edit a comment, passed (id, asset_id, { body })
|
||||
editComment: React.PropTypes.func
|
||||
};
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import {IgnoreUserWizard} from './IgnoreUserWizard';
|
||||
import Toggleable from './Toggleable';
|
||||
|
||||
// TopRightMenu appears as a dropdown in the top right of the comment.
|
||||
// when you click the down cehvron, it expands and shows IgnoreUserWizard
|
||||
// when you click 'cancel' in the wizard, it closes the menu
|
||||
export class TopRightMenu extends React.Component {
|
||||
static propTypes = {
|
||||
|
||||
// comment on which this menu appears
|
||||
comment: PropTypes.shape({
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
ignoreUser: PropTypes.func,
|
||||
|
||||
// show notification to the user (e.g. for errors)
|
||||
notify: PropTypes.func.isRequired,
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
timesReset: 0
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const {comment, ignoreUser, notify} = this.props;
|
||||
|
||||
// timesReset is used as Toggleable key so it re-renders on reset (closing the toggleable)
|
||||
const reset = () => this.setState({timesReset: this.state.timesReset + 1});
|
||||
const ignoreUserAndCloseMenuAndNotifyOnError = async ({id}) => {
|
||||
|
||||
// close menu
|
||||
reset();
|
||||
|
||||
// ignore user
|
||||
try {
|
||||
await ignoreUser({id});
|
||||
} catch (error) {
|
||||
notify('error', 'Failed to ignore user');
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Toggleable key={this.state.timesReset} className="talk-stream-comment-chevron">
|
||||
<div style={{position: 'absolute', right: 0, zIndex: 1}}>
|
||||
<IgnoreUserWizard
|
||||
user={comment.user}
|
||||
cancel={reset}
|
||||
ignoreUser={ignoreUserAndCloseMenuAndNotifyOnError}
|
||||
/>
|
||||
</div>
|
||||
</Toggleable>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,13 +85,24 @@ const singleCommentFragment = gql`
|
||||
edited
|
||||
editableUntil
|
||||
}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
}
|
||||
`;
|
||||
|
||||
const withCommentFragments = withFragments({
|
||||
root: gql`
|
||||
fragment CoralEmbedStream_Comment_root on RootQuery {
|
||||
me {
|
||||
ignoredUsers {
|
||||
id
|
||||
}
|
||||
}
|
||||
__typename
|
||||
me {
|
||||
ignoredUsers {
|
||||
id
|
||||
}
|
||||
}
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
}
|
||||
`,
|
||||
@@ -116,7 +127,6 @@ const withCommentFragments = withFragments({
|
||||
endCursor
|
||||
}
|
||||
`, THREADING_LEVEL)}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
}
|
||||
${singleCommentFragment}
|
||||
`
|
||||
|
||||
@@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
|
||||
import {ADDTL_COMMENTS_ON_LOAD_MORE, THREADING_LEVEL} from '../constants/stream';
|
||||
import {
|
||||
withPostComment, withPostFlag, withPostDontAgree,
|
||||
withDeleteAction, withIgnoreUser, withEditComment
|
||||
withDeleteAction, withEditComment
|
||||
} from 'coral-framework/graphql/mutations';
|
||||
|
||||
import * as authActions from 'coral-embed-stream/src/actions/auth';
|
||||
@@ -302,7 +302,7 @@ const fragments = {
|
||||
questionBoxEnable
|
||||
questionBoxContent
|
||||
questionBoxIcon
|
||||
closeTimeout
|
||||
closedTimeout
|
||||
closedMessage
|
||||
charCountEnable
|
||||
charCount
|
||||
@@ -374,7 +374,6 @@ export default compose(
|
||||
withPostComment,
|
||||
withPostFlag,
|
||||
withPostDontAgree,
|
||||
withIgnoreUser,
|
||||
withDeleteAction,
|
||||
withEditComment,
|
||||
)(StreamContainer);
|
||||
|
||||
@@ -108,33 +108,6 @@ export default {
|
||||
`,
|
||||
},
|
||||
mutations: {
|
||||
IgnoreUser: ({variables}) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previousData) => {
|
||||
const ignoredUserId = variables.id;
|
||||
const updated = update(previousData, {me: {ignoredUsers: {$push: [{
|
||||
id: ignoredUserId,
|
||||
__typename: 'User',
|
||||
}]}}});
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
}),
|
||||
StopIgnoringUser: ({variables}) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Profile: (previousData) => {
|
||||
const noLongerIgnoredUserId = variables.id;
|
||||
|
||||
// remove noLongerIgnoredUserId from ignoredUsers
|
||||
const updated = update(previousData, {me: {ignoredUsers: {
|
||||
$apply: (ignoredUsers) => {
|
||||
return ignoredUsers.filter((u) => u.id !== noLongerIgnoredUserId);
|
||||
}
|
||||
}}});
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
}),
|
||||
PostComment: ({
|
||||
variables: {comment: {asset_id, body, parent_id, tags = []}},
|
||||
state: {auth},
|
||||
|
||||
@@ -38,7 +38,7 @@ export default class Snackbar {
|
||||
}
|
||||
|
||||
alert(message) {
|
||||
const [type, text] = message.split('|');
|
||||
const {type, text} = JSON.parse(message);
|
||||
this.el.style.transform = 'translate(-50%, 20px)';
|
||||
this.el.style.opacity = 0;
|
||||
this.el.className = `coral-notif-${type}`;
|
||||
|
||||
@@ -293,7 +293,7 @@ export const withIgnoreUser = withMutation(
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
ignoreUser: ({id}) => {
|
||||
ignoreUser: (id) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
export function createNotificationService(pym) {
|
||||
return {
|
||||
success(msg) {
|
||||
pym.sendMessage('coral-alert', `success|${msg}`);
|
||||
success(text) {
|
||||
pym.sendMessage('coral-alert', JSON.stringify({kind: 'success', text}));
|
||||
},
|
||||
error(msg) {
|
||||
pym.sendMessage('coral-alert', `error|${msg}`);
|
||||
error(text) {
|
||||
pym.sendMessage('coral-alert', JSON.stringify({kind: 'error', text}));
|
||||
},
|
||||
info(msg) {
|
||||
pym.sendMessage('coral-alert', `info|${msg}`);
|
||||
info(text) {
|
||||
pym.sendMessage('coral-alert', JSON.stringify({kind: 'info', text}));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
.ignoredUser {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.ignoredUserList {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.ignoredUser > * {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.stopListening {
|
||||
color: #D0011B;
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stopListening:before {
|
||||
content: '\00a0\00a0\00a0\00a0';
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import styles from './IgnoredUsers.css';
|
||||
|
||||
class IgnoredUsers extends Component {
|
||||
static propTypes = {
|
||||
users: PropTypes.arrayOf(PropTypes.shape({
|
||||
username: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
})).isRequired,
|
||||
|
||||
// accepts { id }
|
||||
stopIgnoring: PropTypes.func.isRequired,
|
||||
}
|
||||
render() {
|
||||
const {users, stopIgnoring} = this.props;
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
users.length
|
||||
? <p>{t('framework.because_you_ignored')}</p>
|
||||
: null
|
||||
}
|
||||
<dl className={styles.ignoredUserList}>
|
||||
{
|
||||
users.map(({username, id}) => (
|
||||
<span className={styles.ignoredUser} key={id}>
|
||||
<dt key={id}>{ username }</dt>
|
||||
<dd className={styles.stopListening}>
|
||||
<a
|
||||
onClick={() => stopIgnoring({id})}
|
||||
className={styles.link}>{t('framework.stop_ignoring')}</a>
|
||||
</dd>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default IgnoredUsers;
|
||||
@@ -3,12 +3,10 @@ import {compose, gql} from 'react-apollo';
|
||||
import React, {Component} from 'react';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {withQuery} from 'coral-framework/hocs';
|
||||
|
||||
import {withStopIgnoringUser} from 'coral-framework/graphql/mutations';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
|
||||
import {link} from 'coral-framework/services/pym';
|
||||
import NotLoggedIn from '../components/NotLoggedIn';
|
||||
import IgnoredUsers from '../components/IgnoredUsers';
|
||||
import {Spinner} from 'coral-ui';
|
||||
import CommentHistory from 'talk-plugin-history/CommentHistory';
|
||||
|
||||
@@ -55,7 +53,7 @@ class ProfileContainer extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {auth, auth: {user}, showSignInDialog, stopIgnoringUser, root, data} = this.props;
|
||||
const {auth, auth: {user}, showSignInDialog, root, data} = this.props;
|
||||
const {me} = this.props.root;
|
||||
const loading = this.props.data.loading;
|
||||
|
||||
@@ -77,15 +75,11 @@ class ProfileContainer extends Component {
|
||||
<h2>{user.username}</h2>
|
||||
{emailAddress ? <p>{emailAddress}</p> : null}
|
||||
|
||||
{me.ignoredUsers && me.ignoredUsers.length
|
||||
? <div>
|
||||
<h3>{t('framework.ignored_users')}</h3>
|
||||
<IgnoredUsers
|
||||
users={me.ignoredUsers}
|
||||
stopIgnoring={stopIgnoringUser}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
<Slot
|
||||
fill="profileSections"
|
||||
data={data}
|
||||
queryData={{root}}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -98,8 +92,10 @@ class ProfileContainer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This Slot should be included in `talk-plugin-history` instead.
|
||||
const slots = [
|
||||
'profileSections',
|
||||
|
||||
// TODO: This Slot should be included in `talk-plugin-history` instead.
|
||||
'commentContent',
|
||||
];
|
||||
|
||||
@@ -143,10 +139,6 @@ const withProfileQuery = withQuery(
|
||||
query CoralEmbedStream_Profile {
|
||||
me {
|
||||
id
|
||||
ignoredUsers {
|
||||
id,
|
||||
username,
|
||||
}
|
||||
comments(query: {limit: 10}) {
|
||||
...TalkSettings_CommentConnectionFragment
|
||||
}
|
||||
@@ -165,6 +157,5 @@ const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withStopIgnoringUser,
|
||||
withProfileQuery
|
||||
)(ProfileContainer);
|
||||
|
||||
+28
-3
@@ -25,8 +25,33 @@ const genAssetsByID = (context, ids) => AssetModel.find({
|
||||
* @param {Object} query the query
|
||||
* @return {Promise} resolves the assets
|
||||
*/
|
||||
const getAssetsByQuery = (context, query) => {
|
||||
return AssetsService.search(query);
|
||||
const getAssetsByQuery = async (context, query) => {
|
||||
|
||||
// If we are requesting based on a limit, ask for one more than we want.
|
||||
const limit = query.limit;
|
||||
if (limit) {
|
||||
query.limit += 1;
|
||||
}
|
||||
|
||||
const nodes = await AssetsService.search(query);
|
||||
|
||||
// The hasNextPage is always handled the same (ask for one more than we need,
|
||||
// if there is one more, than there is more).
|
||||
let hasNextPage = false;
|
||||
if (limit && nodes.length > limit) {
|
||||
|
||||
// There was one more than we expected! Set hasNextPage = true and remove
|
||||
// the last item from the array that we requested.
|
||||
hasNextPage = true;
|
||||
nodes.splice(limit, 1);
|
||||
}
|
||||
|
||||
return {
|
||||
startCursor: nodes && nodes.length > 0 ? nodes[0].created_at : null,
|
||||
endCursor: nodes && nodes.length > 0 ? nodes[nodes.length - 1].created_at : null,
|
||||
hasNextPage,
|
||||
nodes,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -84,7 +109,7 @@ module.exports = (context) => ({
|
||||
getByURL: (url) => findOrCreateAssetByURL(context, url),
|
||||
|
||||
findByUrl: (url) => findByUrl(context, url),
|
||||
search: (query) => getAssetsByQuery(context, query),
|
||||
getByQuery: (query) => getAssetsByQuery(context, query),
|
||||
getByID: new DataLoader((ids) => genAssetsByID(context, ids)),
|
||||
getForMetrics: () => getAssetsForMetrics(context),
|
||||
getAll: new util.SingletonResolver(() => AssetModel.find({}))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const SettingsService = require('../../services/settings');
|
||||
const util = require('./util');
|
||||
const {SingletonResolver} = require('./util');
|
||||
|
||||
/**
|
||||
* Creates a set of loaders based on a GraphQL context.
|
||||
@@ -7,5 +7,5 @@ const util = require('./util');
|
||||
* @return {Object} object of loaders
|
||||
*/
|
||||
module.exports = () => ({
|
||||
Settings: new util.SingletonResolver(() => SettingsService.retrieve())
|
||||
Settings: new SingletonResolver(() => SettingsService.retrieve())
|
||||
});
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
const errors = require('../../errors');
|
||||
const {
|
||||
UPDATE_ASSET_SETTINGS,
|
||||
UPDATE_ASSET_STATUS,
|
||||
} = require('../../perms/constants');
|
||||
|
||||
const AssetsService = require('../../services/assets');
|
||||
const AssetModel = require('../../models/asset');
|
||||
|
||||
/**
|
||||
* updateSettings will update the settings on an asset.
|
||||
*
|
||||
* @param {Object} ctx graphql context
|
||||
* @param {String} id the asset's id to update
|
||||
* @param {Object} settings the settings to update on the asset.
|
||||
*/
|
||||
const updateSettings = async (ctx, id, settings) => AssetsService.overrideSettings(id, settings);
|
||||
|
||||
/**
|
||||
* updateStatus will update the status of an asset.
|
||||
*
|
||||
* @param {Object} ctx graphql context
|
||||
* @param {String} id the asset's id to update
|
||||
* @param {Object} status the status to change on the asset relating to it's
|
||||
* current state.
|
||||
*/
|
||||
const updateStatus = async (ctx, id, {closedAt, closedMessage}) => AssetModel.update({
|
||||
id,
|
||||
}, {
|
||||
$set: {
|
||||
closedAt,
|
||||
closedMessage
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = (ctx) => {
|
||||
let mutators = {
|
||||
Asset: {
|
||||
updateSettings: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
updateStatus: () => Promise.reject(errors.ErrNotAuthorized)
|
||||
}
|
||||
};
|
||||
|
||||
if (ctx.user) {
|
||||
if (ctx.user.can(UPDATE_ASSET_SETTINGS)) {
|
||||
mutators.Asset.updateSettings = (id, settings) => updateSettings(ctx, id, settings);
|
||||
}
|
||||
|
||||
if (ctx.user.can(UPDATE_ASSET_STATUS)) {
|
||||
mutators.Asset.updateStatus = (id, status) => updateStatus(ctx, id, status);
|
||||
}
|
||||
}
|
||||
|
||||
return mutators;
|
||||
};
|
||||
@@ -3,6 +3,8 @@ const debug = require('debug')('talk:graph:mutators');
|
||||
|
||||
const Comment = require('./comment');
|
||||
const Action = require('./action');
|
||||
const Asset = require('./asset');
|
||||
const Settings = require('./settings');
|
||||
const Tag = require('./tag');
|
||||
const Token = require('./token');
|
||||
const User = require('./user');
|
||||
@@ -14,6 +16,8 @@ let mutators = [
|
||||
// Load in the core mutators.
|
||||
Comment,
|
||||
Action,
|
||||
Asset,
|
||||
Settings,
|
||||
Tag,
|
||||
Token,
|
||||
User,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
const errors = require('../../errors');
|
||||
|
||||
const {
|
||||
UPDATE_SETTINGS,
|
||||
UPDATE_WORDLIST,
|
||||
} = require('../../perms/constants');
|
||||
|
||||
const SettingsService = require('../../services/settings');
|
||||
|
||||
const update = async (ctx, settings) => SettingsService.update(settings);
|
||||
|
||||
const updateWordlist = async (ctx, wordlist) => SettingsService.updateWordlist(wordlist);
|
||||
|
||||
module.exports = (ctx) => {
|
||||
let mutators = {
|
||||
Settings: {
|
||||
update: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
updateWordlist: () => Promise.reject(errors.ErrNotAuthorized)
|
||||
}
|
||||
};
|
||||
|
||||
if (ctx.user) {
|
||||
if (ctx.user.can(UPDATE_SETTINGS)) {
|
||||
mutators.Settings.update = (id, settings) => update(ctx, id, settings);
|
||||
}
|
||||
|
||||
if (ctx.user.can(UPDATE_WORDLIST)) {
|
||||
mutators.Settings.updateWordlist = (id, status) => updateWordlist(ctx, id, status);
|
||||
}
|
||||
}
|
||||
|
||||
return mutators;
|
||||
};
|
||||
@@ -25,6 +25,12 @@ const RootMutation = {
|
||||
rejectUsername(_, {input: {id, message}}, {mutators: {User}}) {
|
||||
return wrapResponse(null)(User.rejectUsername({id, message}));
|
||||
},
|
||||
updateAssetSettings(_, {id, input: settings}, {mutators: {Asset}}) {
|
||||
return wrapResponse(null)(Asset.updateSettings(id, settings));
|
||||
},
|
||||
updateAssetStatus(_, {id, input: status}, {mutators: {Asset}}) {
|
||||
return wrapResponse(null)(Asset.updateStatus(id, status));
|
||||
},
|
||||
ignoreUser(_, {id}, {mutators: {User}}) {
|
||||
return wrapResponse(null)(User.ignoreUser({id}));
|
||||
},
|
||||
@@ -50,6 +56,12 @@ const RootMutation = {
|
||||
removeTag(_, {tag}, {mutators: {Tag}}) {
|
||||
return wrapResponse(null)(Tag.remove(tag));
|
||||
},
|
||||
updateSettings(_, {input: settings}, {mutators: {Settings}}) {
|
||||
return wrapResponse(null)(Settings.update(settings));
|
||||
},
|
||||
updateWordlist(_, {input: wordlist}, {mutators: {Settings}}) {
|
||||
return wrapResponse(null)(Settings.updateWordlist(wordlist));
|
||||
},
|
||||
createToken(_, {input}, {mutators: {Token}}) {
|
||||
return wrapResponse('token')(Token.create(input));
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ const RootQuery = {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Assets.search(query);
|
||||
return Assets.getByQuery(query);
|
||||
},
|
||||
asset(_, query, {loaders: {Assets}}) {
|
||||
if (query.id) {
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
const {
|
||||
VIEW_PROTECTED_SETTINGS,
|
||||
} = require('../../perms/constants');
|
||||
|
||||
const {decorateWithPermissionCheck} = require('./util');
|
||||
|
||||
const Settings = {};
|
||||
|
||||
// PROTECTED_SETTINGS are the settings keys that must be protected for only some
|
||||
// eyes.
|
||||
const PROTECTED_SETTINGS = {
|
||||
'premodLinksEnable': [VIEW_PROTECTED_SETTINGS],
|
||||
'autoCloseStream': [VIEW_PROTECTED_SETTINGS],
|
||||
'wordlist': [VIEW_PROTECTED_SETTINGS],
|
||||
'domains': [VIEW_PROTECTED_SETTINGS],
|
||||
};
|
||||
|
||||
// decorate the fields on the settings resolver with a permission check.
|
||||
decorateWithPermissionCheck(Settings, PROTECTED_SETTINGS);
|
||||
|
||||
module.exports = Settings;
|
||||
|
||||
+26
-1
@@ -13,6 +13,31 @@ const decorateWithTags = (typeResolver) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* decorateWithPermissionCheck will decorate the field resolver with
|
||||
* permission checks.
|
||||
*
|
||||
* @param {Object} typeResolver the type resolver
|
||||
* @param {Object} protect the object with field -> Array<String> of permissions
|
||||
*/
|
||||
const decorateWithPermissionCheck = (typeResolver, protect) => {
|
||||
for (const [field, permissions] of Object.entries(protect)) {
|
||||
let fieldResolver = (obj) => obj[field];
|
||||
if (field in typeResolver) {
|
||||
fieldResolver = typeResolver[field];
|
||||
}
|
||||
|
||||
typeResolver[field] = (obj, args, ctx, info) => {
|
||||
if (!ctx.user || !ctx.user.can(...permissions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fieldResolver(obj, args, ctx, info);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
decorateWithTags
|
||||
decorateWithTags,
|
||||
decorateWithPermissionCheck,
|
||||
};
|
||||
|
||||
+259
-15
@@ -164,7 +164,19 @@ input AssetsQuery {
|
||||
|
||||
# Limit the number of results to be returned
|
||||
limit: Int = 10
|
||||
|
||||
# open filters assets that are open/closed/all. Not providing this parameter
|
||||
# will return all the assets, true will return assets that are open, and false
|
||||
# will return assets that are closed.
|
||||
open: Boolean
|
||||
|
||||
# sortOrder specifies the order of the sort for the returned Assets.
|
||||
sortOrder: SORT_ORDER = DESC
|
||||
|
||||
# Skip results from the last created_at timestamp.
|
||||
cursor: Cursor
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Tags
|
||||
################################################################################
|
||||
@@ -572,27 +584,85 @@ enum MODERATION_MODE {
|
||||
POST
|
||||
}
|
||||
|
||||
# Site wide global settings.
|
||||
# Wordlist describes all the available wordlists.
|
||||
type Wordlist {
|
||||
|
||||
# banned words will by default reject the comment if it is found.
|
||||
banned: [String!]!
|
||||
|
||||
# suspect words will simply flag the comment.
|
||||
suspect: [String!]!
|
||||
}
|
||||
|
||||
# Domains describes all the available lists of domains.
|
||||
type Domains {
|
||||
|
||||
# whitelist is the list of domains that the embed is allowed to render on.
|
||||
whitelist: [String!]!
|
||||
}
|
||||
|
||||
# Settings stores the global settings for a given installation.
|
||||
type Settings {
|
||||
|
||||
# Moderation mode for the site.
|
||||
# moderation is the moderation mode for all Asset's on the site.
|
||||
moderation: MODERATION_MODE!
|
||||
|
||||
# Enables a requirement for email confirmation before a user can login.
|
||||
requireEmailConfirmation: Boolean
|
||||
|
||||
# infoBoxEnable will enable the Info Box content visible above the question
|
||||
# box.
|
||||
infoBoxEnable: Boolean
|
||||
|
||||
# infoBoxContent is the content of the Info Box.
|
||||
infoBoxContent: String
|
||||
premodLinksEnable: Boolean
|
||||
|
||||
# questionBoxEnable will enable the Question Box's content to be visible above
|
||||
# the comment box.
|
||||
questionBoxEnable: Boolean
|
||||
|
||||
# questionBoxContent is the content of the Question Box.
|
||||
questionBoxContent: String
|
||||
|
||||
# premodLinksEnable will put all comments that contain links into premod.
|
||||
premodLinksEnable: Boolean
|
||||
|
||||
# questionBoxIcon is the icon for the Question Box.
|
||||
questionBoxIcon: String
|
||||
closeTimeout: Int
|
||||
|
||||
# autoCloseStream when true will auto close the stream when the `closeTimeout`
|
||||
# amount of seconds have been reached.
|
||||
autoCloseStream: Boolean
|
||||
|
||||
# customCssUrl is the URL of the custom CSS used to display on the frontend.
|
||||
customCssUrl: String
|
||||
|
||||
# closedTimeout is the amount of seconds from the created_at timestamp that a
|
||||
# given asset will be considered closed.
|
||||
closedTimeout: Int
|
||||
|
||||
# closedMessage is the message shown to the user when the given Asset is
|
||||
# closed.
|
||||
closedMessage: 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
|
||||
|
||||
# charCountEnable is true when the character count restriction is enabled.
|
||||
charCountEnable: Boolean
|
||||
|
||||
# charCount is the maximum number of characters a comment may be.
|
||||
charCount: Int
|
||||
|
||||
# organizationName is the name of the organization.
|
||||
organizationName: String
|
||||
|
||||
# wordlist will return a given list of words.
|
||||
wordlist: Wordlist
|
||||
|
||||
# domains will return a given list of domains.
|
||||
domains: Domains
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -648,6 +718,22 @@ type Asset {
|
||||
author: String
|
||||
}
|
||||
|
||||
# AssetConnection represents a paginable subset of a asset list.
|
||||
type AssetConnection {
|
||||
|
||||
# Indicates that there are more assets after this subset.
|
||||
hasNextPage: Boolean!
|
||||
|
||||
# Cursor of first asset in subset.
|
||||
startCursor: Cursor
|
||||
|
||||
# Cursor of last asset in subset.
|
||||
endCursor: Cursor
|
||||
|
||||
# Subset of assets.
|
||||
nodes: [Asset!]!
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Errors
|
||||
################################################################################
|
||||
@@ -735,7 +821,7 @@ type RootQuery {
|
||||
comment(id: ID!): Comment
|
||||
|
||||
# All assets. Requires the `ADMIN` role.
|
||||
assets(query: AssetsQuery): [Asset]
|
||||
assets(query: AssetsQuery): AssetConnection
|
||||
|
||||
# Find or create an asset by url, or just find with the ID.
|
||||
asset(id: ID, url: String): Asset
|
||||
@@ -905,6 +991,56 @@ input RejectUsernameInput {
|
||||
message: String!
|
||||
}
|
||||
|
||||
# Configurable settings that can be overridden for the Asset. You must specify
|
||||
# all fields that should be updated.
|
||||
input AssetSettingsInput {
|
||||
|
||||
# premodLinksEnable will put all comments that contain links into premod.
|
||||
premodLinksEnable: Boolean
|
||||
|
||||
# moderation is the moderation mode for the asset.
|
||||
moderation: MODERATION_MODE
|
||||
|
||||
# questionBoxEnable will enable the Question Boxs' content to be visable above
|
||||
# the comment box.
|
||||
questionBoxEnable: Boolean
|
||||
|
||||
# questionBoxContent is the content of the Question Box.
|
||||
questionBoxContent: String
|
||||
|
||||
# questionBoxIcon is the icon for the Question Box.
|
||||
questionBoxIcon: String
|
||||
}
|
||||
|
||||
# UpdateAssetStatusInput contains the input to change the status of a comment as
|
||||
# it relates to being open/closed for commenting.
|
||||
input UpdateAssetStatusInput {
|
||||
|
||||
# closedAt is the time that the asset will be closed for commenting. If this
|
||||
# is null or in the future, it will be open for commenting.
|
||||
closedAt: Date
|
||||
|
||||
# closedMessage is the message to be set on the asset when it is closed. If it
|
||||
# is null, then the message will default to the globally set `closedMessage`.
|
||||
closedMessage: String
|
||||
}
|
||||
|
||||
# UpdateAssetStatusResponse is the response returned with possibly some errors
|
||||
# relating to the update status attempt.
|
||||
type UpdateAssetStatusResponse implements Response {
|
||||
|
||||
# An array of errors relating to the mutation that occurred.
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# UpdateAssetSettingsResponse is the response returned with possibly some errors
|
||||
# relating to the update settings attempt.
|
||||
type UpdateAssetSettingsResponse implements Response {
|
||||
|
||||
# An array of errors relating to the mutation that occurred.
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# DeleteActionResponse is the response returned with possibly some errors
|
||||
# relating to the delete action attempt.
|
||||
type DeleteActionResponse implements Response {
|
||||
@@ -998,6 +1134,91 @@ type EditCommentResponse implements Response {
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# UpdateSettingsInput is the input used to input the global site settings. This
|
||||
# will override the existing settings, so all fields must be included.
|
||||
input UpdateSettingsInput {
|
||||
|
||||
# moderation is the moderation mode for all Asset's on the site.
|
||||
moderation: MODERATION_MODE
|
||||
|
||||
# Enables a requirement for email confirmation before a user can login.
|
||||
requireEmailConfirmation: Boolean
|
||||
|
||||
# infoBoxEnable will enable the Info Box content visible above the question
|
||||
# box.
|
||||
infoBoxEnable: Boolean
|
||||
|
||||
# infoBoxContent is the content of the Info Box.
|
||||
infoBoxContent: String
|
||||
|
||||
# questionBoxEnable will enable the Question Box's content to be visible above
|
||||
# the comment box.
|
||||
questionBoxEnable: Boolean
|
||||
|
||||
# questionBoxContent is the content of the Question Box.
|
||||
questionBoxContent: String
|
||||
|
||||
# premodLinksEnable will put all comments that contain links into premod.
|
||||
premodLinksEnable: Boolean
|
||||
|
||||
# questionBoxIcon is the icon for the Question Box.
|
||||
questionBoxIcon: String
|
||||
|
||||
# autoCloseStream when true will auto close the stream when the `closeTimeout`
|
||||
# amount of seconds have been reached.
|
||||
autoCloseStream: Boolean
|
||||
|
||||
# customCssUrl is the URL of the custom CSS used to display on the frontend.
|
||||
customCssUrl: String
|
||||
|
||||
# closedTimeout is the amount of seconds from the created_at timestamp that a
|
||||
# given asset will be considered closed.
|
||||
closedTimeout: Int
|
||||
|
||||
# closedMessage is the message shown to the user when the given Asset is
|
||||
# closed.
|
||||
closedMessage: String
|
||||
|
||||
# charCountEnable is true when the character count restriction is enabled.
|
||||
charCountEnable: Boolean
|
||||
|
||||
# charCount is the maximum number of characters a comment may be.
|
||||
charCount: Int
|
||||
|
||||
# organizationName is the name of the organization.
|
||||
organizationName: 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
|
||||
}
|
||||
|
||||
# UpdateSettingsResponse contains any errors that were rendered as a result
|
||||
# of the mutation.
|
||||
type UpdateSettingsResponse implements Response {
|
||||
|
||||
# An array of errors relating to the mutation that occurred.
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# UpdateWordlistInput is the list of words that composes the Wordlist.
|
||||
input UpdateWordlistInput {
|
||||
|
||||
# banned words will by default reject the comment if it is found.
|
||||
banned: [String!]!
|
||||
|
||||
# suspect words will simply flag the comment.
|
||||
suspect: [String!]!
|
||||
}
|
||||
|
||||
# UpdateWordlistResponse contains any errors that were rendered as a result
|
||||
# of the mutation.
|
||||
type UpdateWordlistResponse implements Response {
|
||||
|
||||
# An array of errors relating to the mutation that occurred.
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# CreateTokenInput contains the input to create the token.
|
||||
input CreateTokenInput {
|
||||
|
||||
@@ -1033,31 +1254,35 @@ type RevokeTokenResponse implements Response {
|
||||
type RootMutation {
|
||||
|
||||
# Creates a comment on the asset.
|
||||
createComment(comment: CreateCommentInput!): CreateCommentResponse
|
||||
createComment(comment: CreateCommentInput!): CreateCommentResponse!
|
||||
|
||||
# Creates a flag on an entity.
|
||||
createFlag(flag: CreateFlagInput!): CreateFlagResponse
|
||||
createFlag(flag: CreateFlagInput!): CreateFlagResponse!
|
||||
|
||||
# Creates a don't agree action on an entity.
|
||||
createDontAgree(dontagree: CreateDontAgreeInput!): CreateDontAgreeResponse
|
||||
createDontAgree(dontagree: CreateDontAgreeInput!): CreateDontAgreeResponse!
|
||||
|
||||
# Delete an action based on the action id.
|
||||
deleteAction(id: ID!): DeleteActionResponse
|
||||
|
||||
# Edit a comment
|
||||
editComment(id: ID!, asset_id: ID!, edit: EditCommentInput): EditCommentResponse
|
||||
editComment(id: ID!, asset_id: ID!, edit: EditCommentInput): EditCommentResponse!
|
||||
|
||||
# Sets User status. Requires the `ADMIN` role.
|
||||
setUserStatus(id: ID!, status: USER_STATUS!): SetUserStatusResponse
|
||||
# Mutation is restricted.
|
||||
setUserStatus(id: ID!, status: USER_STATUS!): SetUserStatusResponse!
|
||||
|
||||
# Suspends a user. Requires the `ADMIN` role.
|
||||
suspendUser(input: SuspendUserInput!): SuspendUserResponse
|
||||
# Mutation is restricted.
|
||||
suspendUser(input: SuspendUserInput!): SuspendUserResponse!
|
||||
|
||||
# Reject a username. Requires the `ADMIN` role.
|
||||
rejectUsername(input: RejectUsernameInput!): RejectUsernameResponse
|
||||
# Mutation is restricted.
|
||||
rejectUsername(input: RejectUsernameInput!): RejectUsernameResponse!
|
||||
|
||||
# Sets Comment status. Requires the `ADMIN` role.
|
||||
setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse
|
||||
# Mutation is restricted.
|
||||
setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse!
|
||||
|
||||
# Add a tag.
|
||||
addTag(tag: ModifyTagInput!): ModifyTagResponse!
|
||||
@@ -1065,17 +1290,36 @@ type RootMutation {
|
||||
# Removes a tag.
|
||||
removeTag(tag: ModifyTagInput!): ModifyTagResponse!
|
||||
|
||||
# Updates settings on a given asset.
|
||||
# Mutation is restricted.
|
||||
updateAssetSettings(id: ID!, input: AssetSettingsInput!): UpdateAssetSettingsResponse!
|
||||
|
||||
# Updates the status of an asset allowing you to close/reopen an asset for
|
||||
# commenting.
|
||||
# Mutation is restricted.
|
||||
updateAssetStatus(id: ID!, input: UpdateAssetStatusInput!): UpdateAssetStatusResponse!
|
||||
|
||||
# updateSettings will update the global settings.
|
||||
# Mutation is restricted.
|
||||
updateSettings(input: UpdateSettingsInput!): UpdateSettingsResponse!
|
||||
|
||||
# updateWordlist will update the given Wordlist.
|
||||
# Mutation is restricted.
|
||||
updateWordlist(input: UpdateWordlistInput!): UpdateWordlistResponse!
|
||||
|
||||
# Ignore comments by another user
|
||||
ignoreUser(id: ID!): IgnoreUserResponse
|
||||
|
||||
# CreateToken will create a token that is attached to the current user.
|
||||
# Mutation is restricted.
|
||||
createToken(input: CreateTokenInput!): CreateTokenResponse!
|
||||
|
||||
# RevokeToken will revoke an existing token.
|
||||
# Mutation is restricted.
|
||||
revokeToken(input: RevokeTokenInput!): RevokeTokenResponse!
|
||||
|
||||
# Stop Ignoring comments by another user
|
||||
stopIgnoringUser(id: ID!): StopIgnoringUserResponse
|
||||
# Stop Ignoring comments by another user.
|
||||
stopIgnoringUser(id: ID!): StopIgnoringUserResponse!
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
@@ -219,7 +219,6 @@ en:
|
||||
framework:
|
||||
banned_account_header: "Your account is currently banned."
|
||||
banned_account_body: "This means that you cannot Like, Report, or write comments."
|
||||
because_you_ignored: "Because you ignored the following commenters, their comments are hidden."
|
||||
comment: comment
|
||||
comment_is_ignored: "This comment is hidden because you ignored this user."
|
||||
comments: comments
|
||||
@@ -230,13 +229,11 @@ en:
|
||||
error: "Usernames can contain letters numbers and _ only"
|
||||
label: "New Username"
|
||||
msg: "Your account is currently suspended because your username has been deemed inappropriate. To restore your account please enter a new username. Please contact us if you have any questions."
|
||||
ignored_users: "Ignored users"
|
||||
my_comments: "My Comments"
|
||||
my_profile: "My profile"
|
||||
new_count: "View {0} new {1}"
|
||||
profile: Profile
|
||||
show_all_comments: "Show all comments"
|
||||
stop_ignoring: "Stop ignoring"
|
||||
success_bio_update: "Your biography has been updated"
|
||||
success_name_update: "Your username has been updated"
|
||||
success_update_settings: "The changes you have made have been applied to the comment stream on this article"
|
||||
|
||||
@@ -227,13 +227,11 @@ es:
|
||||
error: "Nombres de usuarios pueden solamente incluir letras, números y _"
|
||||
label: "Nuevo Nombre"
|
||||
msg: "Tu cuenta está suspendida porque tu nombre de usuario ha sido considerado no apropiado para el espacio. Para recuperar la cuenta, por favor ingresar un nuevo nombre de usuario. Contáctanos si tienes alguna pregunta."
|
||||
ignored_users: "Usuarios ignorados"
|
||||
my_comments: "Mis Comentarios"
|
||||
my_profile: "Mi perfil"
|
||||
new_count: "Ver {0} {1} nuevo"
|
||||
profile: Perfil
|
||||
show_all_comments: "Mostrar todos los comentarios"
|
||||
stop_ignoring: "No ignorar más"
|
||||
success_bio_update: "Tu biografia fue actualizada"
|
||||
success_name_update: "Tu nombre de usuario ha sido actualizado"
|
||||
success_update_settings: "La configuración de este articulo fue actualizada"
|
||||
|
||||
@@ -188,7 +188,6 @@ fr:
|
||||
error: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\""
|
||||
label: "Nouveau nom d'utilisateur"
|
||||
msg: "Votre compte est actuellement suspendu car votre nom d'utilisateur a été jugé inapproprié. Pour restaurer votre compte, entrez un nouveau nom d'utilisateur. Contactez-nous si vous avez des questions."
|
||||
ignored_users: "Utilisateurs ignorés"
|
||||
my_comments: "Mes commentaires"
|
||||
my_profile: "Mon profil"
|
||||
new_count: "Voir {0} nouveau {1}"
|
||||
|
||||
@@ -214,7 +214,6 @@ pt_BR:
|
||||
framework:
|
||||
banned_account_header: "Sua conta está atualmente proibida."
|
||||
banned_account_body: "Isso significa que você não pode gostar, informar ou escrever comentários."
|
||||
because_you_ignored: "Porque você ignorou os seguintes comentadores, seus comentários estão ocultos."
|
||||
comment: comentário
|
||||
comment_is_ignored: "Este comentário está oculto porque você ignorou esse usuário."
|
||||
comments: comentários
|
||||
@@ -225,13 +224,11 @@ pt_BR:
|
||||
error: "Nomes de usuários podem conter números de letras e _ somente"
|
||||
label: "Novo usuário"
|
||||
msg: "Sua conta está suspensa porque seu nome de usuário foi considerado inapropriado. Para restaurar sua conta, insira um novo nome de usuário. Entre em contato conosco se você tiver alguma dúvida."
|
||||
ignored_users: "Usuários ignorados"
|
||||
my_comments: "Meus comentários"
|
||||
my_profile: "Meu perfil"
|
||||
new_count: "Ver {0} {1}"
|
||||
profile: Perfil
|
||||
show_all_comments: "Exibir todos os comentários"
|
||||
stop_ignoring: "Pare de ignorar"
|
||||
success_bio_update: "Sua biografia foi atualizada"
|
||||
success_name_update: "Seu nome de usuário foi atualizado"
|
||||
success_update_settings: "As alterações que você fez foram aplicadas no hilo de comentários neste artigo"
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
"material-design-lite": "^1.2.1",
|
||||
"metascraper": "^1.0.7",
|
||||
"minimist": "^1.2.0",
|
||||
"moment": "^2.18.1",
|
||||
"mongoose": "^4.11.7",
|
||||
"morgan": "^1.8.2",
|
||||
"ms": "^2.0.0",
|
||||
|
||||
@@ -16,6 +16,10 @@ module.exports = {
|
||||
UPDATE_CONFIG: 'UPDATE_CONFIG',
|
||||
CREATE_TOKEN: 'CREATE_TOKEN',
|
||||
REVOKE_TOKEN: 'REVOKE_TOKEN',
|
||||
UPDATE_ASSET_SETTINGS: 'UPDATE_ASSET_SETTINGS',
|
||||
UPDATE_ASSET_STATUS: 'UPDATE_ASSET_STATUS',
|
||||
UPDATE_SETTINGS: 'UPDATE_SETTINGS',
|
||||
UPDATE_WORDLIST: 'UPDATE_WORDLIST',
|
||||
|
||||
// queries
|
||||
SEARCH_ASSETS: 'SEARCH_ASSETS',
|
||||
@@ -27,6 +31,7 @@ module.exports = {
|
||||
LIST_OWN_TOKENS: 'LIST_OWN_TOKENS',
|
||||
SEARCH_COMMENT_STATUS_HISTORY: 'SEARCH_COMMENT_STATUS_HISTORY',
|
||||
VIEW_SUSPENSION_INFO: 'VIEW_SUSPENSION_INFO',
|
||||
VIEW_PROTECTED_SETTINGS: 'VIEW_PROTECTED_SETTINGS',
|
||||
|
||||
// subscriptions
|
||||
SUBSCRIBE_COMMENT_ACCEPTED: 'SUBSCRIBE_COMMENT_ACCEPTED',
|
||||
|
||||
+2
-5
@@ -41,11 +41,8 @@ const findGrant = (user, perms) => {
|
||||
*/
|
||||
module.exports = (user, ...perms) => {
|
||||
|
||||
// make sure all the passed permissions are not typos
|
||||
const missingPerms = perms.filter((perm) => {
|
||||
return allPermissions.indexOf(perm) === -1;
|
||||
});
|
||||
|
||||
// Make sure all the passed permissions are not typos.
|
||||
const missingPerms = perms.filter((perm) => !allPermissions.includes(perm));
|
||||
if (missingPerms.length > 0) {
|
||||
throw new Error(`${missingPerms.join(' ')} are not valid permissions.`);
|
||||
}
|
||||
|
||||
@@ -4,33 +4,26 @@ const types = require('./constants');
|
||||
module.exports = (user, perm) => {
|
||||
switch (perm) {
|
||||
case types.CREATE_COMMENT:
|
||||
return true;
|
||||
case types.CREATE_ACTION:
|
||||
return true;
|
||||
case types.DELETE_ACTION:
|
||||
return true;
|
||||
case types.EDIT_NAME:
|
||||
return true;
|
||||
case types.EDIT_COMMENT:
|
||||
return true;
|
||||
case types.UPDATE_USER_ROLES:
|
||||
return check(user, ['ADMIN']);
|
||||
case types.REJECT_USERNAME:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
case types.SET_USER_STATUS:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
case types.SUSPEND_USER:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
case types.SET_COMMENT_STATUS:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
case types.ADD_COMMENT_TAG:
|
||||
return check(user, ['ADMIN', 'MODERATOR', 'STAFF']);
|
||||
case types.REMOVE_COMMENT_TAG:
|
||||
return check(user, ['ADMIN', 'MODERATOR', 'STAFF']);
|
||||
case types.UPDATE_USER_ROLES:
|
||||
case types.REJECT_USERNAME:
|
||||
case types.SET_USER_STATUS:
|
||||
case types.SUSPEND_USER:
|
||||
case types.SET_COMMENT_STATUS:
|
||||
case types.UPDATE_CONFIG:
|
||||
case types.UPDATE_SETTINGS:
|
||||
case types.UPDATE_WORDLIST:
|
||||
case types.UPDATE_ASSET_SETTINGS:
|
||||
case types.UPDATE_ASSET_STATUS:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
case types.CREATE_TOKEN:
|
||||
return check(user, ['ADMIN']);
|
||||
case types.REVOKE_TOKEN:
|
||||
return check(user, ['ADMIN']);
|
||||
default:
|
||||
|
||||
@@ -21,6 +21,8 @@ module.exports = (user, perm) => {
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
case types.VIEW_SUSPENSION_INFO:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
case types.VIEW_PROTECTED_SETTINGS:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export {addNotification} from 'coral-framework/actions/notification';
|
||||
export {notify} from 'coral-framework/actions/notification';
|
||||
|
||||
@@ -5,3 +5,7 @@ export {default as withFragments} from 'coral-framework/hocs/withFragments';
|
||||
export {default as excludeIf} from 'coral-framework/hocs/excludeIf';
|
||||
export {default as connect} from 'coral-framework/hocs/connect';
|
||||
export {default as withEmit} from 'coral-framework/hocs/withEmit';
|
||||
export {
|
||||
withIgnoreUser,
|
||||
withStopIgnoringUser,
|
||||
} from 'coral-framework/graphql/mutations';
|
||||
|
||||
@@ -7,4 +7,5 @@ export {
|
||||
capitalize,
|
||||
getErrorMessages,
|
||||
getDefinitionName,
|
||||
getShallowChanges,
|
||||
} from 'coral-framework/utils';
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
"talk-plugin-sort-newest",
|
||||
"talk-plugin-sort-oldest",
|
||||
"talk-plugin-sort-most-respected",
|
||||
"talk-plugin-sort-most-replied"
|
||||
"talk-plugin-sort-most-replied",
|
||||
"talk-plugin-author-menu",
|
||||
"talk-plugin-member-since",
|
||||
"talk-plugin-ignore-user"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
"add-module-exports",
|
||||
"transform-class-properties",
|
||||
"transform-decorators-legacy",
|
||||
"transform-object-assign",
|
||||
"transform-object-rest-spread",
|
||||
"transform-async-to-generator",
|
||||
"transform-react-jsx"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import {SET_CONTENT_SLOT, RESET_CONTENT_SLOT, OPEN_MENU, CLOSE_MENU} from './constants';
|
||||
|
||||
export const setContentSlot = (slot) => ({
|
||||
type: SET_CONTENT_SLOT,
|
||||
slot,
|
||||
});
|
||||
|
||||
export const resetContentSlot = () => ({
|
||||
type: RESET_CONTENT_SLOT,
|
||||
});
|
||||
|
||||
export const openMenu = (id) => ({
|
||||
type: OPEN_MENU,
|
||||
id,
|
||||
});
|
||||
|
||||
export const closeMenu = () => ({
|
||||
type: CLOSE_MENU,
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
.root {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: buttonReset from "coral-framework/styles/reset.css";
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import Menu from './Menu';
|
||||
import styles from './AuthorName.css';
|
||||
import {ClickOutside} from 'plugin-api/beta/client/components';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({data, root, asset, comment, contentSlot, menuVisible, toggleMenu, hideMenu}) => {
|
||||
return (
|
||||
<ClickOutside onClickOutside={hideMenu}>
|
||||
<div className={cn(styles.root, 'talk-plugin-author-menu')}>
|
||||
<button
|
||||
className={cn(styles.button, 'talk-plugin-author-menu-button')}
|
||||
onClick={toggleMenu}
|
||||
>
|
||||
<span className={styles.name}>
|
||||
{comment.user.username}
|
||||
</span>
|
||||
</button>
|
||||
{menuVisible &&
|
||||
<Menu
|
||||
data={data}
|
||||
root={root}
|
||||
asset={asset}
|
||||
comment={comment}
|
||||
contentSlot={contentSlot}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
.menu {
|
||||
background-color: white;
|
||||
border: solid 1px #999;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
-webkit-box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2);
|
||||
z-index: 9;
|
||||
top: 26px;
|
||||
left: 0px;
|
||||
min-width: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu::before{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: #999;
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: -21px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.menu::after{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: white;
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: -20px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import styles from './Menu.css';
|
||||
import {Slot} from 'plugin-api/beta/client/components';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({data, root, asset, comment, contentSlot}) => {
|
||||
if (contentSlot) {
|
||||
return (
|
||||
<div className={cn(styles.menu, 'talk-plugin-author-menu-popup')}>
|
||||
<Slot
|
||||
fill={contentSlot}
|
||||
data={data}
|
||||
queryData={{asset, root, comment}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(styles.menu, 'talk-plugin-author-menu-popup')}>
|
||||
<Slot
|
||||
className={cn('talk-plugin-author-menu-infos')}
|
||||
fill={'authorMenuInfos'}
|
||||
data={data}
|
||||
queryData={{asset, root, comment}}
|
||||
/>
|
||||
<Slot
|
||||
className={cn(styles.actions, 'talk-plugin-author-menu-actions')}
|
||||
fill={'authorMenuActions'}
|
||||
data={data}
|
||||
queryData={{asset, root, comment}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
const prefix = 'TALK_AUTHOR_MENU';
|
||||
|
||||
export const SET_CONTENT_SLOT = `${prefix}_SET_CONTENT_SLOT`;
|
||||
export const RESET_CONTENT_SLOT = `${prefix}_RESET_CONTENT_SLOT`;
|
||||
export const OPEN_MENU = `${prefix}_OPEN_MENU`;
|
||||
export const CLOSE_MENU = `${prefix}_CLOSE_MENU`;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import React from 'react';
|
||||
import {connect, withFragments} from 'plugin-api/beta/client/hocs';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import AuthorName from '../components/AuthorName';
|
||||
import {setContentSlot, resetContentSlot, openMenu, closeMenu} from '../actions';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {getSlotFragmentSpreads, getShallowChanges} from 'plugin-api/beta/client/utils';
|
||||
|
||||
class AuthorNameContainer extends React.Component {
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
|
||||
// Specifically handle `showMenuForComment` if it is the only change.
|
||||
const changes = getShallowChanges(this.props, nextProps);
|
||||
if (changes.length === 1 && changes[0] === 'showMenuForComment') {
|
||||
const commentId = this.props.comment.id;
|
||||
if (
|
||||
commentId !== this.props.showMenuForComment &&
|
||||
commentId !== nextProps.showMenuForComment
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
|
||||
toggleMenu = () => {
|
||||
if (this.props.showMenuForComment === this.props.comment.id) {
|
||||
this.props.closeMenu();
|
||||
} else {
|
||||
this.props.openMenu(this.props.comment.id);
|
||||
}
|
||||
}
|
||||
|
||||
hideMenu = () => {
|
||||
if (this.props.showMenuForComment === this.props.comment.id) {
|
||||
this.props.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <AuthorName
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
asset={this.props.asset}
|
||||
comment={this.props.comment}
|
||||
contentSlot={this.props.contentSlot}
|
||||
menuVisible={this.props.showMenuForComment === this.props.comment.id}
|
||||
toggleMenu={this.toggleMenu}
|
||||
hideMenu={this.hideMenu}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
const slots = [
|
||||
'authorMenuInfos',
|
||||
'authorMenuActions',
|
||||
];
|
||||
|
||||
const mapStateToProps = ({talkPluginAuthorMenu: state}) => ({
|
||||
contentSlot: state.contentSlot,
|
||||
showMenuForComment: state.showMenuForComment,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
setContentSlot,
|
||||
resetContentSlot,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
}, dispatch);
|
||||
|
||||
const withAuthorNameFragments = withFragments({
|
||||
root: gql`
|
||||
fragment TalkAuthorMenu_AuthorName_root on RootQuery {
|
||||
__typename
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
}`,
|
||||
asset: gql`
|
||||
fragment TalkAuthorMenu_AuthorName_asset on Asset {
|
||||
__typename
|
||||
${getSlotFragmentSpreads(slots, 'asset')}
|
||||
}`,
|
||||
comment: gql`
|
||||
fragment TalkAuthorMenu_AuthorName_comment on Comment {
|
||||
__typename
|
||||
id
|
||||
user {
|
||||
username
|
||||
}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
}`,
|
||||
});
|
||||
|
||||
const enhance = compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withAuthorNameFragments,
|
||||
);
|
||||
|
||||
export default enhance(AuthorNameContainer);
|
||||
@@ -0,0 +1,11 @@
|
||||
import AuthorName from './containers/AuthorName';
|
||||
import reducer from './reducer';
|
||||
import translations from './translations.yml';
|
||||
|
||||
export default {
|
||||
reducer,
|
||||
slots: {
|
||||
commentAuthorName: [AuthorName]
|
||||
},
|
||||
translations
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import {SET_CONTENT_SLOT, RESET_CONTENT_SLOT, OPEN_MENU, CLOSE_MENU} from './constants';
|
||||
|
||||
const initialState = {
|
||||
contentSlot: null,
|
||||
showMenuForComment: null,
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case SET_CONTENT_SLOT:
|
||||
return {
|
||||
...state,
|
||||
contentSlot: action.slot,
|
||||
};
|
||||
case RESET_CONTENT_SLOT:
|
||||
return {
|
||||
...state,
|
||||
contentSlot: initialState.contentSlot,
|
||||
};
|
||||
case OPEN_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: action.id,
|
||||
};
|
||||
case CLOSE_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: null,
|
||||
contentSlot: null,
|
||||
};
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
en:
|
||||
talk-plugin-author-menu:
|
||||
es:
|
||||
talk-plugin-author-menu:
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
"add-module-exports",
|
||||
"transform-class-properties",
|
||||
"transform-decorators-legacy",
|
||||
"transform-object-assign",
|
||||
"transform-object-rest-spread",
|
||||
"transform-async-to-generator",
|
||||
"transform-react-jsx"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.root {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: buttonReset from "coral-framework/styles/reset.css";
|
||||
border: 1px solid rgba(1, 1, 1, 0.8);
|
||||
border-radius: 1px;
|
||||
padding: 3px 6px;
|
||||
font-size: 12px;
|
||||
transition: color 100ms, background 100ms;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(1, 1, 1, 0.8);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import styles from './IgnoreUserAction.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({ignoreUser}) => (
|
||||
<button
|
||||
className={cn(styles.button, 'talk-plugin-ignore-user-action')}
|
||||
onClick={ignoreUser}>
|
||||
{t('talk-plugin-ignore-user.ignore_user')}
|
||||
</button>
|
||||
);
|
||||
@@ -0,0 +1,45 @@
|
||||
.root {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 0;
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: buttonReset from "coral-framework/styles/reset.css";
|
||||
border: 1px solid rgba(1, 1, 1, 0.8);
|
||||
border-radius: 1px;
|
||||
padding: 3px 6px;
|
||||
font-size: 12px;
|
||||
transition: color 100ms, background 100ms;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(1, 1, 1, 0.8);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel {
|
||||
composes: buttonReset from "coral-framework/styles/reset.css";
|
||||
padding: 3px 6px;
|
||||
font-size: 12px;
|
||||
transition: color 100ms;
|
||||
margin-right: 4px;
|
||||
|
||||
&:hover {
|
||||
color: rgba(1, 1, 1, 0.75);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import styles from './IgnoreUserConfirmation.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({ignoreUser, cancel, username}) => (
|
||||
<aside className={cn(styles.root, 'talk-plugin-ignore-user-confirmation')}>
|
||||
<div className={styles.message}>
|
||||
<h1 className={styles.title}>
|
||||
{t('talk-plugin-ignore-user.confirmation_title', username)}
|
||||
</h1>
|
||||
{t('talk-plugin-ignore-user.confirmation')}
|
||||
</div>
|
||||
<div className={cn(styles.actions, 'talk-plugin-ignore-user-confirmation-actions')}>
|
||||
<button className={cn(styles.cancel, 'talk-plugin-ignore-user-confirmation-cancel')} onClick={cancel}>
|
||||
{t('talk-plugin-ignore-user.cancel')}
|
||||
</button>
|
||||
<button className={cn(styles.button, 'talk-plugin-ignore-user-confirmation-button')} onClick={ignoreUser}>
|
||||
{t('talk-plugin-ignore-user.ignore_user')}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
.list {
|
||||
display: table;
|
||||
list-style-type: none;
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.username {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: buttonReset from "coral-framework/styles/reset.css";
|
||||
|
||||
margin-left: 16px;
|
||||
color: #D0011B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import styles from './IgnoredUserSection.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
|
||||
export default ({ignoredUsers, stopIgnoringUser}) => (
|
||||
<section className={'talk-plugin-ignore-user-section'}>
|
||||
<h3>{t('talk-plugin-ignore-user.section_title')}</h3>
|
||||
<p>{t('talk-plugin-ignore-user.section_info')}</p>
|
||||
<ul className={styles.list}>
|
||||
{ignoredUsers.map(({username, id}) => (
|
||||
<li className={styles.listItem} key={id}>
|
||||
<span className={styles.username}>{username}</span>
|
||||
<button
|
||||
onClick={() => stopIgnoringUser({id})}
|
||||
className={styles.button}
|
||||
>
|
||||
{t('talk-plugin-ignore-user.stop_ignoring')}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import IgnoreUserAction from '../components/IgnoreUserAction';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {connect, withFragments, excludeIf} from 'plugin-api/beta/client/hocs';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {setContentSlot} from 'plugins/talk-plugin-author-menu/client/actions';
|
||||
import IgnoreUserConfirmation from './IgnoreUserConfirmation';
|
||||
import {getDefinitionName} from 'plugin-api/beta/client/utils';
|
||||
|
||||
class IgnoreUserActionContainer extends React.Component {
|
||||
|
||||
ignoreUser = () => {
|
||||
this.props.setContentSlot('ignoreUserConfirmation');
|
||||
};
|
||||
|
||||
render() {
|
||||
return <IgnoreUserAction
|
||||
ignoreUser={this.ignoreUser}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
setContentSlot,
|
||||
}, dispatch);
|
||||
|
||||
const withIgnoreUserActionFragments = withFragments({
|
||||
root: gql`
|
||||
fragment TalkIgnoreUser_IgnoreUserAction_root on RootQuery {
|
||||
me {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment TalkIgnoreUser_IgnoreUserAction_comment on Comment {
|
||||
user {
|
||||
id
|
||||
}
|
||||
...${getDefinitionName(IgnoreUserConfirmation.fragments.comment)}
|
||||
}
|
||||
${IgnoreUserConfirmation.fragments.comment}
|
||||
`,
|
||||
});
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
withIgnoreUserActionFragments,
|
||||
excludeIf(({root: {me}, comment}) => !me || me.id === comment.user.id),
|
||||
);
|
||||
|
||||
export default enhance(IgnoreUserActionContainer);
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import IgnoreUserConfirmation from '../components/IgnoreUserConfirmation';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {connect, withFragments, withIgnoreUser} from 'plugin-api/beta/client/hocs';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {closeMenu} from 'plugins/talk-plugin-author-menu/client/actions';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {getErrorMessages} from 'plugin-api/beta/client/utils';
|
||||
|
||||
class IgnoreUserConfirmationContainer extends React.Component {
|
||||
|
||||
ignoreUser = () => {
|
||||
const {ignoreUser, notify, comment, closeMenu} = this.props;
|
||||
ignoreUser(comment.user.id)
|
||||
.then(() => {
|
||||
notify('success', t('talk-plugin-ignore-user.notify_success', comment.user.username));
|
||||
})
|
||||
.catch((err) => {
|
||||
notify('error', getErrorMessages(err));
|
||||
});
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
cancel = () => {
|
||||
this.props.closeMenu();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <IgnoreUserConfirmation
|
||||
username={this.props.comment.user.username}
|
||||
ignoreUser={this.ignoreUser}
|
||||
cancel={this.cancel}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
closeMenu,
|
||||
notify,
|
||||
}, dispatch);
|
||||
|
||||
const withIgnoreUserConfirmationFragments = withFragments({
|
||||
comment: gql`
|
||||
fragment TalkIgnoreUser_IgnoreUserConfirmation_comment on Comment {
|
||||
user {
|
||||
id
|
||||
username
|
||||
}
|
||||
}`,
|
||||
});
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
withIgnoreUserConfirmationFragments,
|
||||
withIgnoreUser,
|
||||
);
|
||||
|
||||
export default enhance(IgnoreUserConfirmationContainer);
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import IgnoredUserSection from '../components/IgnoredUserSection';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {withFragments, excludeIf, withStopIgnoringUser} from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class IgnoredUserSectionContainer extends React.Component {
|
||||
|
||||
render() {
|
||||
return <IgnoredUserSection
|
||||
stopIgnoringUser={this.props.stopIgnoringUser}
|
||||
ignoredUsers={this.props.root.me.ignoredUsers}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
const withIgnoredUserSectionFragments = withFragments({
|
||||
root: gql`
|
||||
fragment TalkIgnoreUser_IgnoredUserSection_root on RootQuery {
|
||||
me {
|
||||
id
|
||||
ignoredUsers {
|
||||
id,
|
||||
username,
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
const enhance = compose(
|
||||
withIgnoredUserSectionFragments,
|
||||
withStopIgnoringUser,
|
||||
excludeIf(({root: {me}}) => me.ignoredUsers.length === 0),
|
||||
);
|
||||
|
||||
export default enhance(IgnoredUserSectionContainer);
|
||||
@@ -0,0 +1,43 @@
|
||||
import IgnoreUserAction from './containers/IgnoreUserAction';
|
||||
import IgnoreUserConfirmation from './containers/IgnoreUserConfirmation';
|
||||
import IgnoredUserSection from './containers/IgnoredUserSection';
|
||||
import translations from './translations.yml';
|
||||
import update from 'immutability-helper';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
authorMenuActions: [IgnoreUserAction],
|
||||
ignoreUserConfirmation: [IgnoreUserConfirmation],
|
||||
profileSections: [IgnoredUserSection],
|
||||
},
|
||||
translations,
|
||||
mutations: {
|
||||
IgnoreUser: ({variables}) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previousData) => {
|
||||
const ignoredUserId = variables.id;
|
||||
const updated = update(previousData, {me: {ignoredUsers: {$push: [{
|
||||
id: ignoredUserId,
|
||||
__typename: 'User',
|
||||
}]}}});
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
}),
|
||||
StopIgnoringUser: ({variables}) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Profile: (previousData) => {
|
||||
const noLongerIgnoredUserId = variables.id;
|
||||
|
||||
// remove noLongerIgnoredUserId from ignoredUsers
|
||||
const updated = update(previousData, {me: {ignoredUsers: {
|
||||
$apply: (ignoredUsers) => {
|
||||
return ignoredUsers.filter((u) => u.id !== noLongerIgnoredUserId);
|
||||
}
|
||||
}}});
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
en:
|
||||
talk-plugin-ignore-user:
|
||||
section_title: Ignored users
|
||||
section_info: Because you ignored the following commenters, their comments are hidden.
|
||||
stop_ignoring: Stop ignoring
|
||||
ignore_user: Ignore User
|
||||
cancel: Cancel
|
||||
confirmation: |
|
||||
When you ignore a user, all comments they wrote on the site will be hidden from you. You can
|
||||
undo this later from My Profile.
|
||||
notify_success: |
|
||||
You are now ignoring {0}. You can undo this action from My Profile.
|
||||
confirmation_title: Ignore {0}?
|
||||
es:
|
||||
talk-plugin-ignore-user:
|
||||
section_title: "Usuarios ignorados"
|
||||
stop_ignoring: "No ignorar más"
|
||||
fr:
|
||||
talk-plugin-ignore-user:
|
||||
section_title: "Utilisateurs ignorés"
|
||||
pt_Br:
|
||||
talk-plugin-ignore-user:
|
||||
section_title: "Usuários ignorados"
|
||||
section_info: "Porque você ignorou os seguintes comentadores, seus comentários estão ocultos."
|
||||
stop_ignoring: "Pare de ignorar"
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -5,5 +5,5 @@ export default {
|
||||
translations,
|
||||
slots: {
|
||||
commentReactions: [LikeButton]
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
"add-module-exports",
|
||||
"transform-class-properties",
|
||||
"transform-decorators-legacy",
|
||||
"transform-object-assign",
|
||||
"transform-object-rest-spread",
|
||||
"transform-async-to-generator",
|
||||
"transform-react-jsx"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.root {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.root:not(:last-child) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.memberSince {
|
||||
margin-left: 4px;
|
||||
letter-spacing: 0.2px;
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import styles from './MemberSinceInfo.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
import moment from 'moment';
|
||||
|
||||
export default ({memberSinceDate}) => (
|
||||
<div className={cn(styles.root, 'talk-plugin-member-since')}>
|
||||
<Icon name="date_range" className={cn(styles.icon, 'talk-plugin-member-since-icon')} />
|
||||
<span className={cn(styles.memberSince, 'talk-plugin-member-since-date')}>
|
||||
{t('talk-plugin-member-since.member_since')}: {moment(new Date(memberSinceDate)).format('MMM DD, YYYY')}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import MemberSinceInfo from '../components/MemberSinceInfo';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {withFragments} from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class MemberSinceInfoContainer extends React.Component {
|
||||
render() {
|
||||
return <MemberSinceInfo
|
||||
memberSinceDate={this.props.comment.user.created_at}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
const withMemberSinceInfoFragments = withFragments({
|
||||
comment: gql`
|
||||
fragment TalkAuthorMenu_MemberSinceInfo_comment on Comment {
|
||||
user {
|
||||
username
|
||||
created_at
|
||||
}
|
||||
}`,
|
||||
});
|
||||
|
||||
const enhance = compose(
|
||||
withMemberSinceInfoFragments,
|
||||
);
|
||||
|
||||
export default enhance(MemberSinceInfoContainer);
|
||||
@@ -0,0 +1,9 @@
|
||||
import MemberSinceInfo from './containers/MemberSinceInfo';
|
||||
import translations from './translations.yml';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
authorMenuInfos: [MemberSinceInfo]
|
||||
},
|
||||
translations
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
en:
|
||||
talk-plugin-member-since:
|
||||
member_since: "Member Since"
|
||||
es:
|
||||
talk-plugin-member-since:
|
||||
member_since: "Miembro desde"
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -29,12 +29,13 @@ export default class OffTopicCheckbox extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const checked = this.props.tags.indexOf(this.label) >= 0;
|
||||
return (
|
||||
<div className={styles.offTopic}>
|
||||
{
|
||||
!this.props.isReply ? (
|
||||
<label className={styles.offTopicLabel}>
|
||||
<input type="checkbox" onChange={this.handleChange}/>
|
||||
<input type="checkbox" onChange={this.handleChange} checked={checked}/>
|
||||
{t('off_topic')}
|
||||
</label>
|
||||
) : null
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const scraper = require('../../../services/scraper');
|
||||
const errors = require('../../../errors');
|
||||
const AssetsService = require('../../../services/assets');
|
||||
|
||||
@@ -88,25 +87,6 @@ router.get('/:asset_id', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Adds the asset id to the queue to be scraped.
|
||||
router.post('/:asset_id/scrape', async (req, res, next) => {
|
||||
try {
|
||||
|
||||
// Send back the asset.
|
||||
let asset = await AssetsService.findById(req.params.asset_id);
|
||||
if (!asset) {
|
||||
return next(errors.ErrNotFound);
|
||||
}
|
||||
|
||||
let job = await scraper.create(asset);
|
||||
|
||||
// Send the job back for monitoring.
|
||||
res.status(201).json(job);
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:asset_id/settings', async (req, res, next) => {
|
||||
try {
|
||||
await AssetsService.overrideSettings(req.params.asset_id, req.body);
|
||||
|
||||
+51
-14
@@ -108,19 +108,57 @@ module.exports = class AssetsService {
|
||||
* @param {String} value string to search by.
|
||||
* @return {Promise}
|
||||
*/
|
||||
static search({value, skip, limit} = {}) {
|
||||
if (!value) {
|
||||
return AssetsService.all(skip, limit);
|
||||
} else {
|
||||
return AssetModel
|
||||
.find({
|
||||
$text: {
|
||||
$search: value
|
||||
}
|
||||
})
|
||||
.skip(skip)
|
||||
.limit(limit);
|
||||
static search({value, limit, open, sortOrder, cursor} = {}) {
|
||||
let assets = AssetModel.find({});
|
||||
|
||||
if (value) {
|
||||
assets.merge({
|
||||
$text: {
|
||||
$search: value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (open != null) {
|
||||
if (open) {
|
||||
assets.merge({
|
||||
$or: [
|
||||
{
|
||||
closedAt: null
|
||||
},
|
||||
{
|
||||
closedAt: {
|
||||
$gt: Date.now()
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
assets.merge({
|
||||
closedAt: {
|
||||
$lt: Date.now()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
if (sortOrder === 'DESC') {
|
||||
assets.merge({
|
||||
created_at: {
|
||||
$lt: cursor,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
assets.merge({
|
||||
created_at: {
|
||||
$gt: cursor,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return assets.sort({created_at: sortOrder === 'DESC' ? -1 : 1}).limit(limit);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,10 +223,9 @@ module.exports = class AssetsService {
|
||||
// That's it!
|
||||
}
|
||||
|
||||
static all(skip = null, limit = null) {
|
||||
static all(limit = undefined) {
|
||||
return AssetModel
|
||||
.find({})
|
||||
.skip(skip)
|
||||
.limit(limit);
|
||||
}
|
||||
};
|
||||
|
||||
+1
-2
@@ -30,7 +30,6 @@ const attachMonitors = (client) => {
|
||||
};
|
||||
|
||||
const connectionOptions = {
|
||||
url: REDIS_URL,
|
||||
retry_strategy: function(options) {
|
||||
if (options.error && options.error.code !== 'ECONNREFUSED') {
|
||||
|
||||
@@ -65,7 +64,7 @@ const connectionOptions = {
|
||||
};
|
||||
|
||||
const createClient = () => {
|
||||
let client = new Redis(connectionOptions);
|
||||
let client = new Redis(REDIS_URL, connectionOptions);
|
||||
|
||||
// Attach the monitors that will print debug messages to the console.
|
||||
attachMonitors(client);
|
||||
|
||||
@@ -42,6 +42,19 @@ module.exports = class SettingsService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* updateWordlist will update the wordlists.
|
||||
*
|
||||
* @param {Object} wordlist the Wordlist object
|
||||
*/
|
||||
static updateWordlist(wordlist) {
|
||||
return SettingModel.findOneAndUpdate(selector, {
|
||||
$set: {
|
||||
wordlist,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is run once when the app starts to ensure settings are populated.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
const {graphql} = require('graphql');
|
||||
|
||||
const schema = require('../../../../graph/schema');
|
||||
const Context = require('../../../../graph/context');
|
||||
const UserModel = require('../../../../models/user');
|
||||
const SettingsService = require('../../../../services/settings');
|
||||
const AssetModel = require('../../../../models/asset');
|
||||
|
||||
const {expect} = require('chai');
|
||||
|
||||
describe('graph.mutations.updateAssetSettings', () => {
|
||||
let asset;
|
||||
beforeEach(async () => {
|
||||
await SettingsService.init();
|
||||
asset = await AssetModel.create({url: 'http://new.test.com/'});
|
||||
});
|
||||
|
||||
const QUERY = `
|
||||
mutation UpdateAssetStatus($id: ID!, $settings: AssetSettingsInput!) {
|
||||
updateAssetSettings(id: $id, input: $settings) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
describe('context with different user roles', () => {
|
||||
|
||||
[
|
||||
{error: 'NOT_AUTHORIZED'},
|
||||
{roles: ['ADMIN', 'MODERATOR']},
|
||||
{roles: ['MODERATOR']},
|
||||
].forEach(({roles, error}) => {
|
||||
it(roles ? roles.join(', ') : '<None>', async () => {
|
||||
const user = new UserModel({roles});
|
||||
const ctx = new Context({user});
|
||||
|
||||
const settings = {
|
||||
premodLinksEnable: false,
|
||||
moderation: 'POST',
|
||||
questionBoxEnable: true,
|
||||
questionBoxContent: 'Question?',
|
||||
questionBoxIcon: '<Icon>',
|
||||
};
|
||||
|
||||
const res = await graphql(schema, QUERY, {}, ctx, {
|
||||
id: asset.id,
|
||||
settings,
|
||||
});
|
||||
if (res.errors) {
|
||||
console.error(res.errors);
|
||||
}
|
||||
expect(res.errors).to.be.empty;
|
||||
|
||||
if (error) {
|
||||
expect(res.data.updateAssetSettings.errors).to.not.be.empty;
|
||||
expect(res.data.updateAssetSettings.errors[0]).to.have.property('translation_key', error);
|
||||
} else {
|
||||
expect(res.data.updateAssetSettings.errors).to.be.null;
|
||||
|
||||
const retrievedAsset = await AssetModel.findOne({id: asset.id});
|
||||
Object.keys(settings).forEach((key) => {
|
||||
expect(retrievedAsset.settings).to.have.property(key, settings[key]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
const {graphql} = require('graphql');
|
||||
|
||||
const schema = require('../../../../graph/schema');
|
||||
const Context = require('../../../../graph/context');
|
||||
const UserModel = require('../../../../models/user');
|
||||
const SettingsService = require('../../../../services/settings');
|
||||
const AssetModel = require('../../../../models/asset');
|
||||
|
||||
const {expect} = require('chai');
|
||||
|
||||
describe('graph.mutations.updateAssetStatus', () => {
|
||||
let asset;
|
||||
beforeEach(async () => {
|
||||
await SettingsService.init();
|
||||
asset = await AssetModel.create({url: 'http://new.test.com/'});
|
||||
});
|
||||
|
||||
const QUERY = `
|
||||
mutation UpdateAssetStatus($id: ID!, $status: UpdateAssetStatusInput!) {
|
||||
updateAssetStatus(id: $id, input: $status) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
describe('context with different user roles', () => {
|
||||
|
||||
[
|
||||
{error: 'NOT_AUTHORIZED'},
|
||||
{roles: ['ADMIN', 'MODERATOR']},
|
||||
{roles: ['MODERATOR']},
|
||||
].forEach(({roles, error}) => {
|
||||
it(roles ? roles.join(', ') : '<None>', async () => {
|
||||
const user = new UserModel({roles});
|
||||
const ctx = new Context({user});
|
||||
|
||||
const closedAt = (new Date()).toISOString();
|
||||
const closedMessage = 'my closed message!';
|
||||
|
||||
const res = await graphql(schema, QUERY, {}, ctx, {
|
||||
id: asset.id,
|
||||
status: {
|
||||
closedAt,
|
||||
closedMessage,
|
||||
},
|
||||
});
|
||||
if (res.errors) {
|
||||
console.error(res.errors);
|
||||
}
|
||||
expect(res.errors).to.be.empty;
|
||||
|
||||
if (error) {
|
||||
expect(res.data.updateAssetStatus.errors).to.not.be.empty;
|
||||
expect(res.data.updateAssetStatus.errors[0]).to.have.property('translation_key', error);
|
||||
} else {
|
||||
expect(res.data.updateAssetStatus.errors).to.be.null;
|
||||
|
||||
const retrievedAsset = await AssetModel.findOne({id: asset.id});
|
||||
expect(retrievedAsset.closedAt).to.not.be.null;
|
||||
expect(retrievedAsset).to.have.property('closedMessage', closedMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
const {graphql} = require('graphql');
|
||||
|
||||
const schema = require('../../../../graph/schema');
|
||||
const Context = require('../../../../graph/context');
|
||||
const UserModel = require('../../../../models/user');
|
||||
const SettingsService = require('../../../../services/settings');
|
||||
|
||||
const {expect} = require('chai');
|
||||
|
||||
describe('graph.mutations.updateSettings', () => {
|
||||
beforeEach(async () => {
|
||||
await SettingsService.init();
|
||||
});
|
||||
|
||||
const QUERY = `
|
||||
mutation UpdateSettings($settings: UpdateSettingsInput!) {
|
||||
updateSettings(input: $settings) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
describe('context with different user roles', () => {
|
||||
|
||||
[
|
||||
{error: 'NOT_AUTHORIZED'},
|
||||
{error: 'NOT_AUTHORIZED', roles: []},
|
||||
{roles: ['ADMIN']},
|
||||
{roles: ['ADMIN', 'MODERATOR']},
|
||||
{roles: ['MODERATOR']},
|
||||
].forEach(({roles, error}) => {
|
||||
it(roles ? roles.join(', ') : '<None>', async () => {
|
||||
let user;
|
||||
if (roles != null) {
|
||||
user = new UserModel({roles});
|
||||
}
|
||||
const ctx = new Context({user});
|
||||
|
||||
const newSettings = {
|
||||
premodLinksEnable: false,
|
||||
moderation: 'POST',
|
||||
questionBoxEnable: true,
|
||||
questionBoxContent: 'Question?',
|
||||
questionBoxIcon: '<Icon>',
|
||||
};
|
||||
|
||||
const res = await graphql(schema, QUERY, {}, ctx, {
|
||||
settings: newSettings,
|
||||
});
|
||||
if (res.errors) {
|
||||
console.error(res.errors);
|
||||
}
|
||||
expect(res.errors).to.be.empty;
|
||||
|
||||
if (error) {
|
||||
expect(res.data.updateSettings.errors).to.not.be.empty;
|
||||
expect(res.data.updateSettings.errors[0]).to.have.property('translation_key', error);
|
||||
} else {
|
||||
if (res.data.updateSettings.errors) {
|
||||
console.error(res.data.updateSettings.errors);
|
||||
}
|
||||
expect(res.data.updateSettings.errors).to.be.null;
|
||||
|
||||
const retrievedSettings = await SettingsService.retrieve();
|
||||
Object.keys(newSettings).forEach((key) => {
|
||||
expect(retrievedSettings).to.have.property(key, newSettings[key]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
const {graphql} = require('graphql');
|
||||
|
||||
const schema = require('../../../../graph/schema');
|
||||
const Context = require('../../../../graph/context');
|
||||
const UserModel = require('../../../../models/user');
|
||||
const SettingsService = require('../../../../services/settings');
|
||||
|
||||
const {expect} = require('chai');
|
||||
|
||||
describe('graph.mutations.updateWordlist', () => {
|
||||
beforeEach(async () => {
|
||||
await SettingsService.init();
|
||||
});
|
||||
|
||||
const QUERY = `
|
||||
mutation UpdateWordlist($wordlist: UpdateWordlistInput!) {
|
||||
updateWordlist(input: $wordlist) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
describe('context with different user roles', () => {
|
||||
|
||||
[
|
||||
{error: 'NOT_AUTHORIZED'},
|
||||
{error: 'NOT_AUTHORIZED', roles: []},
|
||||
{roles: ['ADMIN']},
|
||||
{roles: ['ADMIN', 'MODERATOR']},
|
||||
{roles: ['MODERATOR']},
|
||||
].forEach(({roles, error}) => {
|
||||
it(roles && roles.length > 0 ? roles.join(', ') : '<None>', async () => {
|
||||
let user;
|
||||
if (roles != null) {
|
||||
user = new UserModel({roles});
|
||||
}
|
||||
const ctx = new Context({user});
|
||||
|
||||
const wordlist = {
|
||||
banned: [
|
||||
'happy',
|
||||
],
|
||||
suspect: [
|
||||
'sad',
|
||||
],
|
||||
};
|
||||
|
||||
const res = await graphql(schema, QUERY, {}, ctx, {
|
||||
wordlist,
|
||||
});
|
||||
if (res.errors) {
|
||||
console.error(res.errors);
|
||||
}
|
||||
expect(res.errors).to.be.empty;
|
||||
|
||||
if (error) {
|
||||
expect(res.data.updateWordlist.errors).to.not.be.empty;
|
||||
expect(res.data.updateWordlist.errors[0]).to.have.property('translation_key', error);
|
||||
|
||||
const {wordlist: retrievedWordlist} = await SettingsService.retrieve();
|
||||
expect(retrievedWordlist).to.have.property('banned');
|
||||
expect(retrievedWordlist.banned).to.have.members([]);
|
||||
expect(retrievedWordlist).to.have.property('suspect');
|
||||
expect(retrievedWordlist.suspect).to.have.members([]);
|
||||
} else {
|
||||
if (res.data.updateWordlist.errors) {
|
||||
console.error(res.data.updateWordlist.errors);
|
||||
}
|
||||
expect(res.data.updateWordlist.errors).to.be.null;
|
||||
|
||||
const {wordlist: retrievedWordlist} = await SettingsService.retrieve();
|
||||
expect(retrievedWordlist).to.have.property('banned');
|
||||
expect(retrievedWordlist.banned).to.have.members(wordlist.banned);
|
||||
expect(retrievedWordlist).to.have.property('suspect');
|
||||
expect(retrievedWordlist.suspect).to.have.members(wordlist.suspect);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -74,12 +74,10 @@ describe('graph.queries.asset', () => {
|
||||
|
||||
expect(asset.nodes).to.have.length(2);
|
||||
expect(asset.hasNextPage).to.be.false;
|
||||
expect(asset.nodes[0]).to.have.property('id', comments[1].id);
|
||||
expect(asset.nodes[1]).to.have.property('id', comments[0].id);
|
||||
expect(asset.nodes.map(({id}) => id)).to.have.members(comments.slice(0, 2).map(({id}) => id));
|
||||
expect(otherAsset.nodes).to.have.length(2);
|
||||
expect(otherAsset.hasNextPage).to.be.false;
|
||||
expect(otherAsset.nodes[0]).to.have.property('id', comments[3].id);
|
||||
expect(otherAsset.nodes[1]).to.have.property('id', comments[2].id);
|
||||
expect(otherAsset.nodes.map(({id}) => id)).to.have.members(comments.slice(2, 4).map(({id}) => id));
|
||||
|
||||
for (let node of asset.nodes) {
|
||||
for (let otherNode of otherAsset.nodes) {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
const {graphql} = require('graphql');
|
||||
|
||||
const schema = require('../../../../graph/schema');
|
||||
const Context = require('../../../../graph/context');
|
||||
const SettingsService = require('../../../../services/settings');
|
||||
const UserModel = require('../../../../models/user');
|
||||
|
||||
const {expect} = require('chai');
|
||||
|
||||
const defaultSettings = {
|
||||
organizationName: 'The Coral Project'
|
||||
};
|
||||
|
||||
describe('graph.queries.settings', () => {
|
||||
let settings;
|
||||
beforeEach(async () => {
|
||||
settings = await SettingsService.init(defaultSettings);
|
||||
});
|
||||
|
||||
const QUERY = `
|
||||
{
|
||||
settings {
|
||||
moderation
|
||||
requireEmailConfirmation
|
||||
infoBoxEnable
|
||||
infoBoxContent
|
||||
questionBoxEnable
|
||||
questionBoxContent
|
||||
premodLinksEnable
|
||||
questionBoxIcon
|
||||
autoCloseStream
|
||||
customCssUrl
|
||||
closedTimeout
|
||||
closedMessage
|
||||
charCountEnable
|
||||
charCount
|
||||
organizationName
|
||||
wordlist {
|
||||
banned
|
||||
suspect
|
||||
}
|
||||
domains {
|
||||
whitelist
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
describe('context with different user roles', () => {
|
||||
|
||||
const BLACKLISTED_PROPERTIES = [
|
||||
'premodLinksEnable',
|
||||
'autoCloseStream',
|
||||
'wordlist',
|
||||
'domains',
|
||||
];
|
||||
|
||||
[
|
||||
{bl: true},
|
||||
{bl: true, roles: []},
|
||||
{bl: false, roles: ['ADMIN']},
|
||||
{bl: false, roles: ['ADMIN', 'MODERATOR']},
|
||||
{bl: false, roles: ['MODERATOR']},
|
||||
].forEach(({bl, roles}) => {
|
||||
it(roles && roles.length > 0 ? roles.join(', ') : '<None>', async () => {
|
||||
let user;
|
||||
if (roles != null) {
|
||||
user = new UserModel({roles});
|
||||
}
|
||||
|
||||
const ctx = new Context({user});
|
||||
|
||||
const res = await graphql(schema, QUERY, {}, ctx);
|
||||
if (res.errors) {
|
||||
console.error(res.errors);
|
||||
}
|
||||
|
||||
expect(res.errors).to.be.empty;
|
||||
expect(res.data.settings).to.be.object;
|
||||
Object.keys(res.data.settings).forEach((key) => {
|
||||
if (bl && BLACKLISTED_PROPERTIES.includes(key)) {
|
||||
expect(res.data.settings).to.have.property(key, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof settings[key] !== 'object') {
|
||||
expect(res.data.settings).to.have.property(key, settings[key]);
|
||||
} else {
|
||||
expect(res.data.settings).to.have.property(key);
|
||||
expect(res.data.settings[key]).to.not.be.null;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -63,20 +63,13 @@ abbrev@1, abbrev@1.0.x:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
|
||||
|
||||
accepts@^1.3.4:
|
||||
accepts@^1.3.4, accepts@~1.3.3:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f"
|
||||
dependencies:
|
||||
mime-types "~2.1.16"
|
||||
negotiator "0.6.1"
|
||||
|
||||
accepts@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
|
||||
dependencies:
|
||||
mime-types "~2.1.11"
|
||||
negotiator "0.6.1"
|
||||
|
||||
acorn-dynamic-import@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4"
|
||||
@@ -356,18 +349,12 @@ async@2.1.4:
|
||||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
async@2.4.1:
|
||||
async@2.4.1, async@^2.1.2, async@^2.1.4:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7"
|
||||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
async@^2.1.2, async@^2.1.4:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
|
||||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
async@~0.9.0:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
|
||||
@@ -416,15 +403,7 @@ babel-cli@^6.26.0:
|
||||
optionalDependencies:
|
||||
chokidar "^1.6.1"
|
||||
|
||||
babel-code-frame@^6.11.0, babel-code-frame@^6.22.0:
|
||||
version "6.22.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
|
||||
dependencies:
|
||||
chalk "^1.1.0"
|
||||
esutils "^2.0.2"
|
||||
js-tokens "^3.0.0"
|
||||
|
||||
babel-code-frame@^6.26.0:
|
||||
babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
|
||||
dependencies:
|
||||
@@ -465,20 +444,7 @@ babel-eslint@^7.2.1:
|
||||
babel-types "^6.23.0"
|
||||
babylon "^6.17.0"
|
||||
|
||||
babel-generator@^6.18.0:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.24.1.tgz#e715f486c58ded25649d888944d52aa07c5d9497"
|
||||
dependencies:
|
||||
babel-messages "^6.23.0"
|
||||
babel-runtime "^6.22.0"
|
||||
babel-types "^6.24.1"
|
||||
detect-indent "^4.0.0"
|
||||
jsesc "^1.3.0"
|
||||
lodash "^4.2.0"
|
||||
source-map "^0.5.0"
|
||||
trim-right "^1.0.1"
|
||||
|
||||
babel-generator@^6.26.0:
|
||||
babel-generator@^6.18.0, babel-generator@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5"
|
||||
dependencies:
|
||||
@@ -956,31 +922,14 @@ babel-register@^6.26.0:
|
||||
mkdirp "^0.5.1"
|
||||
source-map-support "^0.4.15"
|
||||
|
||||
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.6.1:
|
||||
version "6.23.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
|
||||
dependencies:
|
||||
core-js "^2.4.0"
|
||||
regenerator-runtime "^0.10.0"
|
||||
|
||||
babel-runtime@^6.26.0:
|
||||
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
|
||||
dependencies:
|
||||
core-js "^2.4.0"
|
||||
regenerator-runtime "^0.11.0"
|
||||
|
||||
babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.3.0:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.24.1.tgz#04ae514f1f93b3a2537f2a0f60a5a45fb8308333"
|
||||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
babel-traverse "^6.24.1"
|
||||
babel-types "^6.24.1"
|
||||
babylon "^6.11.0"
|
||||
lodash "^4.2.0"
|
||||
|
||||
babel-template@^6.26.0:
|
||||
babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
|
||||
dependencies:
|
||||
@@ -990,21 +939,7 @@ babel-template@^6.26.0:
|
||||
babylon "^6.18.0"
|
||||
lodash "^4.17.4"
|
||||
|
||||
babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.24.1.tgz#ab36673fd356f9a0948659e7b338d5feadb31695"
|
||||
dependencies:
|
||||
babel-code-frame "^6.22.0"
|
||||
babel-messages "^6.23.0"
|
||||
babel-runtime "^6.22.0"
|
||||
babel-types "^6.24.1"
|
||||
babylon "^6.15.0"
|
||||
debug "^2.2.0"
|
||||
globals "^9.0.0"
|
||||
invariant "^2.2.0"
|
||||
lodash "^4.2.0"
|
||||
|
||||
babel-traverse@^6.26.0:
|
||||
babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
|
||||
dependencies:
|
||||
@@ -1018,16 +953,7 @@ babel-traverse@^6.26.0:
|
||||
invariant "^2.2.2"
|
||||
lodash "^4.17.4"
|
||||
|
||||
babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.24.1.tgz#a136879dc15b3606bda0d90c1fc74304c2ff0975"
|
||||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
esutils "^2.0.2"
|
||||
lodash "^4.2.0"
|
||||
to-fast-properties "^1.0.1"
|
||||
|
||||
babel-types@^6.26.0:
|
||||
babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
|
||||
dependencies:
|
||||
@@ -1036,11 +962,7 @@ babel-types@^6.26.0:
|
||||
lodash "^4.17.4"
|
||||
to-fast-properties "^1.0.3"
|
||||
|
||||
babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0, babylon@^6.17.0:
|
||||
version "6.17.0"
|
||||
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.0.tgz#37da948878488b9c4e3c4038893fa3314b3fc932"
|
||||
|
||||
babylon@^6.18.0:
|
||||
babylon@^6.13.0, babylon@^6.17.0, babylon@^6.18.0:
|
||||
version "6.18.0"
|
||||
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
|
||||
|
||||
@@ -1056,7 +978,7 @@ balanced-match@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.2.1.tgz#7bc658b4bed61eee424ad74f75f5c3e2c4df3cc7"
|
||||
|
||||
balanced-match@^0.4.1, balanced-match@^0.4.2:
|
||||
balanced-match@^0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
|
||||
|
||||
@@ -1112,7 +1034,7 @@ bluebird@2.9.24:
|
||||
version "2.9.24"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.9.24.tgz#14a2e75f0548323dc35aa440d92007ca154e967c"
|
||||
|
||||
bluebird@3.5.0, bluebird@^3.3.4:
|
||||
bluebird@3.5.0, bluebird@^3.3.4, bluebird@^3.4.6:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
||||
|
||||
@@ -1120,30 +1042,11 @@ bluebird@^2.10.2:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
|
||||
|
||||
bluebird@^3.4.6:
|
||||
version "3.4.7"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
|
||||
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
|
||||
version "4.11.6"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215"
|
||||
|
||||
body-parser@^1.12.2:
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.1.tgz#75b3bc98ddd6e7e0d8ffe750dfaca5c66993fa47"
|
||||
dependencies:
|
||||
bytes "2.4.0"
|
||||
content-type "~1.0.2"
|
||||
debug "2.6.1"
|
||||
depd "~1.1.0"
|
||||
http-errors "~1.6.1"
|
||||
iconv-lite "0.4.15"
|
||||
on-finished "~2.3.0"
|
||||
qs "6.4.0"
|
||||
raw-body "~2.2.0"
|
||||
type-is "~1.6.14"
|
||||
|
||||
body-parser@^1.17.2:
|
||||
body-parser@^1.12.2, body-parser@^1.17.2:
|
||||
version "1.17.2"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee"
|
||||
dependencies:
|
||||
@@ -1172,13 +1075,6 @@ bowser@^1.7.2:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.2.tgz#b94cc6925ba6b5e07c421a58e601ce4611264572"
|
||||
|
||||
brace-expansion@^1.0.0:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59"
|
||||
dependencies:
|
||||
balanced-match "^0.4.1"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
|
||||
@@ -1394,7 +1290,7 @@ chalk@2.0.1:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^4.0.0"
|
||||
|
||||
chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
dependencies:
|
||||
@@ -1867,11 +1763,7 @@ core-js@^1.0.0:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
|
||||
core-js@^2.4.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
|
||||
|
||||
core-js@^2.5.0:
|
||||
core-js@^2.4.0, core-js@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.0.tgz#569c050918be6486b3837552028ae0466b717086"
|
||||
|
||||
@@ -2131,9 +2023,9 @@ date-now@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||
|
||||
debug@*:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.0.tgz#1d2feae53349047b08b264ec41906ba17a8516e4"
|
||||
debug@*, debug@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
@@ -2143,12 +2035,6 @@ debug@2.6.0:
|
||||
dependencies:
|
||||
ms "0.7.2"
|
||||
|
||||
debug@2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351"
|
||||
dependencies:
|
||||
ms "0.7.2"
|
||||
|
||||
debug@2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e"
|
||||
@@ -2161,12 +2047,6 @@ debug@2.6.8, debug@^2.2.0, debug@^2.6.8:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@~0.7.4:
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39"
|
||||
@@ -3200,7 +3080,7 @@ glob@7.0.x:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
|
||||
glob@7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
|
||||
dependencies:
|
||||
@@ -3211,7 +3091,7 @@ glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@7.1.2, glob@^7.1.2:
|
||||
glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
dependencies:
|
||||
@@ -3242,10 +3122,6 @@ glob@^6.0.4:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
globals@^9.0.0:
|
||||
version "9.17.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.17.0.tgz#0c0ca696d9b9bb694d2e5470bd37777caad50286"
|
||||
|
||||
globals@^9.17.0, globals@^9.18.0:
|
||||
version "9.18.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
|
||||
@@ -3372,15 +3248,7 @@ graphql-server-module-graphiql@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql-server-module-graphiql/-/graphql-server-module-graphiql-0.6.0.tgz#e37634b05f000731981e8ed13103f9a5861e5da0"
|
||||
|
||||
graphql-subscriptions@^0.4.2:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.4.4.tgz#39cff32d08dd3c990113864bab77154403727e9b"
|
||||
dependencies:
|
||||
"@types/graphql" "^0.9.1"
|
||||
es6-promise "^4.0.5"
|
||||
iterall "^1.1.1"
|
||||
|
||||
graphql-subscriptions@^0.4.3:
|
||||
graphql-subscriptions@^0.4.2, graphql-subscriptions@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.4.3.tgz#2aed6ba87551cc747742b793497ff24b22991867"
|
||||
dependencies:
|
||||
@@ -4240,22 +4108,18 @@ js-stringify@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db"
|
||||
|
||||
js-tokens@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
|
||||
|
||||
js-tokens@^3.0.2:
|
||||
js-tokens@^3.0.0, js-tokens@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||
|
||||
js-yaml@3.x, js-yaml@^3.4.3, js-yaml@^3.5.2, js-yaml@^3.7.0:
|
||||
js-yaml@3.x, js-yaml@^3.5.2:
|
||||
version "3.8.3"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.3.tgz#33a05ec481c850c8875929166fe1beb61c728766"
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
esprima "^3.1.1"
|
||||
|
||||
js-yaml@^3.9.1:
|
||||
js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.1:
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0"
|
||||
dependencies:
|
||||
@@ -4318,11 +4182,7 @@ jshint@^2.8.0:
|
||||
shelljs "0.3.x"
|
||||
strip-json-comments "1.0.x"
|
||||
|
||||
json-loader@^0.5.4:
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de"
|
||||
|
||||
json-loader@^0.5.7:
|
||||
json-loader@^0.5.4, json-loader@^0.5.7:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
|
||||
|
||||
@@ -4954,15 +4814,11 @@ mime-db@~1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.12.0.tgz#3d0c63180f458eb10d325aaa37d7c58ae312e9d7"
|
||||
|
||||
mime-db@~1.27.0:
|
||||
version "1.27.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
|
||||
|
||||
mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7:
|
||||
version "2.1.15"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
|
||||
mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7:
|
||||
version "2.1.16"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23"
|
||||
dependencies:
|
||||
mime-db "~1.27.0"
|
||||
mime-db "~1.29.0"
|
||||
|
||||
mime-types@~2.0.3:
|
||||
version "2.0.14"
|
||||
@@ -4970,12 +4826,6 @@ mime-types@~2.0.3:
|
||||
dependencies:
|
||||
mime-db "~1.12.0"
|
||||
|
||||
mime-types@~2.1.16:
|
||||
version "2.1.16"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23"
|
||||
dependencies:
|
||||
mime-db "~1.29.0"
|
||||
|
||||
mime@1.3.4, mime@1.3.x, mime@^1.3.4:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
|
||||
@@ -4992,13 +4842,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||
|
||||
"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
|
||||
dependencies:
|
||||
brace-expansion "^1.0.0"
|
||||
|
||||
minimatch@^3.0.4, minimatch@~3.0.2:
|
||||
"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
dependencies:
|
||||
@@ -5043,7 +4887,7 @@ mocha@^3.1.2:
|
||||
mkdirp "0.5.1"
|
||||
supports-color "3.1.2"
|
||||
|
||||
moment@2.18.1, moment@2.x.x, moment@^2.10.3:
|
||||
moment@2.18.1, moment@2.x.x, moment@^2.10.3, moment@^2.18.1:
|
||||
version "2.18.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
|
||||
|
||||
@@ -6429,7 +6273,7 @@ pym.js@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pym.js/-/pym.js-1.2.0.tgz#feb1e2c9b396613e5172192b0cdd75408f9c8322"
|
||||
|
||||
q@1.5.0:
|
||||
q@1.5.0, q@^1.1.2:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
|
||||
|
||||
@@ -6441,10 +6285,6 @@ q@2.0.3:
|
||||
pop-iterate "^1.0.1"
|
||||
weak-map "^1.0.5"
|
||||
|
||||
q@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.1.2.tgz#6357e291206701d99f197ab84e57e8ad196f2a89"
|
||||
|
||||
qs@6.4.0, qs@~6.4.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
||||
@@ -6796,10 +6636,6 @@ regenerate@^1.2.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
|
||||
|
||||
regenerator-runtime@^0.10.0:
|
||||
version "0.10.4"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.4.tgz#74cb6598d3ba2eb18694e968a40e2b3b4df9cf93"
|
||||
|
||||
regenerator-runtime@^0.10.5:
|
||||
version "0.10.5"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
|
||||
@@ -7127,7 +6963,7 @@ shelljs@0.3.x:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1"
|
||||
|
||||
shelljs@0.7.8:
|
||||
shelljs@0.7.8, shelljs@^0.7.0:
|
||||
version "0.7.8"
|
||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
|
||||
dependencies:
|
||||
@@ -7135,14 +6971,6 @@ shelljs@0.7.8:
|
||||
interpret "^1.0.0"
|
||||
rechoir "^0.6.2"
|
||||
|
||||
shelljs@^0.7.0:
|
||||
version "0.7.7"
|
||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.7.tgz#b2f5c77ef97148f4b4f6e22682e10bba8667cff1"
|
||||
dependencies:
|
||||
glob "^7.0.0"
|
||||
interpret "^1.0.0"
|
||||
rechoir "^0.6.2"
|
||||
|
||||
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
@@ -7275,7 +7103,7 @@ source-map@0.4.x, source-map@^0.4.4:
|
||||
dependencies:
|
||||
amdefine ">=0.0.4"
|
||||
|
||||
source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
|
||||
source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
|
||||
@@ -7648,10 +7476,6 @@ to-capital-case@^1.0.0:
|
||||
dependencies:
|
||||
to-space-case "^1.0.0"
|
||||
|
||||
to-fast-properties@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320"
|
||||
|
||||
to-fast-properties@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
|
||||
@@ -7753,7 +7577,7 @@ type-detect@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea"
|
||||
|
||||
type-is@~1.6.14, type-is@~1.6.15:
|
||||
type-is@~1.6.15:
|
||||
version "1.6.15"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
|
||||
dependencies:
|
||||
@@ -8051,14 +7875,10 @@ word-wrap@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.1.0.tgz#356153d61d10610d600785c5d701288e0ae764a6"
|
||||
|
||||
word-wrap@1.2.3:
|
||||
word-wrap@1.2.3, word-wrap@^1.0.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
|
||||
word-wrap@^1.0.3:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.1.tgz#248f459b465d179a17bc407c854d3151d07e45d8"
|
||||
|
||||
wordwrap@0.0.2, wordwrap@~0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
||||
|
||||
Reference in New Issue
Block a user