mirror of
https://github.com/wassname/talk.git
synced 2026-07-10 20:37:29 +08:00
Merge branch 'master' into 1920
This commit is contained in:
@@ -34,6 +34,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.filterHeader {
|
||||
margin-top: 30px;
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.filterDetail {
|
||||
margin-top: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.radioGroup {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
width: calc(100% - 300px);
|
||||
padding: 34px 14px;
|
||||
|
||||
@@ -9,6 +9,7 @@ import PropTypes from 'prop-types';
|
||||
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
|
||||
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
|
||||
import { isSuspended, isBanned } from 'coral-framework/utils/user';
|
||||
import { RadioGroup, Radio } from 'react-mdl';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
ADMIN,
|
||||
@@ -64,11 +65,12 @@ class People extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
onSearchChange,
|
||||
onFilterChange,
|
||||
users = [],
|
||||
setUserRole,
|
||||
viewUserDetail,
|
||||
loadMore,
|
||||
filters,
|
||||
} = this.props;
|
||||
|
||||
const hasResults = !!users.nodes.length;
|
||||
@@ -87,11 +89,43 @@ class People extends React.Component {
|
||||
id="commenters-search"
|
||||
type="text"
|
||||
className={styles.searchBoxInput}
|
||||
defaultValue=""
|
||||
onChange={onSearchChange}
|
||||
value={filters.search}
|
||||
onChange={onFilterChange('search')}
|
||||
placeholder={t('streams.search')}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.filterHeader}>
|
||||
{t('community.filter_users')}
|
||||
</div>
|
||||
<div className={styles.filterDetail}>{t('community.status')}</div>
|
||||
<RadioGroup
|
||||
name="statusFilter"
|
||||
value={filters.status}
|
||||
childContainer="div"
|
||||
onChange={onFilterChange('status')}
|
||||
className={styles.radioGroup}
|
||||
>
|
||||
<Radio value="">{t('community.all')}</Radio>
|
||||
<Radio value="active">{t('community.active')}</Radio>
|
||||
<Radio value="suspended">{t('community.suspended')}</Radio>
|
||||
<Radio value="banned">{t('community.banned')}</Radio>
|
||||
</RadioGroup>
|
||||
<div className={styles.filterDetail}>
|
||||
{t('community.filter_role')}
|
||||
</div>
|
||||
<RadioGroup
|
||||
name="roleFilter"
|
||||
value={filters.role}
|
||||
childContainer="div"
|
||||
onChange={onFilterChange('role')}
|
||||
className={styles.radioGroup}
|
||||
>
|
||||
<Radio value="">{t('community.all')}</Radio>
|
||||
<Radio value={ADMIN}>{t('community.admin')}</Radio>
|
||||
<Radio value={STAFF}>{t('community.staff')}</Radio>
|
||||
<Radio value={MODERATOR}>{t('community.moderator')}</Radio>
|
||||
<Radio value={COMMENTER}>{t('community.commenter')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
{hasResults ? (
|
||||
@@ -254,7 +288,8 @@ class People extends React.Component {
|
||||
|
||||
People.propTypes = {
|
||||
users: PropTypes.object.isRequired,
|
||||
onSearchChange: PropTypes.func.isRequired,
|
||||
filters: PropTypes.object.isRequired,
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
setUserRole: PropTypes.func.isRequired,
|
||||
viewUserDetail: PropTypes.func.isRequired,
|
||||
unbanUser: PropTypes.func.isRequired,
|
||||
|
||||
@@ -17,59 +17,65 @@ import update from 'immutability-helper';
|
||||
import { Spinner } from 'coral-ui';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
|
||||
class PeopleContainer extends React.Component {
|
||||
class PeopleContainer extends React.PureComponent {
|
||||
timer = null;
|
||||
|
||||
state = {
|
||||
searchValue: '',
|
||||
search: '',
|
||||
role: '',
|
||||
status: '',
|
||||
};
|
||||
|
||||
onSearchChange = e => {
|
||||
const { value } = e.target;
|
||||
this.setState({ searchValue: value }, () => {
|
||||
statusToQuery = {
|
||||
active: { suspended: false, banned: false },
|
||||
suspended: { suspended: true },
|
||||
banned: { banned: true },
|
||||
};
|
||||
|
||||
onFilterChange = filter => e =>
|
||||
this.setState({ [filter]: e.target.value }, () => {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.search(value);
|
||||
}, 350);
|
||||
this.timer = setTimeout(this.filter, 350);
|
||||
});
|
||||
|
||||
getFilterState = () => {
|
||||
const { role, status } = this.state;
|
||||
|
||||
return {
|
||||
status: this.statusToQuery[status] || null,
|
||||
role: role || null,
|
||||
};
|
||||
};
|
||||
|
||||
search = async value => {
|
||||
return this.props.data.fetchMore({
|
||||
query: SEARCH_QUERY,
|
||||
filter = () =>
|
||||
this.props.data.fetchMore({
|
||||
query: FILTER_QUERY,
|
||||
variables: {
|
||||
value,
|
||||
state: this.getFilterState(),
|
||||
value: this.state.search,
|
||||
limit: 10,
|
||||
},
|
||||
updateQuery: (previous, { fetchMoreResult: { users } }) => {
|
||||
const updated = update(previous, {
|
||||
updateQuery: (previous, { fetchMoreResult: { users } }) =>
|
||||
update(previous, {
|
||||
users: {
|
||||
nodes: {
|
||||
$set: users.nodes,
|
||||
},
|
||||
nodes: { $set: users.nodes },
|
||||
hasNextPage: { $set: users.hasNextPage },
|
||||
endCursor: { $set: users.endCursor },
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
setUserRole = async (id, role) => {
|
||||
await this.props.setUserRole(id, role);
|
||||
};
|
||||
|
||||
loadMore = () => {
|
||||
return this.props.data.fetchMore({
|
||||
loadMore = () =>
|
||||
this.props.data.fetchMore({
|
||||
query: LOAD_MORE_QUERY,
|
||||
variables: {
|
||||
value: this.state.searchValue,
|
||||
limit: 5,
|
||||
cursor: this.props.root.users.endCursor,
|
||||
state: this.getFilterState(),
|
||||
value: this.state.search,
|
||||
limit: 5,
|
||||
},
|
||||
updateQuery: (previous, { fetchMoreResult: { users } }) => {
|
||||
const updated = update(previous, {
|
||||
updateQuery: (previous, { fetchMoreResult: { users } }) =>
|
||||
update(previous, {
|
||||
users: {
|
||||
nodes: {
|
||||
$apply: nodes => appendNewNodes(nodes, users.nodes),
|
||||
@@ -77,11 +83,8 @@ class PeopleContainer extends React.Component {
|
||||
hasNextPage: { $set: users.hasNextPage },
|
||||
endCursor: { $set: users.endCursor },
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.props.data.error) {
|
||||
@@ -98,9 +101,9 @@ class PeopleContainer extends React.Component {
|
||||
|
||||
return (
|
||||
<People
|
||||
onSearchChange={this.onSearchChange}
|
||||
onFilterChange={this.onFilterChange}
|
||||
viewUserDetail={this.props.viewUserDetail}
|
||||
setUserRole={this.setUserRole}
|
||||
setUserRole={this.props.setUserRole}
|
||||
showSuspendUserDialog={this.props.showSuspendUserDialog}
|
||||
showBanUserDialog={this.props.showBanUserDialog}
|
||||
unbanUser={this.props.unbanUser}
|
||||
@@ -109,6 +112,7 @@ class PeopleContainer extends React.Component {
|
||||
root={this.props.root}
|
||||
users={this.props.root.users}
|
||||
loadMore={this.loadMore}
|
||||
filters={this.state}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -125,16 +129,6 @@ PeopleContainer.propTypes = {
|
||||
root: PropTypes.object,
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
viewUserDetail,
|
||||
showSuspendUserDialog,
|
||||
showBanUserDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const LOAD_MORE_QUERY = gql`
|
||||
query TalkAdmin_Community_People_LoadMoreUsers(
|
||||
$limit: Int
|
||||
@@ -169,9 +163,13 @@ const LOAD_MORE_QUERY = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const SEARCH_QUERY = gql`
|
||||
query TalkAdmin_Community_People_SearchUsers($value: String, $limit: Int) {
|
||||
users(query: { value: $value, limit: $limit }) {
|
||||
const FILTER_QUERY = gql`
|
||||
query TalkAdmin_Community_People_FilterUsers(
|
||||
$state: UserStateInput
|
||||
$value: String
|
||||
$limit: Int
|
||||
) {
|
||||
users(query: { state: $state, value: $value, limit: $limit }) {
|
||||
hasNextPage
|
||||
endCursor
|
||||
nodes {
|
||||
@@ -199,6 +197,16 @@ const SEARCH_QUERY = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
viewUserDetail,
|
||||
showSuspendUserDialog,
|
||||
showBanUserDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
export default compose(
|
||||
connect(
|
||||
null,
|
||||
|
||||
@@ -38,5 +38,4 @@
|
||||
position: relative;
|
||||
margin-top: 28px;
|
||||
padding-bottom: 50px;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ export const checkLogin = () => (
|
||||
|
||||
dispatch(checkLoginSuccess(result.user));
|
||||
pym.sendMessage('coral-auth-changed', JSON.stringify(result.user));
|
||||
client.resetWebsocket();
|
||||
|
||||
// We don't need to reset the websocket here because if the request
|
||||
// returned that there was a user (which is the case here), then the
|
||||
// original request has already succeeded, or a previous call to a token
|
||||
// set handler has already reset it.
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.status && error.status === 401 && localStorage) {
|
||||
@@ -49,7 +53,11 @@ const checkLoginSuccess = user => ({
|
||||
user,
|
||||
});
|
||||
|
||||
export const setAuthToken = token => (dispatch, _, { localStorage }) => {
|
||||
export const setAuthToken = token => (
|
||||
dispatch,
|
||||
_,
|
||||
{ localStorage, client }
|
||||
) => {
|
||||
localStorage.setItem('exp', jwtDecode(token).exp);
|
||||
localStorage.setItem('token', token);
|
||||
|
||||
@@ -57,6 +65,9 @@ export const setAuthToken = token => (dispatch, _, { localStorage }) => {
|
||||
// may not be able to persist the auth token any other way. Keep it in redux!
|
||||
dispatch({ type: actions.SET_AUTH_TOKEN, token });
|
||||
|
||||
// Now that we set a token, let's reset the subscriptions.
|
||||
client.resetWebsocket();
|
||||
|
||||
dispatch(checkLogin());
|
||||
};
|
||||
|
||||
@@ -79,6 +90,7 @@ export const handleSuccessfulLogin = (user, token) => (
|
||||
);
|
||||
}
|
||||
|
||||
// Now that we just set a token, set the token!
|
||||
client.resetWebsocket();
|
||||
|
||||
dispatch({
|
||||
|
||||
@@ -88,21 +88,23 @@ export function createClient(options = {}) {
|
||||
});
|
||||
|
||||
client.resetWebsocket = () => {
|
||||
// Close socket connection which will also unregister subscriptions on the server-side.
|
||||
wsClient.close();
|
||||
if (wsClient.client) {
|
||||
// Close socket connection which will also unregister subscriptions on the server-side.
|
||||
wsClient.close(true);
|
||||
|
||||
// Reconnect to the server.
|
||||
wsClient.connect();
|
||||
// Reconnect to the server.
|
||||
wsClient.connect();
|
||||
|
||||
// Reregister all subscriptions (uses non public api).
|
||||
// See: https://github.com/apollographql/subscriptions-transport-ws/issues/171
|
||||
Object.keys(wsClient.operations).forEach(id => {
|
||||
wsClient.sendMessage(
|
||||
id,
|
||||
MessageTypes.GQL_START,
|
||||
wsClient.operations[id].options
|
||||
);
|
||||
});
|
||||
// Re-register all subscriptions (uses non public api).
|
||||
// See: https://github.com/apollographql/subscriptions-transport-ws/issues/171
|
||||
Object.keys(wsClient.operations).forEach(id => {
|
||||
wsClient.sendMessage(
|
||||
id,
|
||||
MessageTypes.GQL_START,
|
||||
wsClient.operations[id].options
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return client;
|
||||
|
||||
@@ -15,7 +15,7 @@ Even if GDPR will not apply to you, it is recommended to enable these
|
||||
features as a best practice to provide your users with control over their own
|
||||
data.
|
||||
|
||||
## GPDR Feature Overview
|
||||
## GDPR Feature Overview
|
||||
|
||||
Integrating our GDPR tools will give your users and organizations the following benefits:
|
||||
|
||||
|
||||
@@ -343,6 +343,20 @@ class ErrCommentTooShort extends TalkError {
|
||||
}
|
||||
}
|
||||
|
||||
// ErrCommentTooLong is returned when the comment is too long.
|
||||
class ErrCommentTooLong extends TalkError {
|
||||
constructor(length, allowed) {
|
||||
super(
|
||||
'Comment was too long',
|
||||
{
|
||||
translation_key: 'COMMENT_TOO_LONG',
|
||||
status: 400,
|
||||
},
|
||||
{ length, allowed }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ErrAssetURLAlreadyExists is returned when a rename operation is requested
|
||||
// but an asset already exists with the new url.
|
||||
class ErrAssetURLAlreadyExists extends TalkError {
|
||||
@@ -413,13 +427,15 @@ module.exports = {
|
||||
ErrAssetURLAlreadyExists,
|
||||
ErrAuthentication,
|
||||
ErrCannotIgnoreStaff,
|
||||
ErrCommentTooShort,
|
||||
ErrCommentingDisabled,
|
||||
ErrCommentTooLong,
|
||||
ErrCommentTooShort,
|
||||
ErrContainsProfanity,
|
||||
ErrEditWindowHasEnded,
|
||||
ErrEmailAlreadyVerified,
|
||||
ErrEmailTaken,
|
||||
ErrEmailVerificationToken,
|
||||
ErrHTTPNotFound,
|
||||
ErrInstallLock,
|
||||
ErrInvalidAssetURL,
|
||||
ErrLoginAttemptMaximumExceeded,
|
||||
@@ -441,5 +457,4 @@ module.exports = {
|
||||
ErrSpecialChars,
|
||||
ErrUsernameTaken,
|
||||
ExtendableError,
|
||||
ErrHTTPNotFound,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,11 @@ const { SEARCH_OTHER_USERS } = require('../../perms/constants');
|
||||
const { escapeRegExp } = require('../../services/regex');
|
||||
|
||||
const mergeState = (query, state) => {
|
||||
const { status } = state;
|
||||
const { role, status } = state;
|
||||
|
||||
if (role) {
|
||||
query.merge({ role });
|
||||
}
|
||||
|
||||
if (status) {
|
||||
const { username, banned, suspended } = status;
|
||||
|
||||
@@ -189,6 +189,7 @@ type UserStatus {
|
||||
}
|
||||
|
||||
input UserStateInput {
|
||||
role: USER_ROLES
|
||||
status: UserStatusInput
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -225,6 +225,7 @@ ar:
|
||||
CANNOT_IGNORE_STAFF: 'لا يمكن تجاهل الموظفين.'
|
||||
COMMENT_PARENT_NOT_VISIBLE: 'التعليق الذي ترد عليه تمت إزالته أو غير موجود.'
|
||||
COMMENT_TOO_SHORT: 'يجب أن تكون التعليقات أكثر من حرف واحد، يرجى مراجعة تعليقك وإعادة المحاولة.'
|
||||
COMMENT_TOO_LONG: 'يتجاوز النص الحد الأقصى للطول المسموح'
|
||||
COMMENTING_CLOSED: 'تم إغلاق فاعلية التعليق'
|
||||
COMMENTING_DISABLED: 'التعليق معطّل حاليًا على هذا الموقع'
|
||||
confirm_password: 'كلمات المرور غير متطابقة. يرجى التحقق مرة أخرى'
|
||||
@@ -281,7 +282,6 @@ ar:
|
||||
reasons:
|
||||
comment:
|
||||
banned_word: 'كلمة محظورة'
|
||||
body_count: 'يتجاوز النص الحد الأقصى للطول المسموح'
|
||||
comment_noagree: أعارض
|
||||
comment_offensive: مسيء
|
||||
comment_other: آخر
|
||||
|
||||
+1
-1
@@ -197,6 +197,7 @@ da:
|
||||
CANNOT_IGNORE_STAFF: 'Kan ikke ignorere personale.'
|
||||
COMMENT_PARENT_NOT_VISIBLE: 'Den kommentar, du svarer på er blevet fjernet eller eksisterer ikke.'
|
||||
COMMENT_TOO_SHORT: 'Din kommentar skal indeholde noget'
|
||||
COMMENT_TOO_LONG: 'Body overstiger max længde'
|
||||
COMMENTING_CLOSED: 'Kommentering er allerede lukket'
|
||||
confirm_password: 'Kodeordene matcher ikke. Tjek venligst igen.'
|
||||
EDIT_USERNAME_NOT_AUTHORIZED: 'Du har ikke tilladelse til at opdatere dit brugernavn.'
|
||||
@@ -238,7 +239,6 @@ da:
|
||||
reasons:
|
||||
comment:
|
||||
banned_word: 'Forbudt ord'
|
||||
body_count: 'Body overstiger max længde'
|
||||
comment_noagree: Uenig
|
||||
comment_offensive: Offensiv
|
||||
comment_other: Andre
|
||||
|
||||
+1
-1
@@ -224,6 +224,7 @@ de:
|
||||
CANNOT_IGNORE_STAFF: 'Mitarbeiter können nicht ignoriert werden.'
|
||||
COMMENT_PARENT_NOT_VISIBLE: 'Der Kommentar, auf den Sie antworten möchten, wurde entfernt oder existiert nicht.'
|
||||
COMMENT_TOO_SHORT: 'Kommentare sollten mehr als ein Zeichen enthalten, bitte überprüfen Sie Ihren Kommentar und probieren Sie es erneut.'
|
||||
COMMENT_TOO_LONG: 'Text überschreitet Zeichenlimit'
|
||||
COMMENTING_CLOSED: 'Kommentarbereich ist bereits geschlossen'
|
||||
COMMENTING_DISABLED: 'Die Kommentarfunktion ist derzeit abgeschaltet'
|
||||
confirm_password: 'Passwörter nicht identisch. Bitte erneut überprüfen'
|
||||
@@ -273,7 +274,6 @@ de:
|
||||
reasons:
|
||||
comment:
|
||||
banned_word: 'Unzulässiges Wort'
|
||||
body_count: 'Text überschreitet Zeichenlimit'
|
||||
comment_noagree: 'Andere Meinung'
|
||||
comment_offensive: Unangemessen
|
||||
comment_other: Anderes
|
||||
|
||||
+5
-1
@@ -62,6 +62,7 @@ en:
|
||||
active: Active
|
||||
admin: Administrator
|
||||
ads_marketing: 'This looks like an ad/marketing'
|
||||
all: 'All'
|
||||
are_you_sure: 'Are you sure you would like to ban {0}?'
|
||||
ban_user: 'Ban User?'
|
||||
banned: Banned
|
||||
@@ -69,6 +70,8 @@ en:
|
||||
cancel: Cancel
|
||||
commenter: Commenter
|
||||
dont_like_username: 'Dislike username'
|
||||
filter_users: 'Filter users'
|
||||
filter_role: Role
|
||||
flaggedaccounts: 'Reported Usernames'
|
||||
flags: Flags
|
||||
impersonating: Impersonation
|
||||
@@ -85,6 +88,7 @@ en:
|
||||
spam_ads: Spam/Ads
|
||||
staff: Staff
|
||||
status: Status
|
||||
suspended: Suspended
|
||||
username_and_email: 'Username and Email'
|
||||
yes_ban_user: 'Yes Ban User'
|
||||
configure:
|
||||
@@ -229,6 +233,7 @@ en:
|
||||
CANNOT_IGNORE_STAFF: 'Cannot ignore Staff members.'
|
||||
COMMENT_PARENT_NOT_VISIBLE: 'The comment that you''re replying to has been removed or doesn''t exist.'
|
||||
COMMENT_TOO_SHORT: 'Comments should be more than one character, please revise your comment and try again.'
|
||||
COMMENT_TOO_LONG: 'Body exceeds max length'
|
||||
COMMENTING_CLOSED: 'Commenting is already closed'
|
||||
COMMENTING_DISABLED: 'Commenting is currently disabled on this site'
|
||||
confirm_password: 'Passwords don''t match. Please check again'
|
||||
@@ -286,7 +291,6 @@ en:
|
||||
reasons:
|
||||
comment:
|
||||
banned_word: 'Banned Word'
|
||||
body_count: 'Body exceeds max length'
|
||||
comment_noagree: Disagree
|
||||
comment_offensive: Offensive
|
||||
comment_other: Other
|
||||
|
||||
+5
-1
@@ -57,6 +57,7 @@ es:
|
||||
active: Activa
|
||||
admin: Administrator
|
||||
ads_marketing: 'Esto parece ser un ad/marketing'
|
||||
all: 'Todos'
|
||||
are_you_sure: '¿Estás segura que quieres suspender a {0}?'
|
||||
ban_user: '¿Quieres suspender al Usuario?'
|
||||
banned: Suspendido
|
||||
@@ -64,6 +65,8 @@ es:
|
||||
cancel: Cancelar
|
||||
commenter: Comentarista
|
||||
dont_like_username: 'No me gusta este nombre de usuario'
|
||||
filter_users: 'Filter users'
|
||||
filter_role: Rol
|
||||
flaggedaccounts: 'Nombres de Usuario Reportados'
|
||||
flags: Reportes
|
||||
impersonating: Impersonando
|
||||
@@ -80,6 +83,7 @@ es:
|
||||
spam_ads: Spam/Publicidad
|
||||
staff: Personal
|
||||
status: Estado
|
||||
suspended: Suspendido
|
||||
username_and_email: 'Usuario y Correo'
|
||||
yes_ban_user: 'Si, Suspendan el usuario'
|
||||
configure:
|
||||
@@ -215,6 +219,7 @@ es:
|
||||
CANNOT_IGNORE_STAFF: 'No puede ignorar a miembros del Staff.'
|
||||
COMMENT_PARENT_NOT_VISIBLE: 'El comentario a la que estás contestando ha sido eliminado o no existe.'
|
||||
COMMENT_TOO_SHORT: 'Tu comentario debe tener algo escrito'
|
||||
COMMENT_TOO_LONG: 'El texto excede el límite permitido'
|
||||
COMMENTING_CLOSED: 'Los comentarios ya estan cerrados'
|
||||
confirm_password: 'Las contraseñas no coinciden. Inténtelo nuevamente'
|
||||
EDIT_USERNAME_NOT_AUTHORIZED: 'No tiene permiso para editar el nombre de usuario.'
|
||||
@@ -260,7 +265,6 @@ es:
|
||||
reasons:
|
||||
comment:
|
||||
banned_word: 'Palabra prohibida'
|
||||
body_count: 'El texto exede el límite permitido'
|
||||
comment_noagree: 'No está de acuerdo'
|
||||
comment_offensive: 'Es ofensivo'
|
||||
comment_other: 'Otra razón'
|
||||
|
||||
+1
-1
@@ -197,6 +197,7 @@ fi_FI:
|
||||
CANNOT_IGNORE_STAFF: 'Työntekijöitä ei voi jättää huomioimatta'
|
||||
COMMENT_PARENT_NOT_VISIBLE: 'Kommenttia, johon yrität vastata, ei enää ole.'
|
||||
COMMENT_TOO_SHORT: 'Kommentin tulee olla vähintään kaksi merkkiä pitkä. Tarkista kirjoittamasi teksti.'
|
||||
COMMENT_TOO_LONG: Liian pitkä viesti'
|
||||
COMMENTING_CLOSED: 'Kommentointi on suljettu'
|
||||
confirm_password: 'Salasanat eivät täsmää. Tarkista, ole hyvä.'
|
||||
EDIT_USERNAME_NOT_AUTHORIZED: 'Sinulla ei ole oikeutta päivittää tai muokata käyttäjänimeä.'
|
||||
@@ -240,7 +241,6 @@ fi_FI:
|
||||
reasons:
|
||||
comment:
|
||||
banned_word: 'Kielletty sana'
|
||||
body_count: 'Liian pitkä viesti'
|
||||
comment_noagree: 'Olen eri mieltä'
|
||||
comment_offensive: Loukkaava
|
||||
comment_other: Muu
|
||||
|
||||
+1
-1
@@ -191,6 +191,7 @@ fr:
|
||||
CANNOT_IGNORE_STAFF: 'Ne peut pas ignorer les membres de l''équipe.'
|
||||
COMMENT_PARENT_NOT_VISIBLE: 'Le commentaire auquel vous répondez a été supprimé ou n’existe plus.'
|
||||
COMMENT_TOO_SHORT: 'Votre commentaire doit contenir quelque chose'
|
||||
COMMENT_TOO_LONG: 'Le texte dépasse la longueur maximale'
|
||||
COMMENTING_CLOSED: 'Les commentaires sont déjà fermés'
|
||||
confirm_password: 'Les mots de passe ne correspondent pas. Vérifiez à nouveau'
|
||||
EDIT_USERNAME_NOT_AUTHORIZED: 'Vous n''avez pas la permission de mettre à jour votre nom d''utilisateur.'
|
||||
@@ -235,7 +236,6 @@ fr:
|
||||
reasons:
|
||||
comment:
|
||||
banned_word: 'Mot banni'
|
||||
body_count: 'Le texte dépasse la longueur maximale'
|
||||
comment_noagree: 'Pas d’accord'
|
||||
comment_offensive: Offensive
|
||||
comment_other: Autre
|
||||
|
||||
+1
-1
@@ -197,6 +197,7 @@ nl_NL:
|
||||
CANNOT_IGNORE_STAFF: 'Kan geen Staff-leden negeren.'
|
||||
COMMENT_PARENT_NOT_VISIBLE: 'De reactie waarop je probeert te reageren bestaat niet of is inmiddels verwijderd.'
|
||||
COMMENT_TOO_SHORT: 'Reacties moeten meer dan één teken hebben. Herzie je reactie en probeer opnieuw.'
|
||||
COMMENT_TOO_LONG: 'Tekst is te lang'
|
||||
COMMENTING_CLOSED: 'Reageren is al afgesloten.'
|
||||
confirm_password: 'Wachtwoorden komen niet overeen. Controleer opnieuw.'
|
||||
EDIT_USERNAME_NOT_AUTHORIZED: 'Je bent niet gemachtigd om je gebruikersnaam te wijzigen.'
|
||||
@@ -239,7 +240,6 @@ nl_NL:
|
||||
reasons:
|
||||
comment:
|
||||
banned_word: 'Geblokeerd woord'
|
||||
body_count: 'Tekst is te lang'
|
||||
comment_noagree: 'Niet eens'
|
||||
comment_offensive: Aanstootgevend
|
||||
comment_other: Anders
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talk",
|
||||
"version": "4.6.4",
|
||||
"version": "4.6.5",
|
||||
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
||||
"main": "app.js",
|
||||
"private": true,
|
||||
@@ -201,7 +201,7 @@
|
||||
"smoothscroll-polyfill": "^0.3.5",
|
||||
"snake-case": "2.1.0",
|
||||
"style-loader": "^0.16.0",
|
||||
"subscriptions-transport-ws": "^0.8.3",
|
||||
"subscriptions-transport-ws": "^0.7.2",
|
||||
"supports-color": "^4",
|
||||
"timeago.js": "^2.0.3",
|
||||
"timekeeper": "^1.0.0",
|
||||
|
||||
@@ -75,7 +75,7 @@ es:
|
||||
Puede editar el comentario o enviarlo para la revisión del moderador.
|
||||
talk-plugin-toxic-comments:
|
||||
unlikely: "Improbable"
|
||||
highly_likely: "Altamente Improbable"
|
||||
highly_likely: "Altamente Probable"
|
||||
possibly: "Posiblemente"
|
||||
likely: "Probable"
|
||||
toxic_comment: "Comentario Tóxico"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"author": "The Coral Project Team <coral@mozillafoundation.org>",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"debug": "^4.0.1",
|
||||
"ms": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { getScores, isToxic } = require('./perspective');
|
||||
const { ErrToxic } = require('./errors');
|
||||
const debug = require('debug')('talk:plugin:toxic-comments');
|
||||
|
||||
function handlePositiveToxic(input) {
|
||||
input.status = 'SYSTEM_WITHHELD';
|
||||
@@ -20,7 +21,7 @@ async function getScore(body) {
|
||||
scores = await getScores(body);
|
||||
} catch (err) {
|
||||
// Warn and let mutation pass.
|
||||
console.trace(err); // TODO: log/handle this differently?
|
||||
debug('Error sending to API: %o', err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
API_TIMEOUT,
|
||||
DO_NOT_STORE,
|
||||
} = require('./config');
|
||||
const debug = require('debug')('talk:plugin:toxic-comments');
|
||||
|
||||
/**
|
||||
* Get scores from the perspective api
|
||||
@@ -13,6 +14,8 @@ const {
|
||||
* @return {object} object containing toxicity scores
|
||||
*/
|
||||
async function getScores(text) {
|
||||
debug('Sending to Perspective: %o', text);
|
||||
|
||||
const response = await fetch(
|
||||
`${API_ENDPOINT}/comments:analyze?key=${API_KEY}`,
|
||||
{
|
||||
@@ -25,7 +28,6 @@ async function getScores(text) {
|
||||
comment: {
|
||||
text,
|
||||
},
|
||||
|
||||
// TODO: support other languages.
|
||||
languages: ['en'],
|
||||
doNotStore: DO_NOT_STORE,
|
||||
@@ -36,7 +38,22 @@ async function getScores(text) {
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// If we get an error, just say it's not a toxic comment.
|
||||
if (data.error) {
|
||||
debug('Recieved Error when submitting: %o', data.error);
|
||||
return {
|
||||
TOXICITY: {
|
||||
summaryScore: 0.0,
|
||||
},
|
||||
SEVERE_TOXICITY: {
|
||||
summaryScore: 0.0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
TOXICITY: {
|
||||
summaryScore: data.attributeScores.TOXICITY.summaryScore.value,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { ErrCommentTooShort } = require('../../../errors');
|
||||
const { ErrCommentTooShort, ErrCommentTooLong } = require('../../../errors');
|
||||
|
||||
// This phase checks to see if the comment is long enough.
|
||||
module.exports = (
|
||||
@@ -17,19 +17,6 @@ module.exports = (
|
||||
|
||||
// Reject if the comment is too long
|
||||
if (charCountEnable && comment.body.length > charCount) {
|
||||
// Add the flag related to Trust to the comment.
|
||||
return {
|
||||
status: 'REJECTED',
|
||||
actions: [
|
||||
{
|
||||
action_type: 'FLAG',
|
||||
user_id: null,
|
||||
group_id: 'BODY_COUNT',
|
||||
metadata: {
|
||||
count: comment.body.length,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
throw new ErrCommentTooLong(comment.body.length, charCount);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user