mirror of
https://github.com/wassname/talk.git
synced 2026-07-06 05:17:19 +08:00
Merge branch 'master' into bump-version
This commit is contained in:
+35
-26
@@ -7,51 +7,53 @@
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017
|
||||
},
|
||||
"plugins": [
|
||||
"promise",
|
||||
"json"
|
||||
],
|
||||
"rules": {
|
||||
"indent": ["error",
|
||||
2
|
||||
],
|
||||
"no-console": [
|
||||
0
|
||||
],
|
||||
"no-console": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"no-template-curly-in-string": [1],
|
||||
"no-unsafe-negation": [1],
|
||||
"array-callback-return": [1],
|
||||
"no-template-curly-in-string": "warn",
|
||||
"no-unsafe-negation": "warn",
|
||||
"array-callback-return": "warn",
|
||||
"arrow-parens": ["warn", "always"],
|
||||
"template-curly-spacing": "warn",
|
||||
"eqeqeq": [2, "smart"],
|
||||
"no-eval": [2],
|
||||
"no-global-assign": [2],
|
||||
"no-implied-eval": [2],
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"no-eval": "error",
|
||||
"no-global-assign": "error",
|
||||
"no-implied-eval": "error",
|
||||
"lines-around-comment": ["warn", {"beforeLineComment": true}],
|
||||
"spaced-comment": ["warn", "always", { "line": { "exceptions": ["-", "="] } }],
|
||||
"no-script-url": [2],
|
||||
"no-throw-literal": [2],
|
||||
"yoda": [1],
|
||||
"no-path-concat": [2],
|
||||
"eol-last": [1],
|
||||
"no-nested-ternary": [1],
|
||||
"no-tabs": [2],
|
||||
"no-unneeded-ternary": [1],
|
||||
"object-curly-spacing": [1],
|
||||
"no-script-url": "error",
|
||||
"no-throw-literal": "error",
|
||||
"yoda": "warn",
|
||||
"no-path-concat": "error",
|
||||
"eol-last": "warn",
|
||||
"no-nested-ternary": "warn",
|
||||
"no-tabs": "error",
|
||||
"no-unneeded-ternary": "warn",
|
||||
"object-curly-spacing": "warn",
|
||||
"space-infix-ops": ["error"],
|
||||
"space-in-parens": ["error", "never"],
|
||||
"space-unary-ops": ["error", {
|
||||
"words": true,
|
||||
"nonwords": false
|
||||
}],
|
||||
"no-const-assign": [2],
|
||||
"no-duplicate-imports": [2],
|
||||
"prefer-template": [1],
|
||||
"no-const-assign": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"prefer-template": "warn",
|
||||
"comma-spacing": ["error", {
|
||||
"after": true
|
||||
}],
|
||||
"no-var": [2],
|
||||
"no-lonely-if": [2],
|
||||
"curly": [2],
|
||||
"no-var": "error",
|
||||
"no-lonely-if": "error",
|
||||
"curly": "error",
|
||||
"no-unused-vars": ["error", {
|
||||
"argsIgnorePattern": "^_|next",
|
||||
"varsIgnorePattern": "^_"
|
||||
@@ -61,6 +63,13 @@
|
||||
}],
|
||||
"newline-per-chained-call": ["error", {
|
||||
"ignoreChainWithDepth": 2
|
||||
}]
|
||||
}],
|
||||
"promise/no-return-wrap": "error",
|
||||
"promise/param-names": "error",
|
||||
"promise/catch-or-return": "error",
|
||||
"promise/no-native": "off",
|
||||
"promise/no-nesting": "warn",
|
||||
"promise/no-promise-in-callback": "warn",
|
||||
"promise/no-callback-in-promise": "warn"
|
||||
}
|
||||
}
|
||||
|
||||
+13
-8
@@ -78,18 +78,23 @@ function rangeJobsByState(state = 'complete', limit) {
|
||||
/**
|
||||
* Cleans up the jobs that are in the queue.
|
||||
*/
|
||||
function cleanupJobs(options) {
|
||||
async function cleanupJobs(options) {
|
||||
const n = 100;
|
||||
|
||||
Promise.all([
|
||||
rangeJobsByState('complete', n),
|
||||
options.stuck ? rangeJobsByState('failed', n) : false
|
||||
])
|
||||
.then((joblists) => joblists.filter((jobs) => jobs).map(removeJobs))
|
||||
.then(() => {
|
||||
try {
|
||||
const joblists = await Promise.all([
|
||||
rangeJobsByState('complete', n),
|
||||
options.stuck ? rangeJobsByState('failed', n) : false
|
||||
]);
|
||||
|
||||
await joblists.filter((jobs) => jobs).map(removeJobs);
|
||||
|
||||
util.shutdown();
|
||||
console.log('Removed old jobs');
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ function versionMatch(name, version) {
|
||||
}
|
||||
}
|
||||
|
||||
const EXTERNAL = /^\w[a-z\-0-9\.]+$/; // Match "react", "path", "fs", "lodash.random", etc.
|
||||
const EXTERNAL = /^\w[a-z\-0-9.]+$/; // Match "react", "path", "fs", "lodash.random", etc.
|
||||
|
||||
function reconcilePackages({quiet = false, upgradeRemote = false}) {
|
||||
const fetchable = [];
|
||||
|
||||
+38
-45
@@ -98,39 +98,32 @@ function getUserCreateAnswers(options) {
|
||||
/**
|
||||
* Prompts for input and registers a user based on those.
|
||||
*/
|
||||
function createUser(options) {
|
||||
getUserCreateAnswers(options)
|
||||
.then((answers) => {
|
||||
if (answers.password !== answers.confirmPassword) {
|
||||
return Promise.reject(new Error('Passwords do not match'));
|
||||
}
|
||||
async function createUser(options) {
|
||||
try {
|
||||
const answers = await getUserCreateAnswers(options);
|
||||
if (answers.password !== answers.confirmPassword) {
|
||||
throw new Error('Passwords do not match');
|
||||
}
|
||||
|
||||
return answers;
|
||||
})
|
||||
.then((answers) => {
|
||||
return UsersService
|
||||
.createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim())
|
||||
.then((user) => {
|
||||
console.log(`Created user ${user.id}.`);
|
||||
const user = await UsersService.createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim());
|
||||
console.log(`Created user ${user.id}.`);
|
||||
|
||||
if (answers.roles.length > 0) {
|
||||
return Promise.all(answers.roles.map((role) => {
|
||||
return UsersService
|
||||
.addRoleToUser(user.id, role)
|
||||
.then(() => {
|
||||
console.log(`Added the role ${role} to User ${user.id}.`);
|
||||
});
|
||||
}));
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown();
|
||||
});
|
||||
if (answers.roles.length > 0) {
|
||||
return Promise.all(answers.roles.map((role) => {
|
||||
return UsersService
|
||||
.addRoleToUser(user.id, role)
|
||||
.then(() => {
|
||||
console.log(`Added the role ${role} to User ${user.id}.`);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
util.shutdown();
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
util.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,21 +162,21 @@ function passwd(userID) {
|
||||
validate: validateRequired('Confirm Password is required')
|
||||
}
|
||||
])
|
||||
.then((answers) => {
|
||||
if (answers.password !== answers.confirmPassword) {
|
||||
return Promise.reject(new Error('Password mismatch'));
|
||||
}
|
||||
.then((answers) => {
|
||||
if (answers.password !== answers.confirmPassword) {
|
||||
throw new Error('Password mismatch');
|
||||
}
|
||||
|
||||
return UsersService.changePassword(userID, answers.password);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Password changed.');
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
});
|
||||
return UsersService.changePassword(userID, answers.password);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Password changed.');
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -125,7 +125,7 @@ module.exports = async ({fix, limit, batch}) => {
|
||||
// Check that the action summaries match the cached counts.
|
||||
if (!comment.action_counts || !(ACTION_COUNT_FIELD in comment.action_counts) || comment.action_counts[ACTION_COUNT_FIELD] !== count) {
|
||||
|
||||
// Batch updates for those changes.
|
||||
// Batch updates for those changes.
|
||||
commentOperations.push({
|
||||
[`action_counts.${ACTION_COUNT_FIELD}`]: count,
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": "../.eslintrc.json",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
|
||||
@@ -52,16 +52,16 @@ export const newPage = () => ({
|
||||
|
||||
export const setRole = (id, role) => (dispatch, _, {rest}) => {
|
||||
return rest(`/users/${id}/role`, {method: 'POST', body: {role}})
|
||||
.then(() => {
|
||||
return dispatch({type: SET_ROLE, id, role});
|
||||
});
|
||||
.then(() => {
|
||||
return dispatch({type: SET_ROLE, id, role});
|
||||
});
|
||||
};
|
||||
|
||||
export const setCommenterStatus = (id, status) => (dispatch, _, {rest}) => {
|
||||
return rest(`/users/${id}/status`, {method: 'POST', body: {status}})
|
||||
.then(() => {
|
||||
return dispatch({type: SET_COMMENTER_STATUS, id, status});
|
||||
});
|
||||
.then(() => {
|
||||
return dispatch({type: SET_COMMENTER_STATUS, id, status});
|
||||
});
|
||||
};
|
||||
|
||||
// Ban User Dialog
|
||||
|
||||
@@ -26,19 +26,19 @@ const validation = (formData, dispatch, next) => {
|
||||
|
||||
// Required Validation
|
||||
const empty = validKeys
|
||||
.filter((name) => {
|
||||
const cond = !formData[name].length;
|
||||
.filter((name) => {
|
||||
const cond = !formData[name].length;
|
||||
|
||||
if (cond) {
|
||||
if (cond) {
|
||||
|
||||
// Adding Error
|
||||
dispatch(addError(name, 'This field is required.'));
|
||||
} else {
|
||||
dispatch(addError(name, ''));
|
||||
}
|
||||
// Adding Error
|
||||
dispatch(addError(name, 'This field is required.'));
|
||||
} else {
|
||||
dispatch(addError(name, ''));
|
||||
}
|
||||
|
||||
return cond;
|
||||
});
|
||||
return cond;
|
||||
});
|
||||
|
||||
if (empty.length) {
|
||||
dispatch(hasError());
|
||||
|
||||
@@ -69,23 +69,23 @@ class AdminLogin extends React.Component {
|
||||
);
|
||||
const requestPasswordForm = (
|
||||
this.props.passwordRequestSuccess
|
||||
? <p className={styles.passwordRequestSuccess} onClick={() => {
|
||||
location.href = location.href;
|
||||
}}>
|
||||
? <p className={styles.passwordRequestSuccess} onClick={() => {
|
||||
location.href = location.href;
|
||||
}}>
|
||||
{this.props.passwordRequestSuccess} <a className={styles.signInLink} href="#">Sign in</a>
|
||||
<Success />
|
||||
</p>
|
||||
: <form onSubmit={this.handleRequestPassword}>
|
||||
<TextField
|
||||
label='Email Address'
|
||||
value={this.state.email}
|
||||
onChange={(e) => this.setState({email: e.target.value})} />
|
||||
<Button
|
||||
type='submit'
|
||||
cStyle='black'
|
||||
full
|
||||
onClick={this.handleRequestPassword}>Reset Password</Button>
|
||||
</form>
|
||||
: <form onSubmit={this.handleRequestPassword}>
|
||||
<TextField
|
||||
label='Email Address'
|
||||
value={this.state.email}
|
||||
onChange={(e) => this.setState({email: e.target.value})} />
|
||||
<Button
|
||||
type='submit'
|
||||
cStyle='black'
|
||||
full
|
||||
onClick={this.handleRequestPassword}>Reset Password</Button>
|
||||
</form>
|
||||
);
|
||||
return (
|
||||
<Layout fixedDrawer restricted={true}>
|
||||
|
||||
@@ -65,7 +65,7 @@ class SuspendUserDialog extends React.Component {
|
||||
{t('suspenduser.title_suspend')}
|
||||
</h1>
|
||||
<p className={styles.description}>
|
||||
{t('suspenduser.description_suspend', username)}
|
||||
{t('suspenduser.description_suspend', username)}
|
||||
</p>
|
||||
<fieldset>
|
||||
<legend className={styles.legend}>{t('suspenduser.select_duration')}</legend>
|
||||
@@ -103,7 +103,7 @@ class SuspendUserDialog extends React.Component {
|
||||
{t('suspenduser.title_notify')}
|
||||
</h1>
|
||||
<p className={styles.description}>
|
||||
{t('suspenduser.description_notify', username)}
|
||||
{t('suspenduser.description_notify', username)}
|
||||
</p>
|
||||
<fieldset>
|
||||
<legend className={styles.legend}>{t('suspenduser.write_message')}</legend>
|
||||
|
||||
@@ -139,29 +139,29 @@ export default class UserDetail extends React.Component {
|
||||
<hr/>
|
||||
{
|
||||
selectedCommentIds.length === 0
|
||||
? (
|
||||
<ul className={styles.commentStatuses}>
|
||||
<li className={activeTab === 'all' ? styles.active : ''} onClick={this.showAll}>All</li>
|
||||
<li className={activeTab === 'rejected' ? styles.active : ''} onClick={this.showRejected}>Rejected</li>
|
||||
</ul>
|
||||
)
|
||||
: (
|
||||
<div className={styles.bulkActionGroup}>
|
||||
<Button
|
||||
onClick={bulkAccept}
|
||||
className={styles.bulkAction}
|
||||
cStyle='approve'
|
||||
icon='done'>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={bulkReject}
|
||||
className={styles.bulkAction}
|
||||
cStyle='reject'
|
||||
icon='close'>
|
||||
</Button>
|
||||
{`${selectedCommentIds.length} comments selected`}
|
||||
</div>
|
||||
)
|
||||
? (
|
||||
<ul className={styles.commentStatuses}>
|
||||
<li className={activeTab === 'all' ? styles.active : ''} onClick={this.showAll}>All</li>
|
||||
<li className={activeTab === 'rejected' ? styles.active : ''} onClick={this.showRejected}>Rejected</li>
|
||||
</ul>
|
||||
)
|
||||
: (
|
||||
<div className={styles.bulkActionGroup}>
|
||||
<Button
|
||||
onClick={bulkAccept}
|
||||
className={styles.bulkAction}
|
||||
cStyle='approve'
|
||||
icon='done'>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={bulkReject}
|
||||
className={styles.bulkAction}
|
||||
cStyle='reject'
|
||||
icon='close'>
|
||||
</Button>
|
||||
{`${selectedCommentIds.length} comments selected`}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div>
|
||||
@@ -190,7 +190,7 @@ export default class UserDetail extends React.Component {
|
||||
className={styles.loadMore}
|
||||
loadMore={loadMore}
|
||||
showLoadMore={hasNextPage}
|
||||
/>
|
||||
/>
|
||||
</Drawer>
|
||||
</ClickOutside>
|
||||
);
|
||||
|
||||
@@ -54,8 +54,8 @@ class UserDetailComment extends React.Component {
|
||||
</span>
|
||||
{
|
||||
(comment.editing && comment.editing.edited)
|
||||
? <span> <span className={styles.editedMarker}>({t('comment.edited')})</span></span>
|
||||
: null
|
||||
? <span> <span className={styles.editedMarker}>({t('comment.edited')})</span></span>
|
||||
: null
|
||||
}
|
||||
|
||||
<div className={styles.badgeBar}>
|
||||
@@ -121,10 +121,10 @@ class UserDetailComment extends React.Component {
|
||||
</div>
|
||||
{flagActions && flagActions.length
|
||||
? <FlagBox
|
||||
actions={flagActions}
|
||||
actionSummaries={flagActionSummaries}
|
||||
viewUserDetail={viewUserDetail}
|
||||
/>
|
||||
actions={flagActions}
|
||||
actionSummaries={flagActionSummaries}
|
||||
viewUserDetail={viewUserDetail}
|
||||
/>
|
||||
: null}
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -13,9 +13,9 @@ const CoralHeader = ({
|
||||
}) => (
|
||||
<Header className={styles.header}>
|
||||
<Logo className={styles.logo} />
|
||||
<div>
|
||||
{
|
||||
auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
|
||||
<div>
|
||||
{
|
||||
auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
|
||||
<Navigation className={styles.nav}>
|
||||
<IndexLink
|
||||
id='dashboardNav'
|
||||
@@ -63,36 +63,36 @@ const CoralHeader = ({
|
||||
</Navigation>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles.rightPanel}>
|
||||
<ul>
|
||||
<li className={styles.settings}>
|
||||
<div>
|
||||
<IconButton name="settings" id="menu-settings"/>
|
||||
<Menu target="menu-settings" align="right">
|
||||
<MenuItem onClick={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="https://github.com/coralproject/talk/releases" target="_blank">
|
||||
}
|
||||
<div className={styles.rightPanel}>
|
||||
<ul>
|
||||
<li className={styles.settings}>
|
||||
<div>
|
||||
<IconButton name="settings" id="menu-settings"/>
|
||||
<Menu target="menu-settings" align="right">
|
||||
<MenuItem onClick={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="https://github.com/coralproject/talk/releases" target="_blank">
|
||||
View latest version
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank">
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank">
|
||||
Report a bug or give feedback
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleLogout}>
|
||||
{t('configure.sign_out')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
{`v${process.env.VERSION}`}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleLogout}>
|
||||
{t('configure.sign_out')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
{`v${process.env.VERSION}`}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Header>
|
||||
);
|
||||
|
||||
|
||||
@@ -55,10 +55,10 @@ class LayoutContainer extends Component {
|
||||
toggleShortcutModal={toggleShortcutModal}
|
||||
{...this.props}
|
||||
>
|
||||
<BanUserDialog />
|
||||
<SuspendUserDialog />
|
||||
<UserDetail />
|
||||
{this.props.children}
|
||||
<BanUserDialog />
|
||||
<SuspendUserDialog />
|
||||
<UserDetail />
|
||||
{this.props.children}
|
||||
</Layout>
|
||||
);
|
||||
} else if (loggedIn) {
|
||||
|
||||
@@ -88,13 +88,13 @@ class UserDetailContainer extends React.Component {
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.isLoadingMore = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.isLoadingMore = false;
|
||||
throw err;
|
||||
});
|
||||
.then(() => {
|
||||
this.isLoadingMore = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.isLoadingMore = false;
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
componentWillReceiveProps(next) {
|
||||
|
||||
@@ -14,21 +14,21 @@ const FlaggedAccounts = (props) => {
|
||||
<div className={styles.mainFlaggedContent}>
|
||||
{
|
||||
hasResults
|
||||
? commenters.map((commenter, index) => {
|
||||
return <User
|
||||
user={commenter}
|
||||
key={index}
|
||||
index={index}
|
||||
modActionButtons={['APPROVE', 'REJECT']}
|
||||
showBanUserDialog={props.showBanUserDialog}
|
||||
showSuspendUserDialog={props.showSuspendUserDialog}
|
||||
showRejectUsernameDialog={props.showRejectUsernameDialog}
|
||||
approveUser={props.approveUser}
|
||||
currentUser={props.currentUser}
|
||||
viewUserDetail={props.viewUserDetail}
|
||||
? commenters.map((commenter, index) => {
|
||||
return <User
|
||||
user={commenter}
|
||||
key={index}
|
||||
index={index}
|
||||
modActionButtons={['APPROVE', 'REJECT']}
|
||||
showBanUserDialog={props.showBanUserDialog}
|
||||
showSuspendUserDialog={props.showSuspendUserDialog}
|
||||
showRejectUsernameDialog={props.showRejectUsernameDialog}
|
||||
approveUser={props.approveUser}
|
||||
currentUser={props.currentUser}
|
||||
viewUserDetail={props.viewUserDetail}
|
||||
/>;
|
||||
})
|
||||
: <EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
|
||||
})
|
||||
: <EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,12 +45,12 @@ const People = ({commenters, searchValue, onSearchChange, ...props}) => {
|
||||
<div className={styles.mainContent}>
|
||||
{
|
||||
hasResults
|
||||
? <Table
|
||||
? <Table
|
||||
headers={tableHeaders}
|
||||
commenters={commenters}
|
||||
onHeaderClickHandler={props.onHeaderClickHandler}
|
||||
/>
|
||||
: <EmptyCard>{t('community.no_results')}</EmptyCard>
|
||||
: <EmptyCard>{t('community.no_results')}</EmptyCard>
|
||||
}
|
||||
<Pager
|
||||
totalPages={props.totalPages}
|
||||
|
||||
@@ -50,9 +50,9 @@ class RejectUsernameDialog extends Component {
|
||||
const next = () => this.setState({stage: stage + 1});
|
||||
const suspend = () => {
|
||||
rejectUsername({id: user.user.id, message: this.state.email})
|
||||
.then(() => {
|
||||
this.props.handleClose();
|
||||
});
|
||||
.then(() => {
|
||||
this.props.handleClose();
|
||||
});
|
||||
};
|
||||
|
||||
const suspendModalActions = [
|
||||
@@ -71,21 +71,21 @@ class RejectUsernameDialog extends Component {
|
||||
const {stage} = this.state;
|
||||
|
||||
return <Dialog
|
||||
className={styles.suspendDialog}
|
||||
id="rejectUsernameDialog"
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
onCancel={handleClose}
|
||||
title={t('reject_username.suspend_user')}>
|
||||
<div className={styles.title}>
|
||||
{t(stages[stage].title, t('reject_username.username'))}
|
||||
</div>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.description}>
|
||||
{t(stages[stage].description, t('reject_username.username'))}
|
||||
</div>
|
||||
{
|
||||
stage === 1 &&
|
||||
className={styles.suspendDialog}
|
||||
id="rejectUsernameDialog"
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
onCancel={handleClose}
|
||||
title={t('reject_username.suspend_user')}>
|
||||
<div className={styles.title}>
|
||||
{t(stages[stage].title, t('reject_username.username'))}
|
||||
</div>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.description}>
|
||||
{t(stages[stage].description, t('reject_username.username'))}
|
||||
</div>
|
||||
{
|
||||
stage === 1 &&
|
||||
<div className={styles.writeContainer}>
|
||||
<div className={styles.emailMessage}>{t('reject_username.write_message')}</div>
|
||||
<div className={styles.emailContainer}>
|
||||
@@ -96,16 +96,16 @@ class RejectUsernameDialog extends Component {
|
||||
onChange={this.onEmailChange}/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className={styles.modalButtons}>
|
||||
{Object.keys(stages[stage].options).map((key, i) => (
|
||||
<Button key={i} onClick={this.onActionClick(stage, i)}>
|
||||
{t(stages[stage].options[key], t('reject_username.username'))}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>;
|
||||
}
|
||||
<div className={styles.modalButtons}>
|
||||
{Object.keys(stages[stage].options).map((key, i) => (
|
||||
<Button key={i} onClick={this.onActionClick(stage, i)}>
|
||||
{t(stages[stage].options[key], t('reject_username.username'))}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onComm
|
||||
<tr>
|
||||
{headers.map((header, i) =>(
|
||||
<th
|
||||
key={i}
|
||||
className="mdl-data-table__cell--non-numeric"
|
||||
onClick={() => onHeaderClickHandler({field: header.field})}>
|
||||
key={i}
|
||||
className="mdl-data-table__cell--non-numeric"
|
||||
onClick={() => onHeaderClickHandler({field: header.field})}>
|
||||
{header.title}
|
||||
</th>
|
||||
))}
|
||||
|
||||
@@ -59,7 +59,7 @@ const User = (props) => {
|
||||
<div className={styles.body}>
|
||||
<div className={styles.flaggedByCount}>
|
||||
<i className="material-icons">flag</i><span className={styles.flaggedByLabel}>{t('community.flags')}({ user.actions.length })</span>:
|
||||
{ user.action_summaries.map(
|
||||
{ user.action_summaries.map(
|
||||
(action, i) => {
|
||||
return <span className={styles.flaggedBy} key={i}>
|
||||
{shortReasons[action.reason]} ({action.count})
|
||||
|
||||
@@ -77,14 +77,14 @@ export const withCommunityQuery = withQuery(gql`
|
||||
}
|
||||
}
|
||||
`, {
|
||||
options: ({params: {action_type = 'FLAG'}}) => {
|
||||
return {
|
||||
variables: {
|
||||
action_type: action_type
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
options: ({params: {action_type = 'FLAG'}}) => {
|
||||
return {
|
||||
variables: {
|
||||
action_type: action_type
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
community: state.community,
|
||||
|
||||
@@ -111,20 +111,20 @@ export default class Configure extends Component {
|
||||
(bool, error) => this.state.errors[error] ? false : bool, this.state.changed);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.leftColumn}>
|
||||
<List onChange={this.changeSection} activeItem={activeSection}>
|
||||
<Item itemId='stream' icon='speaker_notes'>
|
||||
{t('configure.stream_settings')}
|
||||
</Item>
|
||||
<Item itemId='moderation' icon='thumbs_up_down'>
|
||||
{t('configure.moderation_settings')}
|
||||
</Item>
|
||||
<Item itemId='tech' icon='code'>
|
||||
{t('configure.tech_settings')}
|
||||
</Item>
|
||||
</List>
|
||||
<div className={styles.saveBox}>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.leftColumn}>
|
||||
<List onChange={this.changeSection} activeItem={activeSection}>
|
||||
<Item itemId='stream' icon='speaker_notes'>
|
||||
{t('configure.stream_settings')}
|
||||
</Item>
|
||||
<Item itemId='moderation' icon='thumbs_up_down'>
|
||||
{t('configure.moderation_settings')}
|
||||
</Item>
|
||||
<Item itemId='tech' icon='code'>
|
||||
{t('configure.tech_settings')}
|
||||
</Item>
|
||||
</List>
|
||||
<div className={styles.saveBox}>
|
||||
{
|
||||
showSave ?
|
||||
<Button
|
||||
@@ -136,25 +136,25 @@ export default class Configure extends Component {
|
||||
>
|
||||
{t('configure.save_changes')}
|
||||
</Button>
|
||||
:
|
||||
:
|
||||
<Button
|
||||
raised
|
||||
disabled
|
||||
icon='check'
|
||||
full
|
||||
>
|
||||
{t('configure.save_changes')}
|
||||
</Button>
|
||||
{t('configure.save_changes')}
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
{ this.props.saveFetchingError }
|
||||
{ this.props.fetchSettingsError }
|
||||
{ section }
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
{ this.props.saveFetchingError }
|
||||
{ this.props.fetchSettingsError }
|
||||
{ section }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,15 +93,15 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
|
||||
value={settings.charCount}
|
||||
disabled={settings.charCountEnable ? '' : 'disabled'}
|
||||
/>
|
||||
<span>{t('configure.comment_count_text_post')}</span>
|
||||
{
|
||||
errors.charCount &&
|
||||
<span>{t('configure.comment_count_text_post')}</span>
|
||||
{
|
||||
errors.charCount &&
|
||||
<span className={styles.settingsError}>
|
||||
<br/>
|
||||
<Icon name="error_outline"/>
|
||||
{t('configure.comment_count_error')}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -14,19 +14,19 @@ const ActivityWidget = ({assets}) => {
|
||||
<div className={styles.widgetTable}>
|
||||
{
|
||||
assets.length
|
||||
? assets.map((asset) => {
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{asset.commentCount}</p>
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <div className={styles.rowLinkify}>{t('dashboard.no_activity')}</div>
|
||||
? assets.map((asset) => {
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{asset.commentCount}</p>
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <div className={styles.rowLinkify}>{t('dashboard.no_activity')}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,24 +16,24 @@ const FlagWidget = ({assets}) => {
|
||||
<div className={styles.widgetTable}>
|
||||
{
|
||||
assets.length
|
||||
? assets.map((asset) => {
|
||||
let flagSummary = null;
|
||||
if (asset.action_summaries) {
|
||||
flagSummary = asset.action_summaries.find((s) => s.__typename === 'FlagAssetActionSummary');
|
||||
}
|
||||
? assets.map((asset) => {
|
||||
let flagSummary = null;
|
||||
if (asset.action_summaries) {
|
||||
flagSummary = asset.action_summaries.find((s) => s.__typename === 'FlagAssetActionSummary');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/reported/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{flagSummary ? flagSummary.actionCount : 0}</p>
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <div className={styles.rowLinkify}>{t('dashboard.no_flags')}</div>
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/reported/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{flagSummary ? flagSummary.actionCount : 0}</p>
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <div className={styles.rowLinkify}>{t('dashboard.no_flags')}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,20 +15,20 @@ const LikeWidget = ({assets}) => {
|
||||
<div className={styles.widgetTable}>
|
||||
{
|
||||
assets.length
|
||||
? assets.map((asset) => {
|
||||
const likeSummary = asset.action_summaries.find((s) => s.type === 'LikeAssetActionSummary');
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{likeSummary ? likeSummary.actionCount : 0}</p>
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <div className={styles.rowLinkify}>{t('dashboard.no_likes')}</div>
|
||||
? assets.map((asset) => {
|
||||
const likeSummary = asset.action_summaries.find((s) => s.type === 'LikeAssetActionSummary');
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{likeSummary ? likeSummary.actionCount : 0}</p>
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <div className={styles.rowLinkify}>{t('dashboard.no_likes')}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,15 +42,15 @@ export const witDashboardQuery = withQuery(gql`
|
||||
}
|
||||
}
|
||||
`, {
|
||||
options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => {
|
||||
return {
|
||||
variables: {
|
||||
from: dashboardWindowStart,
|
||||
to: dashboardWindowEnd
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => {
|
||||
return {
|
||||
variables: {
|
||||
from: dashboardWindowStart,
|
||||
to: dashboardWindowEnd
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
|
||||
@@ -19,7 +19,7 @@ const InitialStep = (props) => {
|
||||
showErrors={install.showErrors}
|
||||
errorMsg={install.errors.email}
|
||||
noValidate
|
||||
/>
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className={styles.textField}
|
||||
@@ -29,7 +29,7 @@ const InitialStep = (props) => {
|
||||
onChange={handleUserChange}
|
||||
showErrors={install.showErrors}
|
||||
errorMsg={install.errors.username}
|
||||
/>
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className={styles.textField}
|
||||
@@ -39,7 +39,7 @@ const InitialStep = (props) => {
|
||||
onChange={handleUserChange}
|
||||
showErrors={install.showErrors}
|
||||
errorMsg={install.errors.password}
|
||||
/>
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className={styles.textField}
|
||||
@@ -49,13 +49,13 @@ const InitialStep = (props) => {
|
||||
onChange={handleUserChange}
|
||||
showErrors={install.showErrors}
|
||||
errorMsg={install.errors.confirmPassword}
|
||||
/>
|
||||
/>
|
||||
|
||||
{
|
||||
!props.install.isLoading ?
|
||||
<Button cStyle='black' type="submit" full>{t('install.create.save')}</Button>
|
||||
:
|
||||
<Spinner />
|
||||
<Button cStyle='black' type="submit" full>{t('install.create.save')}</Button>
|
||||
:
|
||||
<Spinner />
|
||||
}
|
||||
{props.install.installRequest === 'FAILURE' && <div className={styles.error}>Error: {props.install.installRequestError}</div>}
|
||||
</form>
|
||||
|
||||
@@ -91,8 +91,8 @@ class Comment extends React.Component {
|
||||
</span>
|
||||
{
|
||||
(comment.editing && comment.editing.edited)
|
||||
? <span> <span className={styles.editedMarker}>({t('comment.edited')})</span></span>
|
||||
: null
|
||||
? <span> <span className={styles.editedMarker}>({t('comment.edited')})</span></span>
|
||||
: null
|
||||
}
|
||||
{currentUserId !== comment.user.id &&
|
||||
<ActionsMenu icon="not_interested">
|
||||
@@ -192,10 +192,10 @@ class Comment extends React.Component {
|
||||
/>
|
||||
{flagActions && flagActions.length
|
||||
? <FlagBox
|
||||
actions={flagActions}
|
||||
actionSummaries={flagActionSummaries}
|
||||
viewUserDetail={viewUserDetail}
|
||||
/>
|
||||
actions={flagActions}
|
||||
actionSummaries={flagActionSummaries}
|
||||
viewUserDetail={viewUserDetail}
|
||||
/>
|
||||
: null}
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -97,7 +97,7 @@ class ModerationQueue extends React.Component {
|
||||
rejectComment={props.rejectComment}
|
||||
currentAsset={props.currentAsset}
|
||||
currentUserId={this.props.currentUserId}
|
||||
/>;
|
||||
/>;
|
||||
})
|
||||
}
|
||||
</CSSTransitionGroup>
|
||||
@@ -110,7 +110,7 @@ class ModerationQueue extends React.Component {
|
||||
<LoadMore
|
||||
loadMore={this.loadMore}
|
||||
showLoadMore={comments.length < commentCount}
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,20 +61,20 @@ const StorySearch = (props) => {
|
||||
|
||||
{
|
||||
loading
|
||||
? <Spinner />
|
||||
: assets.map((story, i) => {
|
||||
const storyOpen = story.closedAt === null || new Date(story.closedAt) > new Date();
|
||||
? <Spinner />
|
||||
: assets.map((story, i) => {
|
||||
const storyOpen = story.closedAt === null || new Date(story.closedAt) > new Date();
|
||||
|
||||
return <Story
|
||||
key={i}
|
||||
id={story.id}
|
||||
title={story.title}
|
||||
createdAt={new Date(story.created_at).toISOString()}
|
||||
open={storyOpen}
|
||||
author={story.author}
|
||||
goToStory={props.goToStory}
|
||||
/>;
|
||||
})
|
||||
return <Story
|
||||
key={i}
|
||||
id={story.id}
|
||||
title={story.title}
|
||||
createdAt={new Date(story.created_at).toISOString()}
|
||||
open={storyOpen}
|
||||
author={story.author}
|
||||
goToStory={props.goToStory}
|
||||
/>;
|
||||
})
|
||||
}
|
||||
|
||||
{assets.length === 0 && <div className={styles.noResults}>No results</div>}
|
||||
|
||||
@@ -98,14 +98,14 @@ export const withAssetSearchQuery = withQuery(gql`
|
||||
}
|
||||
}
|
||||
`, {
|
||||
options: ({moderation: {storySearchString = ''}}) => {
|
||||
return {
|
||||
variables: {
|
||||
value: storySearchString
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
options: ({moderation: {storySearchString = ''}}) => {
|
||||
return {
|
||||
variables: {
|
||||
value: storySearchString
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
|
||||
@@ -134,20 +134,20 @@ export default class Stories extends Component {
|
||||
<Radio value='closed'>{t('streams.closed')}</Radio>
|
||||
</RadioGroup>
|
||||
<div className={styles.optionHeader}>{t('streams.sort_by')}</div>
|
||||
<RadioGroup
|
||||
name='sort by'
|
||||
value={sort}
|
||||
childContainer='div'
|
||||
onChange={this.onSettingChange('sort')}
|
||||
className={styles.radioGroup}
|
||||
>
|
||||
<Radio value='desc'>{t('streams.newest')}</Radio>
|
||||
<Radio value='asc'>{t('streams.oldest')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<RadioGroup
|
||||
name='sort by'
|
||||
value={sort}
|
||||
childContainer='div'
|
||||
onChange={this.onSettingChange('sort')}
|
||||
className={styles.radioGroup}
|
||||
>
|
||||
<Radio value='desc'>{t('streams.newest')}</Radio>
|
||||
<Radio value='asc'>{t('streams.oldest')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{
|
||||
assetsIds.length
|
||||
? <div className={styles.mainContent}>
|
||||
? <div className={styles.mainContent}>
|
||||
<DataTable className={styles.streamsTable} rows={assetsIds} onClick={this.goToModeration}>
|
||||
<TableHeader name="title" cellFormatter={this.renderTitle}>{t('streams.article')}</TableHeader>
|
||||
<TableHeader name="publication_date" cellFormatter={this.renderDate}>
|
||||
@@ -162,7 +162,7 @@ export default class Stories extends Component {
|
||||
page={this.state.page}
|
||||
onNewPageHandler={this.onPageClick} />
|
||||
</div>
|
||||
: <EmptyCard>{t('streams.empty_result')}</EmptyCard>
|
||||
: <EmptyCard>{t('streams.empty_result')}</EmptyCard>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -56,13 +56,13 @@ export default ({handleChange, handleApply, changed, ...props}) => (
|
||||
title: t('configure.enable_questionbox'),
|
||||
description: t('configure.enable_questionbox_description')
|
||||
}} />
|
||||
{
|
||||
props.questionBoxEnable && <QuestionBoxBuilder
|
||||
questionBoxIcon={props.questionBoxIcon}
|
||||
questionBoxContent={props.questionBoxContent}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
}
|
||||
{
|
||||
props.questionBoxEnable && <QuestionBoxBuilder
|
||||
questionBoxIcon={props.questionBoxIcon}
|
||||
questionBoxContent={props.questionBoxContent}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -41,39 +41,39 @@ class QuestionBoxBuilder extends React.Component {
|
||||
|
||||
<ul className={styles.qbItemIconList}>
|
||||
<li className={cn(
|
||||
styles.qbItemIcon,
|
||||
{[styles.qbItemIconActive]: questionBoxIcon === 'default'}
|
||||
)}
|
||||
id="qboxicon"
|
||||
onClick={handleChange}
|
||||
data-icon="default" >
|
||||
styles.qbItemIcon,
|
||||
{[styles.qbItemIconActive]: questionBoxIcon === 'default'}
|
||||
)}
|
||||
id="qboxicon"
|
||||
onClick={handleChange}
|
||||
data-icon="default" >
|
||||
<DefaultIcon className={styles.defaultIcon} />
|
||||
</li>
|
||||
<li className={cn(
|
||||
styles.qbItemIcon,
|
||||
{[styles.qbItemIconActive]: questionBoxIcon === 'forum'}
|
||||
)}
|
||||
id="qboxicon"
|
||||
onClick={handleChange}
|
||||
data-icon="forum" >
|
||||
styles.qbItemIcon,
|
||||
{[styles.qbItemIconActive]: questionBoxIcon === 'forum'}
|
||||
)}
|
||||
id="qboxicon"
|
||||
onClick={handleChange}
|
||||
data-icon="forum" >
|
||||
<Icon name="forum" />
|
||||
</li>
|
||||
<li className={cn(
|
||||
styles.qbItemIcon,
|
||||
{[styles.qbItemIconActive]: questionBoxIcon === 'build'}
|
||||
)}
|
||||
id="qboxicon"
|
||||
onClick={handleChange}
|
||||
data-icon="build" >
|
||||
styles.qbItemIcon,
|
||||
{[styles.qbItemIconActive]: questionBoxIcon === 'build'}
|
||||
)}
|
||||
id="qboxicon"
|
||||
onClick={handleChange}
|
||||
data-icon="build" >
|
||||
<Icon name="build" />
|
||||
</li>
|
||||
<li className={cn(
|
||||
styles.qbItemIcon,
|
||||
{[styles.qbItemIconActive]: questionBoxIcon === 'format_quote'}
|
||||
)}
|
||||
id="qboxicon"
|
||||
onClick={handleChange}
|
||||
data-icon="format_quote" >
|
||||
styles.qbItemIcon,
|
||||
{[styles.qbItemIconActive]: questionBoxIcon === 'format_quote'}
|
||||
)}
|
||||
id="qboxicon"
|
||||
onClick={handleChange}
|
||||
data-icon="format_quote" >
|
||||
<Icon name="format_quote" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -115,7 +115,7 @@ class ConfigureStreamContainer extends Component {
|
||||
/>
|
||||
<hr />
|
||||
<h3>{closedAt === 'open' ? t('configure.close') : t('configure.open')} {t('configure.comment_stream')}</h3>
|
||||
{(closedAt === 'open' && closedTimeout) ? <p>{t('configure.comment_stream_will_close')} {this.getClosedIn()}.</p> : ''}
|
||||
{(closedAt === 'open' && closedTimeout) ? <p>{t('configure.comment_stream_will_close')} {this.getClosedIn()}.</p> : ''}
|
||||
<CloseCommentsInfo
|
||||
onClick={this.toggleStatus}
|
||||
status={closedAt}
|
||||
|
||||
@@ -63,7 +63,7 @@ class AllCommentsPane extends React.Component {
|
||||
}
|
||||
|
||||
if (
|
||||
prevComments && nextComments &&
|
||||
prevComments && nextComments &&
|
||||
nextComments.nodes.length < prevComments.nodes.length
|
||||
) {
|
||||
|
||||
@@ -160,31 +160,31 @@ class AllCommentsPane extends React.Component {
|
||||
return commentIsIgnored(comment)
|
||||
? <IgnoredCommentTombstone key={comment.id} />
|
||||
: <Comment
|
||||
commentClassNames={commentClassNames}
|
||||
data={data}
|
||||
root={root}
|
||||
disableReply={disableReply}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
activeReplyBox={activeReplyBox}
|
||||
notify={notify}
|
||||
depth={0}
|
||||
postComment={postComment}
|
||||
asset={asset}
|
||||
currentUser={currentUser}
|
||||
postFlag={postFlag}
|
||||
postDontAgree={postDontAgree}
|
||||
ignoreUser={ignoreUser}
|
||||
commentIsIgnored={commentIsIgnored}
|
||||
loadMore={loadNewReplies}
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
charCountEnable={charCountEnable}
|
||||
maxCharCount={maxCharCount}
|
||||
editComment={editComment}
|
||||
emit={emit}
|
||||
/>;
|
||||
commentClassNames={commentClassNames}
|
||||
data={data}
|
||||
root={root}
|
||||
disableReply={disableReply}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
activeReplyBox={activeReplyBox}
|
||||
notify={notify}
|
||||
depth={0}
|
||||
postComment={postComment}
|
||||
asset={asset}
|
||||
currentUser={currentUser}
|
||||
postFlag={postFlag}
|
||||
postDontAgree={postDontAgree}
|
||||
ignoreUser={ignoreUser}
|
||||
commentIsIgnored={commentIsIgnored}
|
||||
loadMore={loadNewReplies}
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
charCountEnable={charCountEnable}
|
||||
maxCharCount={maxCharCount}
|
||||
editComment={editComment}
|
||||
emit={emit}
|
||||
/>;
|
||||
})}
|
||||
</TransitionGroup>
|
||||
<LoadMore
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class Comment extends React.Component {
|
||||
const {comment: {replies: prevReplies}} = this.props;
|
||||
const {comment: {replies: nextReplies}} = next;
|
||||
if (
|
||||
prevReplies && nextReplies &&
|
||||
prevReplies && nextReplies &&
|
||||
nextReplies.nodes.length < prevReplies.nodes.length
|
||||
) {
|
||||
|
||||
@@ -343,7 +343,7 @@ export default class Comment extends React.Component {
|
||||
|
||||
const view = this.getVisibileReplies();
|
||||
|
||||
// Inactive comments can be viewed by moderators and admins (e.g. using permalinks).
|
||||
// Inactive comments can be viewed by moderators and admins (e.g. using permalinks).
|
||||
const isActive = isCommentActive(comment.status);
|
||||
|
||||
const {loadingState} = this.state;
|
||||
@@ -459,8 +459,8 @@ export default class Comment extends React.Component {
|
||||
<PubDate created_at={comment.created_at} className={'talk-stream-comment-published-date'} />
|
||||
{
|
||||
(comment.editing && comment.editing.edited)
|
||||
? <span> <span className={styles.editedMarker}>({t('comment.edited')})</span></span>
|
||||
: null
|
||||
? <span> <span className={styles.editedMarker}>({t('comment.edited')})</span></span>
|
||||
: null
|
||||
}
|
||||
</span>
|
||||
|
||||
@@ -500,7 +500,7 @@ export default class Comment extends React.Component {
|
||||
<div className={styles.content}>
|
||||
{
|
||||
this.state.isEditing
|
||||
? <EditableCommentContent
|
||||
? <EditableCommentContent
|
||||
editComment={this.editComment}
|
||||
notify={notify}
|
||||
comment={comment}
|
||||
@@ -509,14 +509,14 @@ export default class Comment extends React.Component {
|
||||
maxCharCount={maxCharCount}
|
||||
parentId={parentId}
|
||||
stopEditing={this.stopEditing}
|
||||
/>
|
||||
: <div>
|
||||
<Slot
|
||||
fill="commentContent"
|
||||
defaultComponent={CommentContent}
|
||||
{...slotProps}
|
||||
queryData={queryData}
|
||||
/>
|
||||
: <div>
|
||||
<Slot
|
||||
fill="commentContent"
|
||||
defaultComponent={CommentContent}
|
||||
{...slotProps}
|
||||
queryData={queryData}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -570,23 +570,23 @@ export default class Comment extends React.Component {
|
||||
|
||||
{activeReplyBox === comment.id
|
||||
? <ReplyBox
|
||||
commentPostedHandler={this.commentPostedHandler}
|
||||
charCountEnable={charCountEnable}
|
||||
maxCharCount={maxCharCount}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
parentId={(depth < THREADING_LEVEL) ? comment.id : parentId}
|
||||
notify={notify}
|
||||
postComment={postComment}
|
||||
currentUser={currentUser}
|
||||
assetId={asset.id}
|
||||
/>
|
||||
commentPostedHandler={this.commentPostedHandler}
|
||||
charCountEnable={charCountEnable}
|
||||
maxCharCount={maxCharCount}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
parentId={(depth < THREADING_LEVEL) ? comment.id : parentId}
|
||||
notify={notify}
|
||||
postComment={postComment}
|
||||
currentUser={currentUser}
|
||||
assetId={asset.id}
|
||||
/>
|
||||
: null}
|
||||
|
||||
<TransitionGroup>
|
||||
{view.map((reply) => {
|
||||
return commentIsIgnored(reply)
|
||||
? <IgnoredCommentTombstone key={reply.id} />
|
||||
: <CommentContainer
|
||||
{view.map((reply) => {
|
||||
return commentIsIgnored(reply)
|
||||
? <IgnoredCommentTombstone key={reply.id} />
|
||||
: <CommentContainer
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
setActiveReplyBox={setActiveReplyBox}
|
||||
@@ -614,7 +614,7 @@ export default class Comment extends React.Component {
|
||||
comment={reply}
|
||||
emit={emit}
|
||||
/>;
|
||||
})}
|
||||
})}
|
||||
</TransitionGroup>
|
||||
<div className="talk-load-more-replies">
|
||||
<LoadMore
|
||||
|
||||
@@ -136,15 +136,15 @@ export class EditableCommentContent extends React.Component {
|
||||
<span className={styles.editWindowRemaining}>
|
||||
{
|
||||
this.isEditWindowExpired()
|
||||
? <span>
|
||||
? <span>
|
||||
{t('edit_comment.edit_window_expired')}
|
||||
{
|
||||
typeof this.props.stopEditing === 'function'
|
||||
? <span> <a className={styles.link} onClick={this.props.stopEditing}>{t('edit_comment.edit_window_expired_close')}</a></span>
|
||||
: null
|
||||
? <span> <a className={styles.link} onClick={this.props.stopEditing}>{t('edit_comment.edit_window_expired_close')}</a></span>
|
||||
: null
|
||||
}
|
||||
</span>
|
||||
: <span>
|
||||
: <span>
|
||||
<Icon name="timer" className={styles.timerIcon}/> {t('edit_comment.edit_window_timer_prefix')}
|
||||
<CountdownSeconds
|
||||
until={this.getEditableUntil()}
|
||||
|
||||
@@ -7,12 +7,12 @@ const NewCount = ({count, loadMore}) => {
|
||||
return <div className='talk-new-comments talk-load-more'>
|
||||
{
|
||||
count ?
|
||||
<Button onClick={loadMore}>
|
||||
{count === 1
|
||||
? t('framework.new_count', count, t('framework.comment'))
|
||||
: t('framework.new_count', count, t('framework.comments'))}
|
||||
</Button>
|
||||
: null
|
||||
<Button onClick={loadMore}>
|
||||
{count === 1
|
||||
? t('framework.new_count', count, t('framework.comment'))
|
||||
: t('framework.new_count', count, t('framework.comments'))}
|
||||
</Button>
|
||||
: null
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
|
||||
@@ -256,16 +256,16 @@ class Stream extends React.Component {
|
||||
|
||||
{open
|
||||
? <div id="commentBox">
|
||||
<InfoBox
|
||||
content={asset.settings.infoBoxContent}
|
||||
enable={asset.settings.infoBoxEnable}
|
||||
/>
|
||||
<QuestionBox
|
||||
content={asset.settings.questionBoxContent}
|
||||
enable={asset.settings.questionBoxEnable}
|
||||
icon={asset.settings.questionBoxIcon}
|
||||
/>
|
||||
{!banned &&
|
||||
<InfoBox
|
||||
content={asset.settings.infoBoxContent}
|
||||
enable={asset.settings.infoBoxEnable}
|
||||
/>
|
||||
<QuestionBox
|
||||
content={asset.settings.questionBoxContent}
|
||||
enable={asset.settings.questionBoxEnable}
|
||||
icon={asset.settings.questionBoxIcon}
|
||||
/>
|
||||
{!banned &&
|
||||
temporarilySuspended &&
|
||||
<RestrictedMessageBox>
|
||||
{t(
|
||||
@@ -274,13 +274,13 @@ class Stream extends React.Component {
|
||||
timeago(user.suspension.until)
|
||||
)}
|
||||
</RestrictedMessageBox>}
|
||||
{banned &&
|
||||
{banned &&
|
||||
<SuspendedAccount
|
||||
canEditName={user && user.canEditName}
|
||||
editName={editName}
|
||||
currentUsername={user.username}
|
||||
/>}
|
||||
{showCommentBox &&
|
||||
{showCommentBox &&
|
||||
<CommentBox
|
||||
notify={notify}
|
||||
postComment={postComment}
|
||||
@@ -293,7 +293,7 @@ class Stream extends React.Component {
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
: <p>{asset.settings.closedMessage}</p>}
|
||||
|
||||
<Slot
|
||||
|
||||
@@ -44,40 +44,40 @@ class SuspendedAccount extends Component {
|
||||
|
||||
return <RestrictedMessageBox>
|
||||
<span>{
|
||||
canEditName ?
|
||||
canEditName ?
|
||||
t('framework.edit_name.msg')
|
||||
:
|
||||
<span>
|
||||
<b>{t('framework.banned_account_header')}</b><br/> {t('framework.banned_account_body')}
|
||||
</span>
|
||||
}</span>
|
||||
}</span>
|
||||
{
|
||||
canEditName ?
|
||||
<div>
|
||||
<div className={styles.alert}>
|
||||
{alert}
|
||||
</div>
|
||||
<label
|
||||
htmlFor='username'
|
||||
className="screen-reader-text"
|
||||
aria-hidden={true}>
|
||||
{t('framework.edit_name.label')}
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
className={styles.editNameInput}
|
||||
value={username}
|
||||
placeholder={t('framework.edit_name.label')}
|
||||
id='username'
|
||||
onChange={(e) => this.setState({username: e.target.value})}
|
||||
rows={3}/><br/>
|
||||
<Button
|
||||
onClick={this.onSubmitClick}>
|
||||
{
|
||||
t('framework.edit_name.button')
|
||||
}
|
||||
</Button>
|
||||
</div> : null
|
||||
<div>
|
||||
<div className={styles.alert}>
|
||||
{alert}
|
||||
</div>
|
||||
<label
|
||||
htmlFor='username'
|
||||
className="screen-reader-text"
|
||||
aria-hidden={true}>
|
||||
{t('framework.edit_name.label')}
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
className={styles.editNameInput}
|
||||
value={username}
|
||||
placeholder={t('framework.edit_name.label')}
|
||||
id='username'
|
||||
onChange={(e) => this.setState({username: e.target.value})}
|
||||
rows={3}/><br/>
|
||||
<Button
|
||||
onClick={this.onSubmitClick}>
|
||||
{
|
||||
t('framework.edit_name.button')
|
||||
}
|
||||
</Button>
|
||||
</div> : null
|
||||
}
|
||||
</RestrictedMessageBox>;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export class TopRightMenu extends React.Component {
|
||||
user={comment.user}
|
||||
cancel={reset}
|
||||
ignoreUser={ignoreUserAndCloseMenuAndNotifyOnError}
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
</Toggleable>
|
||||
);
|
||||
|
||||
@@ -60,7 +60,7 @@ function findAndInsertComment(parent, comment) {
|
||||
[connectionField]: {
|
||||
nodes: {
|
||||
$apply: (nodes) =>
|
||||
nodes.map((node) => findAndInsertComment(node, comment))
|
||||
nodes.map((node) => findAndInsertComment(node, comment))
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -176,7 +176,7 @@ function findAndInsertFetchedComments(parent, comments, parent_id) {
|
||||
[connectionField]: {
|
||||
nodes: {
|
||||
$apply: (nodes) =>
|
||||
nodes.map((node) => findAndInsertFetchedComments(node, comments, parent_id))
|
||||
nodes.map((node) => findAndInsertFetchedComments(node, comments, parent_id))
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -84,8 +84,8 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => {
|
||||
|
||||
const updateCallbacks =
|
||||
[base.update || config.options.update]
|
||||
.concat(...configs.map((cfg) => cfg.update))
|
||||
.filter((i) => i);
|
||||
.concat(...configs.map((cfg) => cfg.update))
|
||||
.filter((i) => i);
|
||||
|
||||
const update = (proxy, result) => {
|
||||
if (getResponseErrors(result)) {
|
||||
@@ -101,28 +101,28 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => {
|
||||
base.updateQueries || config.options.updateQueries,
|
||||
...configs.map((cfg) => cfg.updateQueries)
|
||||
]
|
||||
.filter((i) => i)
|
||||
.reduce((res, map) => {
|
||||
Object.keys(map).forEach((key) => {
|
||||
if (!(key in res)) {
|
||||
res[key] = (prev, result) => {
|
||||
if (getResponseErrors(result.mutationResult)) {
|
||||
.filter((i) => i)
|
||||
.reduce((res, map) => {
|
||||
Object.keys(map).forEach((key) => {
|
||||
if (!(key in res)) {
|
||||
res[key] = (prev, result) => {
|
||||
if (getResponseErrors(result.mutationResult)) {
|
||||
|
||||
// Do not run updates when we have mutation errors.
|
||||
return prev;
|
||||
}
|
||||
return map[key](prev, result) || prev;
|
||||
};
|
||||
} else {
|
||||
const existing = res[key];
|
||||
res[key] = (prev, result) => {
|
||||
const next = existing(prev, result);
|
||||
return map[key](next, result) || next;
|
||||
};
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}, {});
|
||||
return prev;
|
||||
}
|
||||
return map[key](prev, result) || prev;
|
||||
};
|
||||
} else {
|
||||
const existing = res[key];
|
||||
res[key] = (prev, result) => {
|
||||
const next = existing(prev, result);
|
||||
return map[key](next, result) || next;
|
||||
};
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
const wrappedConfig = {
|
||||
variables,
|
||||
|
||||
@@ -129,18 +129,18 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => {
|
||||
...lmArgs,
|
||||
query: resolvedDocument,
|
||||
})
|
||||
.then((res) => {
|
||||
this.context.eventEmitter.emit(
|
||||
`query.${name}.fetchMore.${fetchName}.success`,
|
||||
{variables: lmArgs.variables, data: res.data});
|
||||
return Promise.resolve(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.context.eventEmitter.emit(
|
||||
`query.${name}.fetchMore.${fetchName}.error`,
|
||||
{variables: lmArgs.variables, error: err});
|
||||
throw err;
|
||||
});
|
||||
.then((res) => {
|
||||
this.context.eventEmitter.emit(
|
||||
`query.${name}.fetchMore.${fetchName}.success`,
|
||||
{variables: lmArgs.variables, data: res.data});
|
||||
return Promise.resolve(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.context.eventEmitter.emit(
|
||||
`query.${name}.fetchMore.${fetchName}.error`,
|
||||
{variables: lmArgs.variables, error: err});
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -171,8 +171,8 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => {
|
||||
const configs = this.graphqlRegistry.getQueryOptions(name);
|
||||
const reducerCallbacks =
|
||||
[base.reducer || ((i) => i)]
|
||||
.concat(...configs.map((cfg) => cfg.reducer))
|
||||
.filter((i) => i);
|
||||
.concat(...configs.map((cfg) => cfg.reducer))
|
||||
.filter((i) => i);
|
||||
|
||||
const reducer = withSkipOnErrors(
|
||||
reducerCallbacks.reduce(
|
||||
|
||||
@@ -81,11 +81,11 @@ class PluginsService {
|
||||
const pluginConfig = reduxState.config.plugin_config || emptyConfig;
|
||||
return flatten(this.plugins
|
||||
|
||||
// Filter out components that have slots and have been disabled in `plugin_config`
|
||||
.filter((o) => o.module.slots && (!pluginConfig || !pluginConfig[o.name] || !pluginConfig[o.name].disable_components))
|
||||
// Filter out components that have slots and have been disabled in `plugin_config`
|
||||
.filter((o) => o.module.slots && (!pluginConfig || !pluginConfig[o.name] || !pluginConfig[o.name].disable_components))
|
||||
|
||||
.filter((o) => o.module.slots[slot])
|
||||
.map((o) => o.module.slots[slot])
|
||||
.filter((o) => o.module.slots[slot])
|
||||
.map((o) => o.module.slots[slot])
|
||||
)
|
||||
.filter((component) => {
|
||||
if(!component.isExcluded) {
|
||||
@@ -114,8 +114,8 @@ class PluginsService {
|
||||
config: pluginConfig,
|
||||
...(
|
||||
component.fragments
|
||||
? pick(queryData, Object.keys(component.fragments))
|
||||
: withWarnings(component, queryData)
|
||||
? pick(queryData, Object.keys(component.fragments))
|
||||
: withWarnings(component, queryData)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const getMyActionSummary = (type, comment) => {
|
||||
.find((a) => a.current_user);
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* getActionSummary
|
||||
* retrieves the action summaries based on the type and the comment
|
||||
* array could be length > 1, as in the case of FlagActionSummary
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
* getReliability
|
||||
* retrieves reliability value as string
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, {Component, PropTypes} from 'react';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import styles from './IgnoredUsers.css';
|
||||
|
||||
export class IgnoredUsers extends Component {
|
||||
class IgnoredUsers extends Component {
|
||||
static propTypes = {
|
||||
users: PropTypes.arrayOf(PropTypes.shape({
|
||||
username: PropTypes.string,
|
||||
|
||||
@@ -79,12 +79,12 @@ class ProfileContainer extends Component {
|
||||
|
||||
{me.ignoredUsers && me.ignoredUsers.length
|
||||
? <div>
|
||||
<h3>{t('framework.ignored_users')}</h3>
|
||||
<IgnoredUsers
|
||||
users={me.ignoredUsers}
|
||||
stopIgnoring={stopIgnoringUser}
|
||||
/>
|
||||
</div>
|
||||
<h3>{t('framework.ignored_users')}</h3>
|
||||
<IgnoredUsers
|
||||
users={me.ignoredUsers}
|
||||
stopIgnoring={stopIgnoringUser}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -3,34 +3,34 @@ import styles from './CoralLogo.css';
|
||||
|
||||
const CoralLogo = ({className = ''}) => (
|
||||
<svg className={`${styles.base} ${className}`} viewBox='0 0 381 391' version='1.1 xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink'>
|
||||
<g stroke='none' strokeWidth='1' fill='none' fillRule='evenodd'>
|
||||
<g id='Wordmark-Round' className={styles.mark} transform='translate(-1833.000000, -411.000000)' strokeWidth='22' strokeLinecap='round' strokeLinejoin='round'>
|
||||
<g id='coralProjectLogo-2-Copy-2' transform='translate(1842.000000, 421.000000)'>
|
||||
<g id='Layer_2' transform='translate(2.268750, 1.133903)'>
|
||||
<rect id='Rectangle-1' fill='#F47E6B' x='0' y='0' width='358.4625' height='368.518519' rx='40'>
|
||||
</rect>
|
||||
<path d='M0.226875,105.679772 C13.7259375,122.688319 41.29125,131.986325 56.71875,116.792023 C67.0415625,106.586895 62.5040625,93.0934473 76.910625,63.3851852 C83.94375,48.7578348 90.523125,38.3259259 101.980313,37.5321937 C115.139063,36.6250712 131.814375,50.3452991 134.31,69.3948718 C137.826563,95.1344729 115.479375,105.339601 121.15125,123.822222 C127.390313,144.119088 157.110938,140.377208 166.52625,165.096296 C170.042813,174.394302 171.630938,190.382336 163.463438,198.319658 C149.170313,212.266667 120.584063,186.073504 80.7675,198.319658 C73.280625,200.587464 56.3784375,205.803419 51.500625,219.523647 C46.73625,233.130484 55.584375,251.046154 67.60875,260.797721 C93.245625,281.661538 119.79,254.674644 159.379688,271.909972 C181.840313,281.661538 203.053125,303.319088 208.725,330.305983 C211.674375,344.593162 209.6325,356.952707 207.704063,364.549858' id='Shape'>
|
||||
</path>
|
||||
</g>
|
||||
<g id='Layer_3' transform='translate(43.106250, 289.145299)'>
|
||||
<path d='M90.4096875,72.5698006 C78.8390625,41.3874644 64.9996875,31.4091168 54.1096875,28.234188 C41.8584375,24.7190883 30.7415625,29.0279202 17.8096875,21.2039886 C8.394375,15.4210826 3.403125,6.57663818 0.680625,0'id='Shape'></path>
|
||||
</g><g id='Layer_4' transform='translate(220.068750, 209.772080)'>
|
||||
<path d='M81.7884375,152.963533 C74.9821875,122.007977 61.8234375,104.77265 50.593125,94.5675214 C31.8759375,77.6723647 14.746875,77.1054131 5.218125,57.2621083 C4.4240625,55.5612536 -5.7853125,33.4501425 5.218125,17.008547 C14.52,3.06153846 33.350625,1.47407407 41.518125,0.907122507 C64.3190625,-0.907122507 73.280625,9.52478632 100.959375,13.039886 C117.180938,15.0809117 130.793438,13.4934473 139.30125,12.0193732'id='Shape'></path>
|
||||
</g>
|
||||
<g id='Layer_5' transform='translate(285.862500, 289.145299)'>
|
||||
<path d='M74.415,2.04102564 C66.3609375,0.793732194 51.046875,-0.566951567 33.12375,5.1025641 C17.5828125,9.97834758 6.80625,18.0290598 0.9075,23.2450142'id='Shape'></path>
|
||||
</g>
|
||||
<g id='Layer_6' transform='translate(174.693750, 1.133903)'>
|
||||
<path d='M184.109063,151.035897 C170.950313,174.507692 158.699063,180.290598 149.850938,181.311111 C133.85625,183.011966 129.091875,168.724786 104.475938,163.168661 C79.2928125,157.499145 72.2596875,169.745299 52.0678125,163.168661 C30.0609375,155.911681 18.7171875,134.821083 12.705,123.822222 C-1.588125,97.4022792 -0.3403125,71.6626781 0.5671875,51.2524217 C1.588125,29.4814815 6.125625,12.0193732 9.6421875,0.907122507'id='Shape'></path>
|
||||
<path d='M183.541875,69.3948718 C170.496563,56.6951567 157.564688,45.8096866 149.28375,39.0062678 C143.385,34.1304843 134.990625,33.2233618 128.297813,36.8518519 C128.070938,36.9652422 127.844063,37.0786325 127.730625,37.1920228 C122.739375,40.1401709 119.449688,45.3561254 118.8825,51.1390313 C118.201875,59.0763533 117.0675,71.4358974 114.912188,82.8883191 C114.118125,87.0837607 107.085,103.865527 89.7290625,110.668946 C77.2509375,115.544729 62.0503125,110.668946 53.4290625,101.597721 C40.38375,87.7641026 44.8078125,66.9002849 47.416875,55.2210826 C53.5425,26.760114 73.3940625,9.07122507 82.6959375,1.81424501'id='Shape'></path>
|
||||
</g>
|
||||
<g id='Layer_7' transform='translate(3.403125, 179.156695)'>
|
||||
<path d='M0.1134375,0.226780627 C2.949375,6.34985755 8.394375,16.1014245 18.2634375,25.3994302 C27.67875,34.2438746 37.3209375,39.0062678 43.4465625,41.5008547'id='Shape'></path>
|
||||
<g stroke='none' strokeWidth='1' fill='none' fillRule='evenodd'>
|
||||
<g id='Wordmark-Round' className={styles.mark} transform='translate(-1833.000000, -411.000000)' strokeWidth='22' strokeLinecap='round' strokeLinejoin='round'>
|
||||
<g id='coralProjectLogo-2-Copy-2' transform='translate(1842.000000, 421.000000)'>
|
||||
<g id='Layer_2' transform='translate(2.268750, 1.133903)'>
|
||||
<rect id='Rectangle-1' fill='#F47E6B' x='0' y='0' width='358.4625' height='368.518519' rx='40'>
|
||||
</rect>
|
||||
<path d='M0.226875,105.679772 C13.7259375,122.688319 41.29125,131.986325 56.71875,116.792023 C67.0415625,106.586895 62.5040625,93.0934473 76.910625,63.3851852 C83.94375,48.7578348 90.523125,38.3259259 101.980313,37.5321937 C115.139063,36.6250712 131.814375,50.3452991 134.31,69.3948718 C137.826563,95.1344729 115.479375,105.339601 121.15125,123.822222 C127.390313,144.119088 157.110938,140.377208 166.52625,165.096296 C170.042813,174.394302 171.630938,190.382336 163.463438,198.319658 C149.170313,212.266667 120.584063,186.073504 80.7675,198.319658 C73.280625,200.587464 56.3784375,205.803419 51.500625,219.523647 C46.73625,233.130484 55.584375,251.046154 67.60875,260.797721 C93.245625,281.661538 119.79,254.674644 159.379688,271.909972 C181.840313,281.661538 203.053125,303.319088 208.725,330.305983 C211.674375,344.593162 209.6325,356.952707 207.704063,364.549858' id='Shape'>
|
||||
</path>
|
||||
</g>
|
||||
<g id='Layer_3' transform='translate(43.106250, 289.145299)'>
|
||||
<path d='M90.4096875,72.5698006 C78.8390625,41.3874644 64.9996875,31.4091168 54.1096875,28.234188 C41.8584375,24.7190883 30.7415625,29.0279202 17.8096875,21.2039886 C8.394375,15.4210826 3.403125,6.57663818 0.680625,0'id='Shape'></path>
|
||||
</g><g id='Layer_4' transform='translate(220.068750, 209.772080)'>
|
||||
<path d='M81.7884375,152.963533 C74.9821875,122.007977 61.8234375,104.77265 50.593125,94.5675214 C31.8759375,77.6723647 14.746875,77.1054131 5.218125,57.2621083 C4.4240625,55.5612536 -5.7853125,33.4501425 5.218125,17.008547 C14.52,3.06153846 33.350625,1.47407407 41.518125,0.907122507 C64.3190625,-0.907122507 73.280625,9.52478632 100.959375,13.039886 C117.180938,15.0809117 130.793438,13.4934473 139.30125,12.0193732'id='Shape'></path>
|
||||
</g>
|
||||
<g id='Layer_5' transform='translate(285.862500, 289.145299)'>
|
||||
<path d='M74.415,2.04102564 C66.3609375,0.793732194 51.046875,-0.566951567 33.12375,5.1025641 C17.5828125,9.97834758 6.80625,18.0290598 0.9075,23.2450142'id='Shape'></path>
|
||||
</g>
|
||||
<g id='Layer_6' transform='translate(174.693750, 1.133903)'>
|
||||
<path d='M184.109063,151.035897 C170.950313,174.507692 158.699063,180.290598 149.850938,181.311111 C133.85625,183.011966 129.091875,168.724786 104.475938,163.168661 C79.2928125,157.499145 72.2596875,169.745299 52.0678125,163.168661 C30.0609375,155.911681 18.7171875,134.821083 12.705,123.822222 C-1.588125,97.4022792 -0.3403125,71.6626781 0.5671875,51.2524217 C1.588125,29.4814815 6.125625,12.0193732 9.6421875,0.907122507'id='Shape'></path>
|
||||
<path d='M183.541875,69.3948718 C170.496563,56.6951567 157.564688,45.8096866 149.28375,39.0062678 C143.385,34.1304843 134.990625,33.2233618 128.297813,36.8518519 C128.070938,36.9652422 127.844063,37.0786325 127.730625,37.1920228 C122.739375,40.1401709 119.449688,45.3561254 118.8825,51.1390313 C118.201875,59.0763533 117.0675,71.4358974 114.912188,82.8883191 C114.118125,87.0837607 107.085,103.865527 89.7290625,110.668946 C77.2509375,115.544729 62.0503125,110.668946 53.4290625,101.597721 C40.38375,87.7641026 44.8078125,66.9002849 47.416875,55.2210826 C53.5425,26.760114 73.3940625,9.07122507 82.6959375,1.81424501'id='Shape'></path>
|
||||
</g>
|
||||
<g id='Layer_7' transform='translate(3.403125, 179.156695)'>
|
||||
<path d='M0.1134375,0.226780627 C2.949375,6.34985755 8.394375,16.1014245 18.2634375,25.3994302 C27.67875,34.2438746 37.3209375,39.0062678 43.4465625,41.5008547'id='Shape'></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
);
|
||||
|
||||
CoralLogo.propTypes = {
|
||||
|
||||
@@ -56,7 +56,7 @@ export default class Dialog extends Component {
|
||||
className={`mdl-dialog ${className}`}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{children}
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import styles from './Pager.css';
|
||||
|
||||
const Rows = (curr, total, onClickHandler) => Array.from(Array(total)).map((e, i) =>
|
||||
<li className={curr === i ? styles.current : ''}
|
||||
key={i} onClick={() => onClickHandler(i + 1)}>
|
||||
key={i} onClick={() => onClickHandler(i + 1)}>
|
||||
{i + 1}
|
||||
</li>
|
||||
);
|
||||
@@ -17,18 +17,18 @@ const Pager = ({totalPages, page, onNewPageHandler}) => (
|
||||
onClick={() => onNewPageHandler(page - 1)}>
|
||||
Prev
|
||||
</li>
|
||||
:
|
||||
:
|
||||
null
|
||||
}
|
||||
{Rows(page, totalPages, onNewPageHandler)}
|
||||
{
|
||||
(page < totalPages && totalPages > 1) ?
|
||||
<li
|
||||
onClick={() => onNewPageHandler(page + 1)}>
|
||||
<li
|
||||
onClick={() => onNewPageHandler(page + 1)}>
|
||||
Next
|
||||
</li>
|
||||
:
|
||||
null
|
||||
</li>
|
||||
:
|
||||
null
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -7,12 +7,12 @@ const Wizard = (props) => {
|
||||
{React.Children.toArray(children)
|
||||
.filter((child, i) => i === currentStep)
|
||||
.map((child, i) =>
|
||||
React.cloneElement(child, {
|
||||
i,
|
||||
currentStep,
|
||||
...rest
|
||||
})
|
||||
)}
|
||||
React.cloneElement(child, {
|
||||
i,
|
||||
currentStep,
|
||||
...rest
|
||||
})
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -93,18 +93,24 @@ export default class FlagButton extends Component {
|
||||
};
|
||||
if (reason === 'COMMENT_NOAGREE') {
|
||||
postDontAgree(action)
|
||||
.then(({data}) => {
|
||||
if (itemType === 'COMMENTS') {
|
||||
this.setState({localPost: data.createDontAgree.dontagree.id});
|
||||
}
|
||||
});
|
||||
.then(({data}) => {
|
||||
if (itemType === 'COMMENTS') {
|
||||
this.setState({localPost: data.createDontAgree.dontagree.id});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
} else {
|
||||
postFlag({...action, reason})
|
||||
.then(({data}) => {
|
||||
if (itemType === 'COMMENTS') {
|
||||
this.setState({localPost: data.createFlag.flag.id});
|
||||
}
|
||||
});
|
||||
.then(({data}) => {
|
||||
if (itemType === 'COMMENTS') {
|
||||
this.setState({localPost: data.createFlag.flag.id});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,15 +159,15 @@ export default class FlagButton extends Component {
|
||||
className={cn(`${name}-button`, {[`${name}-button-flagged`]: flagged}, styles.button)}>
|
||||
{
|
||||
flagged
|
||||
? <span className={`${name}-button-text`}>{t('reported')}</span>
|
||||
: <span className={`${name}-button-text`}>{t('report')}</span>
|
||||
? <span className={`${name}-button-text`}>{t('reported')}</span>
|
||||
: <span className={`${name}-button-text`}>{t('report')}</span>
|
||||
}
|
||||
<i className={
|
||||
cn(`${name}-icon`, 'material-icons', styles.icon, {
|
||||
flaggedIcon: flagged,
|
||||
[styles.flaggedIcon]: flagged,
|
||||
})}
|
||||
aria-hidden={true}>flag</i>
|
||||
aria-hidden={true}>flag</i>
|
||||
</button>
|
||||
{
|
||||
this.state.showMenu &&
|
||||
@@ -190,10 +196,10 @@ export default class FlagButton extends Component {
|
||||
}
|
||||
{
|
||||
this.state.reason && <div>
|
||||
<label htmlFor={'message'} className={`${name}-popup-radio-label`}>
|
||||
{t('flag_reason')}
|
||||
</label><br/>
|
||||
<textarea
|
||||
<label htmlFor={'message'} className={`${name}-popup-radio-label`}>
|
||||
{t('flag_reason')}
|
||||
</label><br/>
|
||||
<textarea
|
||||
className={`${name}-reason-text`}
|
||||
id="message"
|
||||
rows={4}
|
||||
@@ -208,8 +214,8 @@ export default class FlagButton extends Component {
|
||||
</div>
|
||||
{
|
||||
popupMenu.button && <Button
|
||||
className={`${name}-popup-button`}
|
||||
onClick={this.onPopupContinue}>
|
||||
className={`${name}-popup-button`}
|
||||
onClick={this.onPopupContinue}>
|
||||
{popupMenu.button}
|
||||
</Button>
|
||||
}
|
||||
|
||||
@@ -20,19 +20,19 @@ const getPopupMenu = [
|
||||
},
|
||||
(itemType) => {
|
||||
const options = itemType === 'COMMENTS' ?
|
||||
[
|
||||
{val: comment.offensive, text: t('comment_offensive')},
|
||||
{val: comment.spam, text: t('marketing')},
|
||||
{val: comment.noagree, text: t('no_agree_comment')},
|
||||
{val: comment.other, text: t('other')}
|
||||
]
|
||||
: [
|
||||
{val: username.offensive, text: t('username_offensive')},
|
||||
{val: username.nolike, text: t('no_like_username')},
|
||||
{val: username.impersonating, text: t('user_impersonating')},
|
||||
{val: username.spam, text: t('marketing')},
|
||||
{val: username.other, text: t('other')}
|
||||
];
|
||||
[
|
||||
{val: comment.offensive, text: t('comment_offensive')},
|
||||
{val: comment.spam, text: t('marketing')},
|
||||
{val: comment.noagree, text: t('no_agree_comment')},
|
||||
{val: comment.other, text: t('other')}
|
||||
]
|
||||
: [
|
||||
{val: username.offensive, text: t('username_offensive')},
|
||||
{val: username.nolike, text: t('no_like_username')},
|
||||
{val: username.impersonating, text: t('user_impersonating')},
|
||||
{val: username.spam, text: t('marketing')},
|
||||
{val: username.other, text: t('other')}
|
||||
];
|
||||
return {
|
||||
header: t('step_2_header'),
|
||||
options,
|
||||
|
||||
@@ -4,9 +4,9 @@ import Markdown from 'coral-framework/components/Markdown';
|
||||
const packagename = 'talk-plugin-infobox';
|
||||
|
||||
const InfoBox = ({enable, content}) =>
|
||||
<div
|
||||
className={`${packagename}-info ${enable ? '' : 'hidden'}` }>
|
||||
<Markdown content={content} />
|
||||
</div>;
|
||||
<div
|
||||
className={`${packagename}-info ${enable ? '' : 'hidden'}` }>
|
||||
<Markdown content={content} />
|
||||
</div>;
|
||||
|
||||
export default InfoBox;
|
||||
|
||||
@@ -5,12 +5,12 @@ import t from 'coral-framework/services/i18n';
|
||||
import {BASE_PATH} from 'coral-framework/constants/url';
|
||||
|
||||
const ModerationLink = (props) => props.isAdmin ? (
|
||||
<div className={styles.moderationLink}>
|
||||
<a href={`${BASE_PATH}admin/moderate/${props.assetId}`} target="_blank">
|
||||
{t('moderate_this_stream')}
|
||||
</a>
|
||||
</div>
|
||||
) : null;
|
||||
<div className={styles.moderationLink}>
|
||||
<a href={`${BASE_PATH}admin/moderate/${props.assetId}`} target="_blank">
|
||||
{t('moderate_this_stream')}
|
||||
</a>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
ModerationLink.propTypes = {
|
||||
assetId: PropTypes.string.isRequired,
|
||||
|
||||
@@ -43,8 +43,8 @@ const getCountsByAssetID = (context, asset_ids) => {
|
||||
}
|
||||
}
|
||||
])
|
||||
.then(singleJoinBy(asset_ids, '_id'))
|
||||
.then((results) => results.map((result) => result ? result.count : 0));
|
||||
.then(singleJoinBy(asset_ids, '_id'))
|
||||
.then((results) => results.map((result) => result ? result.count : 0));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -76,8 +76,8 @@ const getParentCountsByAssetID = (context, asset_ids) => {
|
||||
}
|
||||
}
|
||||
])
|
||||
.then(singleJoinBy(asset_ids, '_id'))
|
||||
.then((results) => results.map((result) => result ? result.count : 0));
|
||||
.then(singleJoinBy(asset_ids, '_id'))
|
||||
.then((results) => results.map((result) => result ? result.count : 0));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,20 +35,20 @@ const getAssetActivityMetrics = ({loaders: {Assets}}, {from, to, limit}) => {
|
||||
}},
|
||||
{$limit: limit}
|
||||
])
|
||||
.then((results) => {
|
||||
assetMetrics = results;
|
||||
.then((results) => {
|
||||
assetMetrics = results;
|
||||
|
||||
return Assets.getByID.loadMany(results.map((result) => result.asset_id));
|
||||
})
|
||||
.then((assets) => assets.map((asset, i) => {
|
||||
return Assets.getByID.loadMany(results.map((result) => result.asset_id));
|
||||
})
|
||||
.then((assets) => assets.map((asset, i) => {
|
||||
|
||||
// We're leveraging the fact that the comments returned by the aggregation
|
||||
// query are in the request order that we just made, it's what the
|
||||
// Assets.getByID loader does.
|
||||
asset.commentCount = assetMetrics[i].commentCount;
|
||||
asset.commentCount = assetMetrics[i].commentCount;
|
||||
|
||||
return asset;
|
||||
}));
|
||||
return asset;
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ const genUserByIDs = async (context, ids) => {
|
||||
* @param {Object} context graph context
|
||||
* @param {Object} query query terms to apply to the users query
|
||||
*/
|
||||
const getUsersByQuery = ({user}, {ids, limit, cursor, statuses = null, sortOrder}) => {
|
||||
const getUsersByQuery = (ctx, {ids, limit, cursor, statuses = null, sortOrder}) => {
|
||||
|
||||
let users = UserModel.find();
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@ const createPublicComment = async (context, commentInput) => {
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} status the new status of the comment
|
||||
*/
|
||||
const setStatus = async ({user, loaders: {Comments}, pubsub}, {id, status}) => {
|
||||
const setStatus = async ({user, loaders: {Comments}}, {id, status}) => {
|
||||
let comment = await CommentsService.pushStatus(id, status, user ? user.id : null);
|
||||
|
||||
// If the loaders are present, clear the caches for these values because we
|
||||
|
||||
@@ -5,7 +5,7 @@ const {ADD_COMMENT_TAG, REMOVE_COMMENT_TAG} = require('../../perms/constants');
|
||||
/**
|
||||
* Modifies the targeted model with the specified operation to add/remove a tag.
|
||||
*/
|
||||
const modify = async ({user, loaders: {Tags}, pubsub}, operation, {name, id, item_type, asset_id}) => {
|
||||
const modify = async ({user, loaders: {Tags}}, operation, {name, id, item_type, asset_id}) => {
|
||||
|
||||
// Get the global list of tags from the dataloader.
|
||||
const tags = await Tags.getAll.load({id, item_type, asset_id});
|
||||
|
||||
@@ -2,7 +2,7 @@ const errors = require('../../errors');
|
||||
const UsersService = require('../../services/users');
|
||||
const {SET_USER_STATUS, SUSPEND_USER, REJECT_USERNAME} = require('../../perms/constants');
|
||||
|
||||
const setUserStatus = async ({user, pubsub}, {id, status}) => {
|
||||
const setUserStatus = async ({pubsub}, {id, status}) => {
|
||||
const result = await UsersService.setStatus(id, status);
|
||||
if (result && result.status === 'BANNED') {
|
||||
pubsub.publish('userBanned', result);
|
||||
@@ -10,7 +10,7 @@ const setUserStatus = async ({user, pubsub}, {id, status}) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const suspendUser = async ({user, pubsub}, {id, message, until}) => {
|
||||
const suspendUser = async ({pubsub}, {id, message, until}) => {
|
||||
const result = await UsersService.suspendUser(id, message, until);
|
||||
if (result) {
|
||||
pubsub.publish('userSuspended', result);
|
||||
@@ -18,7 +18,7 @@ const suspendUser = async ({user, pubsub}, {id, message, until}) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const rejectUsername = async ({user, pubsub}, {id, message}) => {
|
||||
const rejectUsername = async ({pubsub}, {id, message}) => {
|
||||
const result = await UsersService.rejectUsername(id, message);
|
||||
if (result) {
|
||||
pubsub.publish('usernameRejected', result);
|
||||
|
||||
+24
-25
@@ -11,7 +11,7 @@
|
||||
"build": "WEBPACK=TRUE NODE_ENV=production webpack -p --config webpack.config.js --bail",
|
||||
"prebuild-watch": "yarn generate-introspection",
|
||||
"build-watch": "WEBPACK=TRUE NODE_ENV=development webpack --progress --config webpack.config.js --watch",
|
||||
"lint": "eslint bin/* .",
|
||||
"lint": "eslint --ext .json bin/* .",
|
||||
"lint-fix": "eslint bin/* . --fix",
|
||||
"test": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}",
|
||||
"test-cover": "TEST_MODE=unit NODE_ENV=test istanbul cover _mocha --report text --check-coverage -- -R spec",
|
||||
@@ -54,42 +54,42 @@
|
||||
},
|
||||
"homepage": "https://github.com/coralproject/talk#readme",
|
||||
"dependencies": {
|
||||
"accepts": "^1.3.3",
|
||||
"accepts": "^1.3.4",
|
||||
"apollo-client": "^1.9.1",
|
||||
"app-module-path": "^2.2.0",
|
||||
"autoprefixer": "^6.5.2",
|
||||
"babel-cli": "^6.24.0",
|
||||
"babel-core": "^6.24.0",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^7.2.1",
|
||||
"babel-loader": "^6.4.1",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-async-to-generator": "^6.16.0",
|
||||
"babel-plugin-transform-class-properties": "^6.23.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-plugin-transform-object-assign": "^6.8.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.23.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.23.0",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"babel-preset-es2015": "^6.24.0",
|
||||
"babel-preset-react": "^6.23.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.17.1",
|
||||
"bowser": "^1.7.0",
|
||||
"body-parser": "^1.17.2",
|
||||
"bowser": "^1.7.2",
|
||||
"cli-table": "^0.3.1",
|
||||
"clipboard": "^1.7.1",
|
||||
"colors": "^1.1.2",
|
||||
"commander": "^2.11.0",
|
||||
"common-tags": "^1.4.0",
|
||||
"compression": "^1.7.0",
|
||||
"compression-webpack-plugin": "^0.4.0",
|
||||
"compression-webpack-plugin": "^1.0.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"copy-webpack-plugin": "^4.0.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-loader": "^0.27.3",
|
||||
"css-loader": "^0.28.5",
|
||||
"dataloader": "^1.3.0",
|
||||
"debug": "^3.0.0",
|
||||
"dialog-polyfill": "^0.4.4",
|
||||
"debug": "^3.0.1",
|
||||
"dialog-polyfill": "^0.4.9",
|
||||
"dotenv": "^4.0.0",
|
||||
"ejs": "^2.5.7",
|
||||
"env-rewrite": "^1.0.2",
|
||||
@@ -97,7 +97,7 @@
|
||||
"exports-loader": "^0.6.4",
|
||||
"express": "^4.15.4",
|
||||
"file-loader": "^0.11.2",
|
||||
"form-data": "^2.2.0",
|
||||
"form-data": "^2.3.1",
|
||||
"fs-extra": "^4.0.1",
|
||||
"gql-merge": "^0.0.4",
|
||||
"graphql": "^0.9.1",
|
||||
@@ -116,7 +116,7 @@
|
||||
"inquirer": "^3.2.2",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"joi": "^10.6.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"json-loader": "^0.5.7",
|
||||
"jsonwebtoken": "^7.4.3",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"keymaster": "^1.6.2",
|
||||
@@ -187,25 +187,24 @@
|
||||
"chai-as-promised": "^6.0.0",
|
||||
"chai-http": "^3.0.0",
|
||||
"enzyme": "^2.9.1",
|
||||
"eslint": "^3.12.1",
|
||||
"eslint-config-postcss": "^2.0.2",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-module-utils": "^2.0.0",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-mocha": "^4.7.0",
|
||||
"eslint": "^4.5.0",
|
||||
"eslint-module-utils": "^2.1.1",
|
||||
"eslint-plugin-json": "^1.2.0",
|
||||
"eslint-plugin-mocha": "^4.11.0",
|
||||
"eslint-plugin-promise": "^3.3.1",
|
||||
"eslint-plugin-react": "^6.6.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"eslint-plugin-react": "^7.3.0",
|
||||
"mocha": "^3.1.2",
|
||||
"mocha-junit-reporter": "^1.12.1",
|
||||
"nodemon": "^1.11.0",
|
||||
"pre-git": "^3.10.0",
|
||||
"pre-git": "^3.15.3",
|
||||
"sinon": "^3.2.1",
|
||||
"sinon-chai": "^2.13.0",
|
||||
"supertest": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8"
|
||||
},
|
||||
"release": {
|
||||
"analyzeCommits": "simple-commit-message"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,13 +51,13 @@ export default (tag, options = {}) => hoistStatics((WrappedComponent) => {
|
||||
assetId: asset.id,
|
||||
itemType: 'COMMENTS',
|
||||
})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = false;
|
||||
forEachError(err, ({msg}) => addNotification('error', msg));
|
||||
});
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = false;
|
||||
forEachError(err, ({msg}) => addNotification('error', msg));
|
||||
});
|
||||
}
|
||||
|
||||
deleteTag = () => {
|
||||
@@ -73,13 +73,13 @@ export default (tag, options = {}) => hoistStatics((WrappedComponent) => {
|
||||
assetId: asset.id,
|
||||
itemType: 'COMMENTS',
|
||||
})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = false;
|
||||
forEachError(err, ({msg}) => addNotification('error', msg));
|
||||
});
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = false;
|
||||
forEachError(err, ({msg}) => addNotification('error', msg));
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -160,48 +160,43 @@ function getReactionConfig(reaction) {
|
||||
}
|
||||
},
|
||||
RootMutation: {
|
||||
[`create${Reaction}Action`]: (_, {input: {item_id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
|
||||
const response = Comments.get.load(item_id).then((comment) => {
|
||||
return Action.create({item_id, item_type: 'COMMENTS', action_type: REACTION})
|
||||
.then((action) => {
|
||||
[`create${Reaction}Action`]: (_, {input: {item_id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => wrapResponse(reaction)(async () => {
|
||||
const comment = await Comments.get.load(item_id);
|
||||
|
||||
if (pubsub) {
|
||||
let action;
|
||||
try {
|
||||
action = await Action.create({item_id, item_type: 'COMMENTS', action_type: REACTION});
|
||||
} catch (err) {
|
||||
if (err instanceof errors.ErrAlreadyExists) {
|
||||
return err.metadata.existing;
|
||||
}
|
||||
|
||||
// The comment is needed to allow better filtering e.g. by asset_id.
|
||||
pubsub.publish(`${reaction}ActionCreated`, {action, comment});
|
||||
}
|
||||
return Promise.resolve(action);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err instanceof errors.ErrAlreadyExists) {
|
||||
return Promise.resolve(err.metadata.existing);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
return wrapResponse(reaction)(response);
|
||||
},
|
||||
[`delete${Reaction}Action`]: (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
|
||||
const response = Action.delete({id})
|
||||
.then((action) => {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Action doesn't exist or was already deleted.
|
||||
if (!action) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return Comments.get.load(action.item_id).then((comment) => {
|
||||
if (pubsub) {
|
||||
|
||||
if (pubsub) {
|
||||
// The comment is needed to allow better filtering e.g. by asset_id.
|
||||
pubsub.publish(`${reaction}ActionCreated`, {action, comment});
|
||||
}
|
||||
|
||||
// The comment is needed to allow better filtering e.g. by asset_id.
|
||||
pubsub.publish(`${reaction}ActionDeleted`, {action, comment});
|
||||
}
|
||||
return Promise.resolve(action);
|
||||
});
|
||||
});
|
||||
return action;
|
||||
}),
|
||||
[`delete${Reaction}Action`]: (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => wrapResponse(reaction)(async () => {
|
||||
const action = await Action.delete({id});
|
||||
if (!action) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return wrapResponse(reaction)(response);
|
||||
}
|
||||
const comment = await Comments.get.load(action.item_id);
|
||||
if (pubsub) {
|
||||
|
||||
// The comment is needed to allow better filtering e.g. by asset_id.
|
||||
pubsub.publish(`${reaction}ActionDeleted`, {action, comment});
|
||||
}
|
||||
|
||||
return action;
|
||||
})
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
|
||||
@@ -39,13 +39,13 @@ class ForgotContent extends React.Component {
|
||||
</Button>
|
||||
{passwordRequestSuccess
|
||||
? <p className={styles.passwordRequestSuccess}>
|
||||
{passwordRequestSuccess}
|
||||
</p>
|
||||
{passwordRequestSuccess}
|
||||
</p>
|
||||
: null}
|
||||
{passwordRequestFailure
|
||||
? <p className={styles.passwordRequestFailure}>
|
||||
{passwordRequestFailure}
|
||||
</p>
|
||||
{passwordRequestFailure}
|
||||
</p>
|
||||
: null}
|
||||
</form>
|
||||
<div className={styles.footer}>
|
||||
|
||||
@@ -9,8 +9,8 @@ const SignInButton = ({loggedIn, showSignInDialog}) => (
|
||||
<div className="talk-stream-auth-sign-in-button">
|
||||
{!loggedIn
|
||||
? <Button id="coralSignInButton" onClick={showSignInDialog} full>
|
||||
{t('sign_in.sign_in_to_comment')}
|
||||
</Button>
|
||||
{t('sign_in.sign_in_to_comment')}
|
||||
</Button>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -97,6 +97,9 @@ class SignInContainer extends React.Component {
|
||||
// allow success UI to be shown for a second, and then close the modal
|
||||
this.props.hideSignInDialog();
|
||||
}, 2500);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -28,63 +28,63 @@ const SignInContent = ({
|
||||
{auth.error && <Alert>{auth.error}</Alert>}
|
||||
{auth.emailVerificationFailure
|
||||
? <form onSubmit={handleResendVerification}>
|
||||
<p>{t('sign_in.request_new_verify_email')}</p>
|
||||
<p>{t('sign_in.request_new_verify_email')}</p>
|
||||
<TextField
|
||||
id="confirm-email"
|
||||
type="email"
|
||||
label={t('sign_in.email')}
|
||||
value={emailToBeResent}
|
||||
onChange={handleChangeEmail}
|
||||
/>
|
||||
<Button id="resendConfirmEmail" type="submit" cStyle="black" full>
|
||||
Send Email
|
||||
</Button>
|
||||
{emailVerificationLoading && <Spinner />}
|
||||
{emailVerificationSuccess && <Success />}
|
||||
</form>
|
||||
: <div>
|
||||
<div className={`${styles.socialConnections} social-connections`}>
|
||||
<Button cStyle="facebook" onClick={fetchSignInFacebook} full>
|
||||
{t('sign_in.facebook_sign_in')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.separator}>
|
||||
<h1>
|
||||
{t('sign_in.or')}
|
||||
</h1>
|
||||
</div>
|
||||
<form onSubmit={handleSignIn}>
|
||||
<TextField
|
||||
id="confirm-email"
|
||||
id="email"
|
||||
type="email"
|
||||
label={t('sign_in.email')}
|
||||
value={emailToBeResent}
|
||||
onChange={handleChangeEmail}
|
||||
value={formData.email}
|
||||
style={{fontSize: 16}}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button id="resendConfirmEmail" type="submit" cStyle="black" full>
|
||||
Send Email
|
||||
</Button>
|
||||
{emailVerificationLoading && <Spinner />}
|
||||
{emailVerificationSuccess && <Success />}
|
||||
<TextField
|
||||
id="password"
|
||||
type="password"
|
||||
label={t('sign_in.password')}
|
||||
value={formData.password}
|
||||
style={{fontSize: 16}}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className={styles.action}>
|
||||
{!auth.isLoading
|
||||
? <Button
|
||||
id="coralLogInButton"
|
||||
type="submit"
|
||||
cStyle="black"
|
||||
className={styles.signInButton}
|
||||
full
|
||||
>
|
||||
{t('sign_in.sign_in')}
|
||||
</Button>
|
||||
: <Spinner />}
|
||||
</div>
|
||||
</form>
|
||||
: <div>
|
||||
<div className={`${styles.socialConnections} social-connections`}>
|
||||
<Button cStyle="facebook" onClick={fetchSignInFacebook} full>
|
||||
{t('sign_in.facebook_sign_in')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.separator}>
|
||||
<h1>
|
||||
{t('sign_in.or')}
|
||||
</h1>
|
||||
</div>
|
||||
<form onSubmit={handleSignIn}>
|
||||
<TextField
|
||||
id="email"
|
||||
type="email"
|
||||
label={t('sign_in.email')}
|
||||
value={formData.email}
|
||||
style={{fontSize: 16}}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
type="password"
|
||||
label={t('sign_in.password')}
|
||||
value={formData.password}
|
||||
style={{fontSize: 16}}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className={styles.action}>
|
||||
{!auth.isLoading
|
||||
? <Button
|
||||
id="coralLogInButton"
|
||||
type="submit"
|
||||
cStyle="black"
|
||||
className={styles.signInButton}
|
||||
full
|
||||
>
|
||||
{t('sign_in.sign_in')}
|
||||
</Button>
|
||||
: <Spinner />}
|
||||
</div>
|
||||
</form>
|
||||
</div>}
|
||||
</div>}
|
||||
<div className={`${styles.footer} footer`}>
|
||||
<span>
|
||||
<a onClick={() => changeView('FORGOT')}>
|
||||
|
||||
@@ -44,15 +44,15 @@ export default class ModTag extends React.Component {
|
||||
|
||||
return alreadyTagged ? (
|
||||
<span className={cn(styles.tag, styles.featured)}
|
||||
onClick={deleteTag}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave} >
|
||||
onClick={deleteTag}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave} >
|
||||
<Icon name="star_outline" className={cn(styles.tagIcon)} />
|
||||
{!this.state.on ? t('talk-plugin-featured-comments.featured') : t('talk-plugin-featured-comments.un_feature')}
|
||||
</span>
|
||||
) : (
|
||||
<span className={cn(styles.tag, {[styles.featured]: alreadyTagged})}
|
||||
onClick={this.postTag} >
|
||||
onClick={this.postTag} >
|
||||
<Icon name="star_outline" className={cn(styles.tagIcon)} />
|
||||
{alreadyTagged ? t('talk-plugin-featured-comments.featured') : t('talk-plugin-featured-comments.feature')}
|
||||
</span>
|
||||
|
||||
@@ -33,16 +33,16 @@ export default class Tag extends React.Component {
|
||||
const {tooltip} = this.state;
|
||||
return(
|
||||
<div className={styles.noSelect} onMouseEnter={this.showTooltip}
|
||||
onMouseLeave={this.hideTooltip} onTouchStart={this.showTooltip}
|
||||
onTouchEnd={this.hideTooltip}>
|
||||
{
|
||||
isTagged(this.props.comment.tags, 'FEATURED') ? (
|
||||
<span
|
||||
className={cn(styles.tag, styles.noSelect, {[styles.on]: tooltip})}>
|
||||
{t('talk-plugin-featured-comments.featured')}
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
onMouseLeave={this.hideTooltip} onTouchStart={this.showTooltip}
|
||||
onTouchEnd={this.hideTooltip}>
|
||||
{
|
||||
isTagged(this.props.comment.tags, 'FEATURED') ? (
|
||||
<span
|
||||
className={cn(styles.tag, styles.noSelect, {[styles.on]: tooltip})}>
|
||||
{t('talk-plugin-featured-comments.featured')}
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
{tooltip && <Tooltip className={styles.tooltip} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -73,7 +73,7 @@ export default class PermalinkButton extends React.Component {
|
||||
ref={(ref) => this.linkButton = ref}
|
||||
onClick={this.toggle}
|
||||
className={cn(`${name}-button`, styles.button)}>
|
||||
{t('permalink')}
|
||||
{t('permalink')}
|
||||
<Icon name="link" className={styles.icon}/>
|
||||
</button>
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ router.get('/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, nex
|
||||
|
||||
let [result, count] = await Promise.all([
|
||||
UsersService
|
||||
.search(value)
|
||||
.sort({[field]: (asc === 'true') ? 1 : -1})
|
||||
.skip((page - 1) * limit)
|
||||
.limit(limit),
|
||||
.search(value)
|
||||
.sort({[field]: (asc === 'true') ? 1 : -1})
|
||||
.skip((page - 1) * limit)
|
||||
.limit(limit),
|
||||
UsersService.count()
|
||||
]);
|
||||
|
||||
@@ -136,8 +136,8 @@ router.post('/', async (req, res, next) => {
|
||||
try {
|
||||
let user = await UsersService.createLocalUser(email, password, username);
|
||||
|
||||
// Send an email confirmation. The Front end will know about the
|
||||
// requireEmailConfirmation as it's included in the settings get endpoint.
|
||||
// Send an email confirmation. The Front end will know about the
|
||||
// requireEmailConfirmation as it's included in the settings get endpoint.
|
||||
await SendEmailConfirmation(req.app, user.id, email, redirectUri);
|
||||
|
||||
res.status(201).json(user);
|
||||
|
||||
+26
-23
@@ -1,26 +1,28 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const errors = require('../../errors');
|
||||
const Assets = require('../../services/assets');
|
||||
|
||||
const body = 'Lorem ipsum dolor sponge amet, consectetur adipiscing clam. Ut lobortis sollicitudin pillar a ornare. Curabitur dignissim vestibulum cay non rhoncus. Cras laoreet ante vel nunc hendrerit, shelf imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Talk volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non trust nec neque congue faucibus porttitor sit amet elkhorn.';
|
||||
|
||||
router.get('/id/:asset_id', (req, res, next) => {
|
||||
router.get('/id/:asset_id', async (req, res, next) => {
|
||||
try {
|
||||
const asset = await Assets.findById(req.params.asset_id);
|
||||
if (asset === null) {
|
||||
return next(errors.ErrNotFound);
|
||||
}
|
||||
|
||||
return Assets.findById(req.params.asset_id)
|
||||
.then((asset) => {
|
||||
if (asset === null) {
|
||||
return res.json({'message': 'Asset not found'});
|
||||
}
|
||||
res.render('article', {
|
||||
title: asset.title,
|
||||
asset_id: asset.id,
|
||||
asset_url: asset.url,
|
||||
body: '',
|
||||
basePath: '/client/embed/stream'
|
||||
});
|
||||
})
|
||||
.catch((err) => next(err));
|
||||
res.render('article', {
|
||||
title: asset.title,
|
||||
asset_id: asset.id,
|
||||
asset_url: asset.url,
|
||||
body: '',
|
||||
basePath: '/client/embed/stream'
|
||||
});
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/title/:asset_title', (req, res) => {
|
||||
@@ -33,17 +35,18 @@ router.get('/title/:asset_title', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/', (req, res, next) => {
|
||||
router.get('/', async (req, res, next) => {
|
||||
let skip = req.query.skip ? parseInt(req.query.skip) : 0;
|
||||
let limit = req.query.limit ? parseInt(req.query.limit) : 25;
|
||||
|
||||
return Assets.all(skip, limit)
|
||||
.then((assets) => {
|
||||
res.render('articles', {
|
||||
assets: assets
|
||||
});
|
||||
})
|
||||
.catch((err) => next(err));
|
||||
try {
|
||||
const assets = await Assets.all(skip, limit);
|
||||
res.render('articles', {
|
||||
assets: assets
|
||||
});
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
+46
-64
@@ -29,34 +29,27 @@ const keyfunc = (key) => {
|
||||
* resolved as the value to cache.
|
||||
* @return {Promise} Resolves to the value either retrieved from cache
|
||||
*/
|
||||
cache.wrap = (key, expiry, work, kf = keyfunc) => {
|
||||
return cache
|
||||
.get(key, kf)
|
||||
.then((value) => {
|
||||
if (value !== null) {
|
||||
debug('wrap: hit', kf(key));
|
||||
return value;
|
||||
}
|
||||
cache.wrap = async (key, expiry, work, kf = keyfunc) => {
|
||||
let value = await cache.get(key, kf);
|
||||
if (value !== null) {
|
||||
debug('wrap: hit', kf(key));
|
||||
return value;
|
||||
}
|
||||
|
||||
debug('wrap: miss', kf(key));
|
||||
debug('wrap: miss', kf(key));
|
||||
|
||||
return work()
|
||||
.then((value) => {
|
||||
value = await work();
|
||||
|
||||
process.nextTick(() => {
|
||||
cache
|
||||
.set(key, value, expiry, kf)
|
||||
.then(() => {
|
||||
debug('wrap: set complete');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
process.nextTick(async () => {
|
||||
try {
|
||||
await cache.set(key, value, expiry, kf);
|
||||
debug('wrap: set complete');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
return value;
|
||||
});
|
||||
});
|
||||
return value;
|
||||
};
|
||||
|
||||
// This is designed to increment a key and add an expiry iff the key already
|
||||
@@ -221,52 +214,41 @@ cache.decrMany = (keys, expiry, kf = keyfunc) => {
|
||||
* provided key into a string for the cache.
|
||||
* @return {Promise} resovles to the values for the keys
|
||||
*/
|
||||
cache.wrapMany = (keys, expiry, work, kf = keyfunc) => {
|
||||
return cache
|
||||
.getMany(keys, kf)
|
||||
.then((values) => {
|
||||
cache.wrapMany = async (keys, expiry, work, kf = keyfunc) => {
|
||||
let values = await cache.getMany(keys, kf);
|
||||
|
||||
// find any of the null valued items by collecting the work
|
||||
let workRefs = values
|
||||
.map((value, index) => ({value, index, key: keys[index]}))
|
||||
.filter(({value}) => value === null);
|
||||
// find any of the null valued items by collecting the work
|
||||
let workRefs = values
|
||||
.map((value, index) => ({value, index, key: keys[index]}))
|
||||
.filter(({value}) => value === null);
|
||||
|
||||
let workKeys = workRefs.map(({key}) => key);
|
||||
let workKeys = workRefs.map(({key}) => key);
|
||||
|
||||
debug(`wrapMany: hit ratio: ${keys.length - workKeys.length}/${keys.length}`);
|
||||
debug(`wrapMany: hit ratio: ${keys.length - workKeys.length}/${keys.length}`);
|
||||
|
||||
if (workKeys.length > 0) {
|
||||
return work(workKeys)
|
||||
.then((workedValues) => {
|
||||
if (workKeys.length > 0) {
|
||||
const workedValues = await work(workKeys);
|
||||
|
||||
// Set the items in the cache that we needed to retrive after the
|
||||
// next process tick.
|
||||
process.nextTick(() => {
|
||||
cache
|
||||
.setMany(workKeys, workedValues, expiry, kf)
|
||||
.then(() => {
|
||||
debug('wrapMany: setMany complete');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
return workedValues;
|
||||
})
|
||||
.then((workedValues) => {
|
||||
|
||||
// Walk over the worked keys to merge them with the existing values.
|
||||
for (let i = 0; i < workRefs.length; i++) {
|
||||
values[workRefs[i].index] = workedValues[i];
|
||||
}
|
||||
|
||||
return values;
|
||||
});
|
||||
} else {
|
||||
return values;
|
||||
}
|
||||
// Set the items in the cache that we needed to retrive after the
|
||||
// next process tick.
|
||||
process.nextTick(() => {
|
||||
cache
|
||||
.setMany(workKeys, workedValues, expiry, kf)
|
||||
.then(() => {
|
||||
debug('wrapMany: setMany complete');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Walk over the worked keys to merge them with the existing values.
|
||||
for (let i = 0; i < workRefs.length; i++) {
|
||||
values[workRefs[i].index] = workedValues[i];
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+10
-10
@@ -102,19 +102,19 @@ const mailer = module.exports = {
|
||||
// Render the TEXT version of the email.
|
||||
templates.render(template, 'txt', locals)
|
||||
])
|
||||
.then(([html, text]) => {
|
||||
.then(([html, text]) => {
|
||||
|
||||
// Create the job.
|
||||
return mailer.task.create({
|
||||
title: 'Mail',
|
||||
message: {
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
}
|
||||
return mailer.task.create({
|
||||
title: 'Mail',
|
||||
message: {
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* The key must be composed of alpha characters with periods seperating them.
|
||||
*/
|
||||
const KEY_REGEX = /^(?:[A-Za-z][A-Za-z\.]*[A-Za-z])?(?:[A-Za-z]*)$/;
|
||||
const KEY_REGEX = /^(?:[A-Za-z][A-Za-z.]*[A-Za-z])?(?:[A-Za-z]*)$/;
|
||||
|
||||
/**
|
||||
* Allows metadata properties to be set/unset from specific models. It is the
|
||||
|
||||
+19
-20
@@ -112,7 +112,7 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
|
||||
* @param {User} user the user to be validated
|
||||
* @param {Function} done the callback for the validation
|
||||
*/
|
||||
function ValidateUserLogin(loginProfile, user, done) {
|
||||
async function ValidateUserLogin(loginProfile, user, done) {
|
||||
if (!user) {
|
||||
return done(new Error('user not found'));
|
||||
}
|
||||
@@ -127,30 +127,29 @@ function ValidateUserLogin(loginProfile, user, done) {
|
||||
}
|
||||
|
||||
// The user is a local user, check if we need email confirmation.
|
||||
return SettingsService.retrieve().then(({requireEmailConfirmation = false}) => {
|
||||
const {requireEmailConfirmation = false} = await SettingsService.retrieve();
|
||||
|
||||
// If we have the requirement of checking that emails for users are
|
||||
// verified, then we need to check the email address to ensure that it has
|
||||
// been verified.
|
||||
if (requireEmailConfirmation) {
|
||||
// If we have the requirement of checking that emails for users are
|
||||
// verified, then we need to check the email address to ensure that it has
|
||||
// been verified.
|
||||
if (requireEmailConfirmation) {
|
||||
|
||||
// Get the profile representing the local account.
|
||||
let profile = user.profiles.find((profile) => profile.id === loginProfile.id);
|
||||
// Get the profile representing the local account.
|
||||
let profile = user.profiles.find((profile) => profile.id === loginProfile.id);
|
||||
|
||||
// This should never get to this point, if it does, don't let this past.
|
||||
if (!profile) {
|
||||
throw new Error('ID indicated by loginProfile is not on user object');
|
||||
}
|
||||
|
||||
// If the profile doesn't have a metadata field, or it does not have a
|
||||
// confirmed_at field, or that field is null, then send them back.
|
||||
if (!profile.metadata || !profile.metadata.confirmed_at || profile.metadata.confirmed_at === null) {
|
||||
return done(new errors.ErrAuthentication(loginProfile.id));
|
||||
}
|
||||
// This should never get to this point, if it does, don't let this past.
|
||||
if (!profile) {
|
||||
throw new Error('ID indicated by loginProfile is not on user object');
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
});
|
||||
// If the profile doesn't have a metadata field, or it does not have a
|
||||
// confirmed_at field, or that field is null, then send them back.
|
||||
if (!profile.metadata || !profile.metadata.confirmed_at || profile.metadata.confirmed_at === null) {
|
||||
return done(new errors.ErrAuthentication(loginProfile.id));
|
||||
}
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
||||
+17
-29
@@ -39,7 +39,7 @@ const scraper = {
|
||||
/**
|
||||
* Scrapes the given asset for metadata.
|
||||
*/
|
||||
scrape(asset) {
|
||||
async scrape(asset) {
|
||||
return metascraper.scrapeUrl(asset.url, Object.assign({}, metascraper.RULES, {
|
||||
section: ($) => $('meta[property="article:section"]').attr('content'),
|
||||
modified: ($) => $('meta[property="article:modified"]').attr('content')
|
||||
@@ -71,45 +71,33 @@ const scraper = {
|
||||
|
||||
debug(`Now processing ${scraper.task.name} jobs`);
|
||||
|
||||
scraper.task.process((job, done) => {
|
||||
scraper.task.process(async (job, done) => {
|
||||
|
||||
debug(`Starting on Job[${job.id}] for Asset[${job.data.asset_id}]`);
|
||||
|
||||
AssetsService
|
||||
try {
|
||||
|
||||
// Find the asset, or complain that it doesn't exist.
|
||||
.findById(job.data.asset_id)
|
||||
.then((asset) => {
|
||||
if (!asset) {
|
||||
throw new Error('asset not found');
|
||||
}
|
||||
|
||||
return asset;
|
||||
})
|
||||
const asset = await AssetsService.findById(job.data.asset_id);
|
||||
if (!asset) {
|
||||
return done(new Error('asset not found'));
|
||||
}
|
||||
|
||||
// Scrape the metadata from the asset.
|
||||
.then(scraper.scrape)
|
||||
const meta = await scraper.scrape(asset);
|
||||
|
||||
debug(`Scraped ${JSON.stringify(meta)} on Job[${job.id}] for Asset[${job.data.asset_id}]`);
|
||||
|
||||
// Assign the metadata retrieved for the asset to the db.
|
||||
.then((meta) => {
|
||||
debug(`Scraped ${JSON.stringify(meta)} on Job[${job.id}] for Asset[${job.data.asset_id}]`);
|
||||
await scraper.update(job.data.asset_id, meta);
|
||||
} catch (err) {
|
||||
|
||||
return scraper.update(job.data.asset_id, meta);
|
||||
})
|
||||
debug(`Failed to scrape on Job[${job.id}] for Asset[${job.data.asset_id}]:`, err);
|
||||
return done(err);
|
||||
}
|
||||
|
||||
// Finish the job because we just handled our scraping + updating the
|
||||
// asset in the database.
|
||||
.then(() => {
|
||||
debug(`Finished on Job[${job.id}] for Asset[${job.data.asset_id}]`);
|
||||
done();
|
||||
})
|
||||
|
||||
// Handle errors that occur.
|
||||
.catch((err) => {
|
||||
debug(`Failed to scrape on Job[${job.id}] for Asset[${job.data.asset_id}]:`, err);
|
||||
|
||||
done(err);
|
||||
});
|
||||
debug(`Finished on Job[${job.id}] for Asset[${job.data.asset_id}]`);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -227,14 +227,14 @@ module.exports = class UsersService {
|
||||
resolve(hashedPassword);
|
||||
});
|
||||
})
|
||||
.then((hashedPassword) => {
|
||||
return UserModel.update({id}, {
|
||||
$inc: {__v: 1},
|
||||
$set: {
|
||||
password: hashedPassword
|
||||
}
|
||||
.then((hashedPassword) => {
|
||||
return UserModel.update({id}, {
|
||||
$inc: {__v: 1},
|
||||
$set: {
|
||||
password: hashedPassword
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -484,7 +484,7 @@ module.exports = class UsersService {
|
||||
},
|
||||
subject: 'Your account has been suspended',
|
||||
to: localProfile.id // This only works if the user has registered via e-mail.
|
||||
// We may want a standard way to access a user's e-mail address in the future
|
||||
// We may want a standard way to access a user's e-mail address in the future
|
||||
};
|
||||
|
||||
await MailerService.sendSimple(options);
|
||||
@@ -520,7 +520,7 @@ module.exports = class UsersService {
|
||||
},
|
||||
subject: 'Email Suspension',
|
||||
to: localProfile.id // This only works if the user has registered via e-mail.
|
||||
// We may want a standard way to access a user's e-mail address in the future
|
||||
// We may want a standard way to access a user's e-mail address in the future
|
||||
};
|
||||
|
||||
await MailerService.sendSimple(options);
|
||||
|
||||
+27
-24
@@ -1,8 +1,8 @@
|
||||
const debug = require('debug')('talk:services:wordlist');
|
||||
const _ = require('lodash');
|
||||
const {RegexpTokenizer} = require('natural');
|
||||
const tokenizer = new RegexpTokenizer({pattern: /[\.\s\'\"\?\!]/});
|
||||
const nameTokenizer = new RegexpTokenizer({pattern: /\_/});
|
||||
const tokenizer = new RegexpTokenizer({pattern: /[.\s'"?!]/});
|
||||
const nameTokenizer = new RegexpTokenizer({pattern: /_/});
|
||||
const SettingsService = require('./settings');
|
||||
const Errors = require('../errors');
|
||||
|
||||
@@ -69,20 +69,20 @@ class Wordlist {
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((word) => {
|
||||
if (word.length === 1) {
|
||||
return [word];
|
||||
}
|
||||
.map((word) => {
|
||||
if (word.length === 1) {
|
||||
return [word];
|
||||
}
|
||||
|
||||
return tokenizer.tokenize(word.toLowerCase());
|
||||
})
|
||||
.filter((tokens) => {
|
||||
if (tokens.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return tokenizer.tokenize(word.toLowerCase());
|
||||
})
|
||||
.filter((tokens) => {
|
||||
if (tokens.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,22 +250,25 @@ class Wordlist {
|
||||
* @return {Function} the Connect middleware
|
||||
*/
|
||||
static filter(...fields) {
|
||||
return (req, res, next) => {
|
||||
return async (req, res, next) => {
|
||||
|
||||
// Create a new instance of the Wordlist.
|
||||
const wl = new Wordlist();
|
||||
|
||||
wl
|
||||
.load()
|
||||
.then(() => {
|
||||
try {
|
||||
|
||||
// Perform a filtering operation using the new instance of the
|
||||
// Wordlist.
|
||||
req.wordlist = wl.filter(req.body, ...fields);
|
||||
await wl.load();
|
||||
|
||||
// Call the next piece of middleware.
|
||||
next();
|
||||
});
|
||||
// Perform a filtering operation using the new instance of the
|
||||
// Wordlist.
|
||||
req.wordlist = wl.filter(req.body, ...fields);
|
||||
|
||||
} catch(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Call the next piece of middleware.
|
||||
return next();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ before(function(done) {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function(done) {
|
||||
Promise.all(Object.keys(mongoose.connection.collections).map((collection) => {
|
||||
beforeEach(async () => {
|
||||
await Promise.all(Object.keys(mongoose.connection.collections).map((collection) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
mongoose.connection.collections[collection].remove(function(err) {
|
||||
if (err) {
|
||||
@@ -23,13 +23,7 @@ beforeEach(function(done) {
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
}))
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch((err) => {
|
||||
done(err);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
|
||||
@@ -51,12 +51,12 @@ describe('graph.Context', () => {
|
||||
id: '1',
|
||||
name: 'Tag',
|
||||
})
|
||||
.then(() => {
|
||||
throw new Error('should not reach this point');
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.equal(errors.ErrNotAuthorized);
|
||||
});
|
||||
.then(() => {
|
||||
throw new Error('should not reach this point');
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.equal(errors.ErrNotAuthorized);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -181,24 +181,24 @@ describe('graph.mutations.createComment', () => {
|
||||
body
|
||||
}
|
||||
})
|
||||
.then(({data, errors}) => {
|
||||
expect(errors).to.be.undefined;
|
||||
expect(data.createComment).to.have.property('comment').not.null;
|
||||
expect(data.createComment.comment).to.have.property('status', status);
|
||||
expect(data.createComment).to.have.property('errors').null;
|
||||
.then(({data, errors}) => {
|
||||
expect(errors).to.be.undefined;
|
||||
expect(data.createComment).to.have.property('comment').not.null;
|
||||
expect(data.createComment.comment).to.have.property('status', status);
|
||||
expect(data.createComment).to.have.property('errors').null;
|
||||
|
||||
return ActionModel.find({
|
||||
item_id: data.createComment.comment.id,
|
||||
action_type: 'FLAG'
|
||||
return ActionModel.find({
|
||||
item_id: data.createComment.comment.id,
|
||||
action_type: 'FLAG'
|
||||
});
|
||||
})
|
||||
.then((actions) => {
|
||||
if (flagged) {
|
||||
expect(actions).to.have.length(1);
|
||||
} else {
|
||||
expect(actions).to.have.length(0);
|
||||
}
|
||||
});
|
||||
})
|
||||
.then((actions) => {
|
||||
if (flagged) {
|
||||
expect(actions).to.have.length(1);
|
||||
} else {
|
||||
expect(actions).to.have.length(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -25,9 +25,9 @@ describe('/api/v1/account/username', () => {
|
||||
.set(passport.inject({id: '456', roles: ['ADMIN']}));
|
||||
|
||||
const res = await chai.request(app)
|
||||
.put('/api/v1/account/username')
|
||||
.set(passport.inject({id: mockUser.id, roles: []}))
|
||||
.send({username: 'MojoJojo'});
|
||||
.put('/api/v1/account/username')
|
||||
.set(passport.inject({id: mockUser.id, roles: []}))
|
||||
.send({username: 'MojoJojo'});
|
||||
|
||||
expect(res).to.have.status(204);
|
||||
});
|
||||
@@ -38,9 +38,9 @@ describe('/api/v1/account/username', () => {
|
||||
.set(passport.inject({id: '456', roles: ['ADMIN']}));
|
||||
|
||||
let res = chai.request(app)
|
||||
.put('/api/v1/account/username')
|
||||
.set(passport.inject({id: 'wrongid', roles: []}))
|
||||
.send({username: 'MojoJojo'});
|
||||
.put('/api/v1/account/username')
|
||||
.set(passport.inject({id: 'wrongid', roles: []}))
|
||||
.send({username: 'MojoJojo'});
|
||||
|
||||
return expect(res).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
@@ -25,14 +25,11 @@ const SettingsService = require('../../../../../services/settings');
|
||||
describe('/api/v1/auth/local', () => {
|
||||
|
||||
let mockUser;
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
const settings = {requireEmailConfirmation: false, wordlist: {banned: ['bad'], suspect: ['naughty']}};
|
||||
return SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUser('maria@gmail.com', 'password!', 'Maria')
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
});
|
||||
});
|
||||
await SettingsService.init(settings);
|
||||
|
||||
mockUser = await UsersService.createLocalUser('maria@gmail.com', 'password!', 'Maria');
|
||||
});
|
||||
|
||||
describe('email confirmation disabled', () => {
|
||||
@@ -53,12 +50,12 @@ describe('/api/v1/auth/local', () => {
|
||||
it('should not send back the user on a unsuccessful login', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/auth/local')
|
||||
.send({email: 'maria@gmail.com', password: 'password!3'})
|
||||
.catch((err) => {
|
||||
expect(err).to.not.be.null;
|
||||
expect(err.response).to.have.status(401);
|
||||
expect(err.response.body).to.have.property('message', 'not authorized');
|
||||
});
|
||||
.send({email: 'maria@gmail.com', password: 'password!3'})
|
||||
.catch((err) => {
|
||||
expect(err).to.not.be.null;
|
||||
expect(err.response).to.have.status(401);
|
||||
expect(err.response.body).to.have.property('message', 'not authorized');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -20,9 +20,9 @@ describe('/api/v1/users/:user_id/email/confirm', () => {
|
||||
beforeEach(() => SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
|
||||
})
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
|
||||
describe('#post', () => {
|
||||
it('should send an email when we hit the endpoint', () => {
|
||||
@@ -56,9 +56,9 @@ describe('/api/v1/users/:user_id/actions', () => {
|
||||
beforeEach(() => SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
|
||||
})
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
|
||||
describe('#post', () => {
|
||||
it('it should update actions', () => {
|
||||
@@ -82,9 +82,9 @@ describe('/api/v1/users/:user_id/username-enable', () => {
|
||||
beforeEach(() => SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
|
||||
})
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
.then((user) => {
|
||||
mockUser = user;
|
||||
}));
|
||||
|
||||
describe('#post', () => {
|
||||
it('it should enable a user to edit their username', () => {
|
||||
|
||||
@@ -2,64 +2,58 @@ const UsersService = require('../../../services/users');
|
||||
const SettingsService = require('../../../services/settings');
|
||||
|
||||
const chai = require('chai');
|
||||
chai.use(require('chai-as-promised'));
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('services.UsersService', () => {
|
||||
|
||||
let mockUsers;
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
|
||||
|
||||
return SettingsService.init(settings).then(() => {
|
||||
return UsersService.createLocalUsers([{
|
||||
email: 'stampi@gmail.com',
|
||||
username: 'Stampi',
|
||||
password: '1Coral!-'
|
||||
}, {
|
||||
email: 'sockmonster@gmail.com',
|
||||
username: 'Sockmonster',
|
||||
password: '2Coral!2'
|
||||
}, {
|
||||
email: 'marvel@gmail.com',
|
||||
username: 'Marvel',
|
||||
password: '3Coral!3'
|
||||
}]).then((users) => {
|
||||
mockUsers = users;
|
||||
});
|
||||
});
|
||||
await SettingsService.init(settings);
|
||||
mockUsers = await UsersService.createLocalUsers([{
|
||||
email: 'stampi@gmail.com',
|
||||
username: 'Stampi',
|
||||
password: '1Coral!-'
|
||||
}, {
|
||||
email: 'sockmonster@gmail.com',
|
||||
username: 'Sockmonster',
|
||||
password: '2Coral!2'
|
||||
}, {
|
||||
email: 'marvel@gmail.com',
|
||||
username: 'Marvel',
|
||||
password: '3Coral!3'
|
||||
}]);
|
||||
});
|
||||
|
||||
describe('#findById()', () => {
|
||||
it('should find a user by id', () => {
|
||||
return UsersService
|
||||
.findById(mockUsers[0].id)
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('username', 'Stampi');
|
||||
});
|
||||
it('should find a user by id', async () => {
|
||||
const user = await UsersService.findById(mockUsers[0].id);
|
||||
expect(user).to.have.property('username', 'Stampi');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#findByIdArray()', () => {
|
||||
it('should find an array of users from an array of ids', () => {
|
||||
it('should find an array of users from an array of ids', async () => {
|
||||
const ids = mockUsers.map((user) => user.id);
|
||||
return UsersService.findByIdArray(ids).then((result) => {
|
||||
expect(result).to.have.length(3);
|
||||
});
|
||||
const users = await UsersService.findByIdArray(ids);
|
||||
expect(users).to.have.length(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#findPublicByIdArray()', () => {
|
||||
it('should find an array of users from an array of ids', () => {
|
||||
it('should find an array of users from an array of ids', async () => {
|
||||
const ids = mockUsers.map((user) => user.id);
|
||||
return UsersService.findPublicByIdArray(ids).then((result) => {
|
||||
expect(result).to.have.length(3);
|
||||
const sorted = result.sort((a, b) => {
|
||||
if(a.username < b.username) {return -1;}
|
||||
if(a.username > b.username) {return 1;}
|
||||
return 0;
|
||||
});
|
||||
expect(sorted[0]).to.have.property('username', 'Marvel');
|
||||
const users = await UsersService.findPublicByIdArray(ids);
|
||||
expect(users).to.have.length(3);
|
||||
|
||||
const sorted = users.sort((a, b) => {
|
||||
if(a.username < b.username) {return -1;}
|
||||
if(a.username > b.username) {return 1;}
|
||||
return 0;
|
||||
});
|
||||
expect(sorted[0]).to.have.property('username', 'Marvel');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,56 +76,43 @@ describe('services.UsersService', () => {
|
||||
username: 'StampiTheSecond',
|
||||
password: '1Coralito!'
|
||||
}])
|
||||
.then((user) => {
|
||||
expect(user).to.be.null;
|
||||
})
|
||||
.catch((error) => {
|
||||
expect(error).to.not.be.null;
|
||||
});
|
||||
.then((user) => {
|
||||
expect(user).to.be.null;
|
||||
})
|
||||
.catch((error) => {
|
||||
expect(error).to.not.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createEmailConfirmToken', () => {
|
||||
|
||||
it('should create a token for a valid user', () => {
|
||||
return UsersService
|
||||
.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)
|
||||
.then((token) => {
|
||||
expect(token).to.not.be.null;
|
||||
});
|
||||
it('should create a token for a valid user', async () => {
|
||||
const token = await UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id);
|
||||
expect(token).to.not.be.null;
|
||||
});
|
||||
|
||||
it('should not create a token for a user already verified', () => {
|
||||
return UsersService
|
||||
.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)
|
||||
.then((token) => {
|
||||
expect(token).to.not.be.null;
|
||||
it('should not create a token for a user already verified', async () => {
|
||||
const token = await UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id);
|
||||
expect(token).to.not.be.null;
|
||||
|
||||
return UsersService.verifyEmailConfirmation(token);
|
||||
})
|
||||
.then(() => {
|
||||
return UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id);
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.have.property('message', 'email address already confirmed');
|
||||
});
|
||||
await UsersService.verifyEmailConfirmation(token);
|
||||
|
||||
return expect(UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#verifyEmailConfirmation', () => {
|
||||
|
||||
it('should correctly validate a valid token', () => {
|
||||
return UsersService
|
||||
.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id)
|
||||
.then((token) => {
|
||||
expect(token).to.not.be.null;
|
||||
it('should correctly validate a valid token', async () => {
|
||||
const token = await UsersService.createEmailConfirmToken(mockUsers[0].id, mockUsers[0].profiles[0].id);
|
||||
expect(token).to.not.be.null;
|
||||
|
||||
return UsersService.verifyEmailConfirmation(token);
|
||||
});
|
||||
return expect(UsersService.verifyEmailConfirmation(token)).to.eventually.not.be.rejected;
|
||||
});
|
||||
|
||||
it('should correctly reject an invalid token', () => {
|
||||
it('should correctly reject an invalid token', async () => {
|
||||
return UsersService
|
||||
.verifyEmailConfirmation('cats')
|
||||
.catch((err) => {
|
||||
@@ -265,31 +246,13 @@ describe('services.UsersService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error if canEditName is false', (done) => {
|
||||
UsersService
|
||||
.editName(mockUsers[0].id, 'Jojo')
|
||||
.then(() => UsersService.findById(mockUsers[0].id))
|
||||
.then(() => {
|
||||
done(new Error('Error expected'));
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.ok;
|
||||
done();
|
||||
});
|
||||
it('should return an error if canEditName is false', async () => {
|
||||
return expect(UsersService.editName(mockUsers[0].id, 'Jojo')).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('should return an error if the username is already taken', (done) => {
|
||||
UsersService
|
||||
.toggleNameEdit(mockUsers[0].id, true)
|
||||
.then(() => UsersService.editName(mockUsers[0].id, 'Marvel'))
|
||||
.then(() => UsersService.findById(mockUsers[0].id))
|
||||
.then(() => {
|
||||
done(new Error('Error expected'));
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.be.ok;
|
||||
done();
|
||||
});
|
||||
it('should return an error if the username is already taken', async () => {
|
||||
await UsersService.toggleNameEdit(mockUsers[0].id, true);
|
||||
return expect(UsersService.editName(mockUsers[0].id, 'Marvel')).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('should not allow non-alphanumeric characters in usernames', () => {
|
||||
|
||||
Reference in New Issue
Block a user