Merge branch 'master' of github.com:coralproject/talk into reject-username

* 'master' of github.com:coralproject/talk: (33 commits)
  apply @cvle suggestion for fix
  Take karma into account when doing e2e
  Bump version to 4.4.2
  move placeholder right behind contentEditable
  added pivotal tracker ref
  moved event handler to bundle
  Update stream.njk
  disable CSP until we can work on configuration + webpack issues
  added missing translation
  Remove redundant period. It's in the translation strings already.
  Remove redundancy
  One more missing German translation
  Add missing translations (en/de) for plugins ignore-user and local-auth.
  text-replace error
  support the __webpack_nonce__ parameter
  csp fixes
  added comment
  `karma` -> `karmaThresholds`
  name changes
  Fix Touch issues on IOS Safari (iPad)
  ...
This commit is contained in:
okbel
2018-05-28 11:23:34 -03:00
52 changed files with 632 additions and 456 deletions
@@ -14,7 +14,8 @@ const ApproveButton = ({ active, minimal, onClick, className, disabled }) => {
className={cn(
styles.root,
{ [styles.minimal]: minimal, [styles.active]: active },
className
className,
'talk-admin-approve-button'
)}
onClick={onClick}
disabled={disabled || active}
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import Label from 'coral-ui/components/Label';
import Slot from 'coral-framework/components/Slot';
import { t } from 'coral-framework/services/i18n';
import FlagLabel from 'coral-ui/components/FlagLabel';
import cn from 'classnames';
import styles from './CommentLabels.css';
@@ -63,10 +64,14 @@ const CommentLabels = ({
<FlagLabel iconName="person">{getUserFlaggedType(actions)}</FlagLabel>
)}
{hasSuspectedWords(actions) && (
<FlagLabel iconName="sms_failed">Suspect</FlagLabel>
<FlagLabel iconName="sms_failed">
{t('flags.reasons.comment.suspect_word')}
</FlagLabel>
)}
{hasHistoryFlag(actions) && (
<FlagLabel iconName="sentiment_very_dissatisfied">History</FlagLabel>
<FlagLabel iconName="sentiment_very_dissatisfied">
{t('flags.reasons.comment.trust')}
</FlagLabel>
)}
</div>
<Slot
@@ -0,0 +1,103 @@
.karmaTooltip {
position: relative;
display: inline-block;
margin: 2px 4px 0;
}
.icon {
font-size: 16px;
color: #0D5B8F;
user-select: none;
-webkit-tap-highlight-color:rgba(0,0,0,0);
> i {
vertical-align: baseline;
}
}
.icon:hover {
cursor: pointer;
}
.menu {
background-color: white;
border: solid 1px #999;
border-radius: 3px;
padding: 10px;
position: absolute;
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2);
z-index: 10;
top: 32px;
left: -100px;
width: 150px;
text-align: left;
color: #616161;
}
.menu::before{
content: '';
border: 10px solid transparent;
border-top-color: #999;
position: absolute;
left: 96px;
top: -20px;
transform: rotate(180deg);
}
.menu::after{
content: '';
border: 10px solid transparent;
border-top-color: white;
position: absolute;
left: 96px;
top: -19px;
transform: rotate(180deg);
}
.menu ul {
list-style: none;
padding: 0;
li {
display: flex;
justify-content: space-between;
margin: 5px 0;
}
}
.label {
padding: 4px 5px;
border-radius: 3px;
color: #fff;
font-weight: 400;
text-align: center;
font-size: .9em;
line-height: normal;
letter-spacing: .4px;
min-width: 25px;
display: block;
/* &.reliable { background-color: #03AB61; } */
/* &.neutral { background-color: #616161; } */
&.unreliable { background-color: #F44336; }
}
.descriptionList {
padding: 0;
margin: 0;
list-style: none;
}
.strongItem {
margin-right: 3px;
}
.descriptionItem {
font-size: 0.9em;
}
.link {
color: #2B7EB5;
text-decoration: underline;
display: block;
}
@@ -0,0 +1,82 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Icon } from 'coral-ui';
import styles from './KarmaTooltip.css';
import ClickOutside from 'coral-framework/components/ClickOutside';
import t from 'coral-framework/services/i18n';
const initialState = { menuVisible: false };
class KarmaTooltip extends React.Component {
static propTypes = {
thresholds: PropTypes.shape({
reliable: PropTypes.number.isRequired,
unreliable: PropTypes.number.isRequired,
}).isRequired,
};
state = initialState;
toogleMenu = () => {
this.setState({ menuVisible: !this.state.menuVisible });
};
hideMenu = () => {
this.setState({ menuVisible: false });
};
render() {
const { thresholds: { unreliable } } = this.props;
const { menuVisible } = this.state;
return (
<ClickOutside onClickOutside={this.hideMenu}>
<div className={cn(styles.karmaTooltip, 'talk-admin-karma-tooltip')}>
<span
onClick={this.toogleMenu}
className={cn(styles.icon, 'talk-admin-karma-tooltip-icon')}
>
<Icon name="info" />
</span>
{menuVisible && (
<div className={cn(styles.menu, 'talk-admin-karma-tooltip-menu')}>
<strong>{t('user_detail.user_karma_score')}</strong>
<ul>
{/* NOTE: we may display this data in the future, keeping around for that eventuality */}
{/* <li>
<span>Reliable</span>{' '}
<span className={cn(styles.label, styles.reliable)}>
&ge; {reliable}
</span>
</li>
<li>
<span>Neutral</span>{' '}
<span className={cn(styles.label, styles.neutral)}>
&lt; {reliable}, &gt; {unreliable}
</span>
</li> */}
<li>
<span>{t('user_detail.unreliable')}</span>{' '}
<span className={cn(styles.label, styles.unreliable)}>
&le; {unreliable}
</span>
</li>
</ul>
<a
className={styles.link}
href={t('user_detail.karma_docs_link')}
target="_blank"
>
{t('user_detail.learn_more')}
</a>
</div>
)}
</div>
</ClickOutside>
);
}
}
export default KarmaTooltip;
@@ -14,7 +14,8 @@ const RejectButton = ({ active, minimal, onClick, className, disabled }) => {
className={cn(
styles.root,
{ [styles.minimal]: minimal, [styles.active]: active },
className
className,
'talk-admin-reject-button'
)}
onClick={onClick}
disabled={disabled || active}
@@ -35,44 +35,49 @@
margin-right: 20px;
}
.karmaStat {
display: flex;
}
.stat:last-child {
margin-right: 0px;
}
.statItem,
.statReportResult {
.statItem, .statReportResult, .statKarmaResult {
padding: 3px 5px;
background-color: #D8D8D8;
border-radius: 3px;
font-weight: 500;
display: block;
font-size: 0.9em;
line-height: normal;
letter-spacing: 0.4px;
min-width: 60px;
min-width: 25px;
display: block;
}
.statResult {
font-size: 1.5em;
padding: 5px 0;
display: inline-block;
text-align: center;
}
.statReportResult {
.statReportResult, .statKarmaResult {
color: white;
margin: 5px 0;
font-weight: 400;
text-align: center;
}
.statReportResult.reliable {
background-color: #749C48;
.statReportResult.reliable, .statKarmaResult.good {
background-color: #03AB61;
}
.statReportResult.neutral {
.statReportResult.neutral, .statKarmaResult.neutral {
background-color: #616161;
}
.statReportResult.unreliable {
.statReportResult.unreliable, .statKarmaResult.bad {
background-color: #F44336;
}
+26 -13
View File
@@ -6,6 +6,7 @@ import styles from './UserDetail.css';
import UserHistory from './UserHistory';
import { Slot } from 'coral-framework/components';
import UserDetailCommentList from '../components/UserDetailCommentList';
import {
getReliability,
isSuspended,
@@ -13,7 +14,9 @@ import {
isUsernameRejected,
isUsernameChanged,
getActiveStatuses,
isSuspended, isBanned, getKarma
} from 'coral-framework/utils/user';
import ButtonCopyToClipboard from './ButtonCopyToClipboard';
import ClickOutside from 'coral-framework/components/ClickOutside';
import {
@@ -28,6 +31,7 @@ import {
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import UserInfoTooltip from './UserInfoTooltip';
import KarmaTooltip from './KarmaTooltip';
import t from 'coral-framework/services/i18n';
import flatten from 'lodash/flatten';
@@ -112,7 +116,13 @@ class UserDetail extends React.Component {
renderLoaded() {
const {
root,
root: { me, user, totalComments, rejectedComments },
root: {
me,
user,
totalComments,
rejectedComments,
settings: { karmaThresholds },
},
activeTab,
selectedCommentIds,
toggleSelect,
@@ -286,18 +296,21 @@ class UserDetail extends React.Component {
{rejectedPercent.toFixed(1)}%
</span>
</li>
<li className={styles.stat}>
<span className={styles.statItem}>
{t('user_detail.reports')}
</span>
<span
className={cn(
styles.statReportResult,
styles[getReliability(user.reliable.flagger)]
)}
>
{capitalize(getReliability(user.reliable.flagger))}
</span>
<li className={cn(styles.stat, styles.karmaStat)}>
<div>
<span className={styles.statItem}>
{t('user_detail.karma')}
</span>
<span
className={cn(
styles.statKarmaResult,
styles[getKarma(user.reliable.commenter)]
)}
>
{user.reliable.commenterKarma}
</span>
</div>
<KarmaTooltip thresholds={karmaThresholds.comment} />
</li>
</ul>
</div>
@@ -189,7 +189,8 @@ export const withUserDetailQuery = withQuery(
provider
}
reliable {
flagger
commenter
commenterKarma
}
state {
status {
@@ -230,6 +231,14 @@ export const withUserDetailQuery = withQuery(
}
${getSlotFragmentSpreads(slots, 'user')}
}
settings {
karmaThresholds {
comment {
reliable
unreliable
}
}
}
me {
id
}
+51 -2
View File
@@ -24,6 +24,25 @@ const userRoleFragment = gql`
}
`;
/**
* calculateReliability will determine the reliability of a karma score based on
* the settings for the karma type.
*
* @param {Number} karma - the current karma value/score for the given user
* @param {Object} thresholds - the karma thresholds to base the karma computation on
*/
const calculateReliability = (karma, { reliable, unreliable }) => {
if (karma >= reliable) {
return true;
}
if (karma <= unreliable) {
return false;
}
return null;
};
export default {
mutations: {
SetUserRole: ({ variables: { id, role } }) => ({
@@ -156,7 +175,9 @@ export default {
}
const updated = update(prev, {
users: {
nodes: { $apply: nodes => nodes.filter(node => node.id !== id) },
nodes: {
$apply: nodes => nodes.filter(node => node.id !== id),
},
},
});
return updated;
@@ -185,7 +206,9 @@ export default {
const updated = update(prev, {
...decrement,
flaggedUsers: {
nodes: { $apply: nodes => nodes.filter(node => node.id !== id) },
nodes: {
$apply: nodes => nodes.filter(node => node.id !== id),
},
},
});
return updated;
@@ -295,12 +318,38 @@ export default {
updateQueries: {
CoralAdmin_UserDetail: prev => {
const increment = {
user: {
reliable: {
commenter: {
$set: calculateReliability(
prev.user.reliable.commenterKarma - 1,
prev.settings.karmaThresholds.comment
),
},
commenterKarma: {
$apply: count => count - 1,
},
},
},
rejectedComments: {
$apply: count => (count < prev.totalComments ? count + 1 : count),
},
};
const decrement = {
user: {
reliable: {
commenter: {
$set: calculateReliability(
prev.user.reliable.commenterKarma + 1,
prev.settings.karmaThresholds.comment
),
},
commenterKarma: {
$apply: count => count + 1,
},
},
},
rejectedComments: {
$apply: count => (count > 0 ? count - 1 : 0),
},
@@ -24,6 +24,7 @@ const ModerationMenu = ({ asset = {}, items, getModPath, activeTab }) => {
>
{items.map(queue => (
<Link
id={`talk-admin-moderate-tab-${queue.key}`}
key={queue.key}
to={getModPath(queue.key, asset.id)}
className={cn('mdl-tabs__tab', styles.tab, {
@@ -15,6 +15,7 @@ import {
} from 'react-virtualized';
import throttle from 'lodash/throttle';
import key from 'keymaster';
import cn from 'classnames';
const hasComment = (nodes, id) => nodes.some(node => node.id === id);
@@ -380,6 +381,11 @@ class ModerationQueue extends React.Component {
...props
} = this.props;
const rootClassName = cn(
styles.root,
`talk-admin-moderate-queue-${this.props.activeTab}`
);
if (comments.length === 0) {
return (
<div className={styles.root}>
@@ -401,7 +407,7 @@ class ModerationQueue extends React.Component {
const comment = comments[index];
return (
<div className={styles.root}>
<div className={rootClassName}>
<Comment
root={this.props.root}
key={comment.id}
@@ -423,7 +429,7 @@ class ModerationQueue extends React.Component {
const view = this.state.view;
return (
<div className={styles.root}>
<div className={rootClassName}>
<ViewMore
viewMore={() => this.viewNewComments()}
count={comments.length - view.length}
+8
View File
@@ -8,6 +8,14 @@ import reducers from './reducers';
import TalkProvider from 'coral-framework/components/TalkProvider';
import pluginsConfig from 'pluginsConfig';
// Resolves touch handling issues encountered on IOS Safari under certain
// circumstances. It may be related to issues reported here:
//
// https://stackoverflow.com/questions/12363742/touchstart-event-is-not-firing-inside-iframe-ios-6
//
// Further details: https://www.pivotaltracker.com/story/show/157794038
document.body.addEventListener('touchstart', () => {});
async function main() {
const context = await createContext({
reducers,
@@ -1,9 +1,9 @@
/* global __webpack_public_path__ */ // eslint-disable-line no-unused-vars
/* global __webpack_public_path__, __webpack_nonce__ */ // eslint-disable-line no-unused-vars
import { getStaticConfiguration } from 'coral-framework/services/staticConfiguration';
// Load the static url from the static configuration.
const { STATIC_URL } = getStaticConfiguration();
const { STATIC_URL, SCRIPT_NONCE } = getStaticConfiguration();
// Update the static url for the imported public path so dynamically imported
// chunks will use the correct path as defined by the process.env.STATIC_URL
@@ -14,3 +14,13 @@ const { STATIC_URL } = getStaticConfiguration();
// https://webpack.js.org/configuration/output/#output-publicpath
//
__webpack_public_path__ = STATIC_URL + 'static/';
// All dynamically included scripts that support nonce's will add this to their
// script tags.
//
// The __webpack_nonce__ can be referenced: https://webpack.js.org/guides/csp/
//
// Pending issues:
// - https://github.com/webpack-contrib/style-loader/pull/319
//
__webpack_nonce__ = SCRIPT_NONCE;
+15
View File
@@ -86,3 +86,18 @@ export const canUsernameBeUpdated = status => {
moment(created_at).isAfter(oldestEditTime)
);
};
/**
* getKarma
* retrieves karma value as string
*/
export const getKarma = reliability => {
if (reliability === null) {
return 'neutral';
} else if (reliability) {
return 'good';
} else {
return 'bad';
}
};