Merge branch 'docs-features' of https://github.com/coralproject/talk into docs-features

This commit is contained in:
David Erwin
2017-07-11 11:06:25 -04:00
46 changed files with 811 additions and 205 deletions
+9 -2
View File
@@ -9,13 +9,17 @@ const program = require('./commander');
program
.command('serve', 'serve the application')
.command('settings', 'interact with the application settings')
.command('assets', 'interact with assets')
.command('setup', 'setup the application')
.command('jobs', 'work with the job queues')
.command('token', 'work with the access tokens')
.command('users', 'work with the application auth')
.command('migration', 'provides utilities for migrating the database')
.command('plugins', 'provides utilities for interacting with the plugin system')
.command(
'plugins',
'provides utilities for interacting with the plugin system'
)
.parse(process.argv);
/**
@@ -25,7 +29,10 @@ program
* labled with the PID written out by the parent process.
*/
process.once('exit', () => {
if ((program.runningCommand.killed === false) && (program.runningCommand.exitCode === null)) {
if (
program.runningCommand.killed === false &&
program.runningCommand.exitCode === null
) {
program.runningCommand.kill('SIGINT');
}
});
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env node
const program = require('./commander');
const inquirer = require('inquirer');
const mongoose = require('../services/mongoose');
const SettingsService = require('../services/settings');
const util = require('./util');
// Register the shutdown criteria.
util.onshutdown([() => mongoose.disconnect()]);
/**
* Change the organization name
*/
async function changeOrgName() {
try {
let settings = await SettingsService.retrieve();
let {organizationName} = await inquirer.prompt([
{
name: 'organizationName',
message: 'Organization Name',
default: settings.organizationName
}
]);
if (settings.organizationName !== organizationName) {
settings.organizationName = organizationName;
await SettingsService.update(settings);
console.log('Settings were updated.');
} else {
console.log('No update needed, no change was made.');
}
} catch (err) {
console.error(err);
util.shutdown(1);
}
util.shutdown();
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('change-org-name')
.description('change the organization name')
.action(changeOrgName);
program.parse(process.argv);
// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
program.outputHelp();
util.shutdown();
}
@@ -1,4 +1,4 @@
import {pym} from 'coral-framework';
import pym from 'coral-framework/services/pym';
import * as actions from '../constants/stream';
export const setActiveReplyBox = (id) => ({type: actions.SET_ACTIVE_REPLY_BOX, id});
@@ -1,11 +1,41 @@
.Reply {
position: relative;
.root {
margin-top: 16px;
margin-left: 20px;
margin-bottom: 15px;
position: relative;
}
.Comment {
margin-bottom: 15px;
position: relative;
.rootLevel0 {
margin-left: 0px;
}
.comment {
padding-left: 20px;
}
.commentLevel0 {
padding-left: 0px;
}
.commentLevel1 {
border-left: 3px #212121 solid;
}
.commentLevel2 {
border-left: 2px #6a6a6a solid;
}
.commentLevel3 {
border-left: 2px #9e9e9e solid;
}
.commentLevel4 {
border-left: 2px #c1c1c1 solid;
}
.highlightedComment {
padding-left: 20px;
border-left: 3px solid rgb(35,118,216);
}
.pendingComment {
@@ -9,6 +9,9 @@ import {can} from 'coral-framework/services/perms';
import {TransitionGroup} from 'react-transition-group';
import cn from 'classnames';
import styles from './Comment.css';
import {THREADING_LEVEL} from '../constants/stream';
import merge from 'lodash/merge';
import mapValues from 'lodash/mapValues';
import {
BestButton,
@@ -316,6 +319,7 @@ export default class Comment extends React.Component {
postDontAgree,
setActiveReplyBox,
activeReplyBox,
loadMore,
deleteAction,
disableReply,
maxCharCount,
@@ -331,9 +335,12 @@ export default class Comment extends React.Component {
const view = this.getVisibileReplies();
const {loadingState} = this.state;
const isReply = !!parentId;
const isPending = comment.id.indexOf('pending') >= 0;
const isHighlighted = highlighted === comment.id;
const hasMoreComments = comment.replies && (comment.replies.hasNextPage || comment.replies.nodes.length > view.length);
const replyCount = this.hasIgnoredReplies() ? '' : comment.replyCount;
const moreRepliesCount = this.hasIgnoredReplies() ? -1 : comment.replyCount - view.length;
const flagSummary = getActionSummary('FlagActionSummary', comment);
const dontAgreeSummary = getActionSummary(
'DontAgreeActionSummary',
@@ -346,11 +353,6 @@ export default class Comment extends React.Component {
myFlag = dontAgreeSummary.find((s) => s.current_user);
}
let commentClass = parentId
? `reply ${styles.Reply}`
: `comment ${styles.Comment}`;
commentClass += comment.id.indexOf('pending') >= 0 ? ` ${styles.pendingComment}` : '';
// call a function, and if it errors, call addNotification('error', ...) (e.g. to show user a snackbar)
const notifyOnError = (fn, errorToMessage) =>
async function(...args) {
@@ -386,7 +388,7 @@ export default class Comment extends React.Component {
);
/**
* classNamesToAdd
* conditionClassNames
* adds classNames based on condition
* classnames is an array of objects with key as classnames and value as conditions
* i.e:
@@ -396,35 +398,45 @@ export default class Comment extends React.Component {
*
* This will add myClassName to comments tagged with STAFF TAG.
* **/
const classNamesToAdd = commentClassNames.reduce((acc, className) => {
let res = [];
// Adding classNames based on tags
Object.keys(className).forEach((cn) => {
const condition = className[cn];
condition.tags.forEach((tag) => {
if (hasTag(comment.tags, tag)) {
res = [...res, cn];
}
});
const conditionalClassNames =
mapValues(merge({}, ...commentClassNames), (condition) => {
if (condition.tags) {
return condition.tags.some((tag) => hasTag(comment.tags, tag));
}
return false;
});
// TODO: Compare rest of the comment obj to the condition if needed
const rootClassName = cn(
'talk-stream-comment-wrapper',
`talk-stream-comment-wrapper-level-${depth}`,
styles.root,
styles[`rootLevel${depth}`],
{
...conditionalClassNames,
[styles.enter]: this.state.animateEnter,
},
);
return [...acc, ...res];
}, []);
const commentClassName = cn(
'talk-stream-comment',
`talk-stream-comment-level-${depth}`,
styles.comment,
styles[`commentLevel${depth}`],
{
[styles.pendingComment]: isPending,
[styles.highlightedComment]: isHighlighted,
'talk-stream-pending-comment': isPending,
'talk-stream-highlighted-comment': isHighlighted,
}
);
return (
<div
className={cn(commentClass, 'talk-stream-comment-wrapper', classNamesToAdd, {[styles.enter]: this.state.animateEnter})}
className={rootClassName}
id={`c_${comment.id}`}
style={{marginLeft: depth * 30}}
>
<hr aria-hidden={true} />
<div
className={highlighted === comment.id ? 'highlighted-comment' : ''}
>
{!isReply && <hr aria-hidden={true} />}
<div className={commentClassName}>
<AuthorName author={comment.user} className={'talk-stream-comment-user-name'} />
{isStaff(comment.tags) ? <TagLabel>Staff</TagLabel> : null}
@@ -553,7 +565,7 @@ export default class Comment extends React.Component {
charCountEnable={charCountEnable}
maxCharCount={maxCharCount}
setActiveReplyBox={setActiveReplyBox}
parentId={parentId || comment.id}
parentId={(depth < THREADING_LEVEL) ? comment.id : parentId}
addNotification={addNotification}
postComment={postComment}
currentUser={currentUser}
@@ -583,6 +595,7 @@ export default class Comment extends React.Component {
deleteAction={deleteAction}
addTag={addTag}
removeTag={removeTag}
loadMore={loadMore}
ignoreUser={ignoreUser}
charCountEnable={charCountEnable}
maxCharCount={maxCharCount}
@@ -598,7 +611,7 @@ export default class Comment extends React.Component {
<div className="talk-load-more-replies">
<LoadMore
topLevel={false}
replyCount={replyCount}
replyCount={moreRepliesCount}
moreComments={hasMoreComments}
loadMore={this.loadNewReplies}
loadingState={loadingState}
@@ -5,27 +5,15 @@ import t from 'coral-framework/services/i18n';
import cn from 'classnames';
class LoadMore extends React.Component {
initialState = true;
replyCountFormat = (count) => {
if (!count) {
return t('framework.view_all_replies_unknown_number');
return t('framework.show_all_replies');
}
if (count === 1) {
return t('framework.view_reply');
return t('framework.show_1_more_reply');
}
if (this.initialState) {
return t('framework.view_all_replies_initial', count);
} else {
return t('framework.view_all_replies', count);
}
}
componentWillReceiveProps(nextProps) {
if (['success', 'error'].indexOf(nextProps.loadingState) >= 0) {
this.initialState = false;
}
return t('framework.show_x_more_replies', count);
}
render () {
@@ -3,7 +3,7 @@ import React, {PropTypes} from 'react';
import t from 'coral-framework/services/i18n';
const NewCount = ({count, loadMore}) => {
return <div className='coral-new-comments coral-load-more'>
return <div className='talk-new-comments talk-load-more'>
{
count ?
<button onClick={loadMore}>
@@ -16,6 +16,7 @@ import IgnoredCommentTombstone from './IgnoredCommentTombstone';
import NewCount from './NewCount';
import {TransitionGroup} from 'react-transition-group';
import {forEachError} from 'coral-framework/utils';
import {getTopLevelParent} from '../graphql/utils';
const hasComment = (nodes, id) => nodes.some((node) => node.id === id);
@@ -169,9 +170,7 @@ class Stream extends React.Component {
const open = asset.closedAt === null;
// even though the permalinked comment is the highlighted one, we're displaying its parent + replies
const highlightedComment = comment && comment.parent
? comment.parent
: comment;
const highlightedComment = comment && getTopLevelParent(comment);
const banned = user && user.status === 'BANNED';
const temporarilySuspended =
@@ -3,3 +3,4 @@ export const ADDTL_COMMENTS_ON_LOAD_MORE = 10;
export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS';
export const ADD_COMMENT_CLASSNAME = 'ADD_COMMENT_CLASSNAME';
export const REMOVE_COMMENT_CLASSNAME = 'REMOVE_COMMENT_CLASSNAME';
export const THREADING_LEVEL = process.env.TALK_THREADING_LEVEL;
@@ -7,7 +7,9 @@ import branch from 'recompose/branch';
import renderComponent from 'recompose/renderComponent';
import {Spinner} from 'coral-ui';
import {authActions, assetActions, pym} from 'coral-framework';
import * as authActions from 'coral-framework/actions/auth';
import * as assetActions from 'coral-framework/actions/asset';
import pym from 'coral-framework/services/pym';
import {getDefinitionName} from 'coral-framework/utils';
import {withQuery} from 'coral-framework/hocs';
import Embed from '../components/Embed';
@@ -2,13 +2,14 @@ import React from 'react';
import {gql, compose} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {ADDTL_COMMENTS_ON_LOAD_MORE} from '../constants/stream';
import {ADDTL_COMMENTS_ON_LOAD_MORE, THREADING_LEVEL} from '../constants/stream';
import {
withPostComment, withPostFlag, withPostDontAgree, withDeleteAction,
withAddTag, withRemoveTag, withIgnoreUser, withEditComment,
} from 'coral-framework/graphql/mutations';
import {notificationActions, authActions} from 'coral-framework';
import * as authActions from 'coral-framework/actions/auth';
import * as notificationActions from 'coral-framework/actions/notification';
import {editName} from 'coral-framework/actions/user';
import {setActiveReplyBox} from '../actions/stream';
import Stream from '../components/Stream';
@@ -21,6 +22,7 @@ import {
insertCommentIntoEmbedQuery,
removeCommentFromEmbedQuery,
insertFetchedCommentsIntoEmbedQuery,
nest,
} from '../graphql/utils';
const {showSignInDialog} = authActions;
@@ -91,9 +93,7 @@ class StreamContainer extends React.Component {
}
loadNewReplies = (parent_id) => {
const comment = this.props.root.comment
? this.props.root.comment.parent || this.props.root.comment // highlighted comment.
: this.props.root.asset.comments.nodes.filter((comment) => comment.id === parent_id)[0];
const comment = findCommentInEmbedQuery(this.props.root, parent_id);
return this.props.data.fetchMore({
query: LOAD_MORE_QUERY,
@@ -157,16 +157,19 @@ const commentFragment = gql`
fragment CoralEmbedStream_Stream_comment on Comment {
id
...${getDefinitionName(Comment.fragments.comment)}
replyCount(excludeIgnored: $excludeIgnored)
replies(excludeIgnored: $excludeIgnored) {
nodes {
id
...${getDefinitionName(Comment.fragments.comment)}
${nest(`
replyCount(excludeIgnored: $excludeIgnored)
replies(excludeIgnored: $excludeIgnored) {
nodes {
id
...${getDefinitionName(Comment.fragments.comment)}
...nest
}
hasNextPage
startCursor
endCursor
}
hasNextPage
startCursor
endCursor
}
`, THREADING_LEVEL)}
}
${Comment.fragments.comment}
`;
@@ -205,16 +208,19 @@ const LOAD_MORE_QUERY = gql`
nodes {
id
...${getDefinitionName(Comment.fragments.comment)}
replyCount(excludeIgnored: $excludeIgnored)
replies(limit: 3, excludeIgnored: $excludeIgnored) {
nodes {
id
...${getDefinitionName(Comment.fragments.comment)}
${nest(`
replyCount(excludeIgnored: $excludeIgnored)
replies(limit: 3, excludeIgnored: $excludeIgnored) {
nodes {
id
...${getDefinitionName(Comment.fragments.comment)}
...nest
}
hasNextPage
startCursor
endCursor
}
hasNextPage
startCursor
endCursor
}
`, THREADING_LEVEL)}
}
hasNextPage
startCursor
@@ -229,9 +235,12 @@ const fragments = {
fragment CoralEmbedStream_Stream_root on RootQuery {
comment(id: $commentId) @include(if: $hasComment) {
...CoralEmbedStream_Stream_comment
parent {
...CoralEmbedStream_Stream_comment
}
${nest(`
parent {
...CoralEmbedStream_Stream_comment
...nest
}
`, THREADING_LEVEL)}
}
asset(id: $assetId, url: $assetUrl) {
id
+59 -19
View File
@@ -1,21 +1,29 @@
import update from 'immutability-helper';
function determineCommentDepth(comment) {
let depth = 0;
let cur = comment;
while (cur.parent) {
cur = cur.parent;
depth++;
}
return depth;
}
function applyToCommentsOrigin(root, callback) {
if (root.comment) {
if (root.comment.parent) {
return update(root, {
comment: {
parent: {
$apply: (node) => callback(node),
},
},
});
let comment = root.comment;
for (let depth = 0; depth <= determineCommentDepth(comment); depth++) {
let changes = {$apply: (node) => node ? callback(node) : node};
for (let i = 0; i < depth; i++) {
changes = {parent: changes};
}
comment = update(comment, changes);
}
return update(root, {
comment: {
$apply: (node) => callback(node),
},
});
return {
...root,
comment,
};
}
return update(root, {
asset: {$apply: (asset) => callback(asset)},
@@ -97,6 +105,13 @@ export function removeCommentFromEmbedQuery(root, id) {
return applyToCommentsOrigin(root, (origin) => findAndRemoveComment(origin, id));
}
export function getTopLevelParent(comment) {
if (comment.parent) {
return getTopLevelParent(comment.parent);
}
return comment;
}
function findComment(nodes, callback) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
@@ -119,12 +134,7 @@ export function findCommentInEmbedQuery(root, callbackOrId) {
callback = (node) => node.id === callbackOrId;
}
if (root.comment) {
if (callback(root.comment)) {
return root.comment;
}
if (root.comment.parent && callback(root.comment.parent)) {
return root.comment.parent;
}
return findComment([getTopLevelParent(root.comment)], callback);
}
if (!root.asset.comments) {
return false;
@@ -179,3 +189,33 @@ function findAndInsertFetchedComments(parent, comments, parent_id) {
export function insertFetchedCommentsIntoEmbedQuery(root, comments, parent_id) {
return applyToCommentsOrigin(root, (origin) => findAndInsertFetchedComments(origin, comments, parent_id));
}
/**
* Nest a string in itself repeatly until `level` has been reached.
*
* Example:
* nest(`
* a
* ...nest
* b
* `, 2)
*
* Output:
* `
* a
* a
* b
* b
* `
*/
export function nest(document, level) {
let result = '';
for (let x = 0; x < level; x++) {
if (x === 0) {
result += document;
continue;
}
result = result.replace('...nest', document);
}
return result.replace('...nest', '');
}
+1 -1
View File
@@ -7,8 +7,8 @@ import './graphql';
import {addExternalConfig} from 'coral-embed-stream/src/actions/config';
import {getStore, injectReducers} from 'coral-framework/services/store';
import {getClient} from 'coral-framework/services/client';
import pym from 'coral-framework/services/pym';
import AppRouter from './AppRouter';
import {pym} from 'coral-framework';
import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins';
import reducers from './reducers';
+24 -29
View File
@@ -26,7 +26,7 @@ body {
}
button {
margin: 5px 10px 5px 0px;
margin: 5px 0px 5px 0px;
background: none;
padding: 0px;
border: none;
@@ -266,11 +266,6 @@ hr {
float: right;
}
.highlighted-comment {
padding-left: 10px;
border-left: 3px solid rgb(35,118,216);
}
/* Tag Labels */
.coral-plugin-tag-label {
@@ -282,26 +277,16 @@ hr {
border-radius: 2px;
}
/* Reply styles */
.comment .reply {
margin: 0px 0px 10px 20px;
}
/* Comment Action Styles */
.commentActionsRight, .replyActionsRight {
display: flex;
justify-content: flex-end;
width: 30%;
}
.commentActionsLeft, .replyActionsLeft {
display: flex;
justify-content: flex-start;
float: left;
width: 70%;
}
.comment__action-container .material-icons {
@@ -422,9 +407,11 @@ button.comment__action-button[disabled],
}
.talk-load-more button {
width: 100%;
text-align: center;
color: #FFF;
background-color: #2376D8;
border-radius: 2px;
cursor: pointer;
padding: 10px;
border-radius: 2px;
@@ -437,30 +424,38 @@ button.comment__action-button[disabled],
background-color: #4399FF;
}
.talk-load-more-replies, .coral-new-comments {
.talk-new-comments {
width: 100%;
display: flex;
justify-content: center;
cursor: pointer;
}
.coral-new-comments {
.talk-load-more-replies {
width: 100%;
padding-left: 20px;
box-sizing: border-box;
}
.talk-new-comments {
position: relative;
top: 1.8em;
z-index: 100;
}
.talk-load-more-replies button.talk-load-more, .coral-new-comments button.talk-load-more{
.talk-load-more-replies .talk-load-more-button {
background-color: transparent;
color: #979797;
border: #979797 solid 1px;
border-radius: 2px;
}
.talk-load-more-replies .talk-load-more:hover button {
background-color: #979797;
color: white;
}
.talk-new-comments button.talk-load-more{
width: initial;
}
@media (min-device-width : 300px) and (max-device-width : 420px) {
.commentActionsLeft.comment__action-container .coral-plugin-replies-reply-button {
visibility: collapse;
margin-left: -30px;
}
.commentActionsLeft.comment__action-container .coral-plugin-replies-reply-button .coral-plugin-replies-icon {
visibility: visible;
}
}
+5 -1
View File
@@ -3,12 +3,16 @@ import bowser from 'bowser';
import * as actions from '../constants/auth';
import * as Storage from '../helpers/storage';
import coralApi, {base} from '../helpers/request';
import pym from '../services/PymConnection';
import pym from '../services/pym';
import {resetWebsocket} from 'coral-framework/services/client';
import t from 'coral-framework/services/i18n';
import {isSlotEmpty} from 'plugin-api/beta/client/services';
export const showSignInDialog = () => (dispatch, getState) => {
if (isSlotEmpty('login')) {
return;
}
const signInPopUp = window.open(
'/embed/stream/login',
'Login',
-9
View File
@@ -1,9 +0,0 @@
import * as authActions from './auth';
import * as assetActions from './asset';
import * as notificationActions from './notification';
export default {
authActions,
assetActions,
notificationActions,
};
@@ -1,4 +1,4 @@
import pym from '../services/PymConnection';
import pym from '../services/pym';
import * as actions from '../constants/notification';
export const addNotification = (notifType, text) => {
@@ -1,7 +1,7 @@
import {Component, cloneElement, Children} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';
import {pym} from 'coral-framework';
import pym from 'coral-framework/services/pym';
export default class ClickOutside extends Component {
static propTypes = {
+4 -2
View File
@@ -12,17 +12,19 @@ import camelize from './camelize';
function getSlotComponents(slot) {
const pluginConfig = getStore().getState().config.plugin_config;
// Filter out components that have been disabled in `plugin_config`
return flatten(plugins
// Filter out components that have been disabled in `plugin_config`
.filter((o) => !pluginConfig[o.plugin] || !pluginConfig[o.plugin].disable_components)
.filter((o) => !pluginConfig || !pluginConfig[o.plugin] || !pluginConfig[o.plugin].disable_components)
.filter((o) => o.module.slots[slot])
.map((o) => o.module.slots[slot]));
}
export function isSlotEmpty(slot) {
return !!getSlotComponents(slot).length;
return getSlotComponents(slot).length === 0;
}
/**
-10
View File
@@ -1,10 +0,0 @@
import pym from './services/PymConnection';
import actions from './actions';
// TODO (bc): Deprecate old actions. Spreading actions is now needed.
export default {
pym,
actions,
...actions
};
+1 -1
View File
@@ -1,6 +1,6 @@
import {Map, fromJS} from 'immutable';
import * as actions from '../constants/auth';
import {pym} from 'coral-framework';
import pym from 'coral-framework/services/pym';
const initialState = Map({
isLoading: false,
+4 -1
View File
@@ -4,13 +4,15 @@ import get from 'lodash/get';
import merge from 'lodash/merge';
import esTA from '../../../node_modules/timeago.js/locales/es';
import frTA from '../../../node_modules/timeago.js/locales/fr';
import en from '../../../locales/en.yml';
import es from '../../../locales/es.yml';
import fr from '../../../locales/fr.yml';
// Translations are happening at https://translate.lingohub.com/the-coral-project/dashboard
const defaultLanguage = 'en';
const translations = {...en, ...es};
const translations = {...en, ...es, ...fr};
let lang;
let timeagoInstance;
@@ -40,6 +42,7 @@ function init() {
}
ta.register('es', esTA);
ta.register('fr', frTA);
timeagoInstance = ta();
}
@@ -1,4 +1,4 @@
import Pym from '../../../node_modules/pym.js';
import Pym from 'pym.js';
const pym = new Pym.Child({polling: 100});
export default pym;
@@ -5,6 +5,8 @@ import {can} from 'coral-framework/services/perms';
import {PopupMenu, Button} from 'coral-ui';
import ClickOutside from 'coral-framework/components/ClickOutside';
import cn from 'classnames';
import styles from './styles.css';
const name = 'coral-plugin-flags';
@@ -146,14 +148,17 @@ export default class FlagButton extends Component {
<button
ref={(ref) => this.flagButton = ref}
onClick={!this.props.banned && !flaggedByCurrentUser && !localPost ? this.onReportClick : null}
className={`${name}-button`}>
className={cn(`${name}-button`, styles.button)}>
{
flagged
? <span className={`${name}-button-text`}>{t('reported')}</span>
: <span className={`${name}-button-text`}>{t('report')}</span>
}
<i className={`${name}-icon material-icons ${flagged && 'flaggedIcon'}`}
style={flagged ? styles.flaggedIcon : {}}
<i className={
cn(`${name}-icon`, 'material-icons', {
flaggedIcon: flagged,
[styles.flaggedIcon]: flagged,
})}
aria-hidden={true}>flag</i>
</button>
{
@@ -214,12 +219,3 @@ export default class FlagButton extends Component {
);
}
}
const styles = {
flaggedIcon: {
color: '#F00'
},
unflaggedIcon: {
color: 'inherit'
}
};
@@ -0,0 +1,7 @@
.button {
margin: 5px 0px 5px 10px;
}
.flaggedIcon {
color: #f00
}
@@ -0,0 +1,5 @@
@media (max-width: 425px) {
.label {
display: none;
}
}
+6 -3
View File
@@ -2,16 +2,19 @@ import React, {PropTypes} from 'react';
import t from 'coral-framework/services/i18n';
import classnames from 'classnames';
import cn from 'classnames';
import styles from './ReplyButton.css';
const name = 'coral-plugin-replies';
const ReplyButton = ({onClick}) => {
return (
<button
className={classnames(`${name}-reply-button`)}
className={cn(`${name}-reply-button`)}
onClick={onClick}>
{t('reply')}
<span className={cn(`${name}-label`, styles.label)}>
{t('reply')}
</span>
<i className={`${name}-icon material-icons`}
aria-hidden={true}>reply</i>
</button>
@@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
import {withStopIgnoringUser} from 'coral-framework/graphql/mutations';
import {link} from 'coral-framework/services/PymConnection';
import {link} from 'coral-framework/services/pym';
import NotLoggedIn from '../components/NotLoggedIn';
import IgnoredUsers from '../components/IgnoredUsers';
import {Spinner} from 'coral-ui';
+20 -7
View File
@@ -29,7 +29,7 @@ const CONFIG = {
// JWT_ISSUER is the value for the issuer for the tokens that will be verified
// when decoding. If `JWT_ISSUER` is not in the environment, then it will try
// `TALK_ROOT_URL`, otherwise, it will be undefined.
JWT_ISSUER: process.env.TALK_JWT_ISSUER || process.env.TALK_ROOT_URL || undefined,
JWT_ISSUER: process.env.TALK_JWT_ISSUER || process.env.TALK_ROOT_URL,
// JWT_EXPIRY is the time for which a given token is valid for.
JWT_EXPIRY: process.env.TALK_JWT_EXPIRY || '1 day',
@@ -91,7 +91,9 @@ const CONFIG = {
if (process.env.NODE_ENV === 'test' && !CONFIG.JWT_SECRET) {
CONFIG.JWT_SECRET = 'keyboard cat';
} else if (!CONFIG.JWT_SECRET) {
throw new Error('TALK_JWT_SECRET must be provided in the environment to sign/verify tokens');
throw new Error(
'TALK_JWT_SECRET must be provided in the environment to sign/verify tokens'
);
}
//------------------------------------------------------------------------------
@@ -108,7 +110,7 @@ if (process.env.NODE_ENV === 'test' && !CONFIG.MONGO_URL) {
// Reset the redis url in the event it hasn't been overrided and we are in a
// testing environment.
if (process.env.NODE_ENV === 'test' && !CONFIG.REDIS_URL) {
CONFIG.REDIS_URL = 'redis://localhost';
CONFIG.REDIS_URL = 'redis://localhost/1';
}
//------------------------------------------------------------------------------
@@ -119,10 +121,15 @@ if (process.env.NODE_ENV === 'test' && !CONFIG.REDIS_URL) {
* This is true when the recaptcha secret is provided and the Recaptcha feature
* is to be enabled.
*/
CONFIG.RECAPTCHA_ENABLED = CONFIG.RECAPTCHA_SECRET && CONFIG.RECAPTCHA_SECRET.length > 0 &&
CONFIG.RECAPTCHA_PUBLIC && CONFIG.RECAPTCHA_PUBLIC.length > 0;
CONFIG.RECAPTCHA_ENABLED =
CONFIG.RECAPTCHA_SECRET &&
CONFIG.RECAPTCHA_SECRET.length > 0 &&
CONFIG.RECAPTCHA_PUBLIC &&
CONFIG.RECAPTCHA_PUBLIC.length > 0;
if (!CONFIG.RECAPTCHA_ENABLED) {
console.warn('Recaptcha is not enabled for login/signup abuse prevention, set TALK_RECAPTCHA_SECRET and TALK_RECAPTCHA_PUBLIC to enable Recaptcha.');
console.warn(
'Recaptcha is not enabled for login/signup abuse prevention, set TALK_RECAPTCHA_SECRET and TALK_RECAPTCHA_PUBLIC to enable Recaptcha.'
);
}
//------------------------------------------------------------------------------
@@ -138,7 +145,13 @@ if (!CONFIG.RECAPTCHA_ENABLED) {
];
if (requiredProps.some((prop) => !CONFIG[prop])) {
console.warn(`${requiredProps.map((v) => `TALK_${v}`).join(', ')} should be defined in the environment if you would like to send password reset emails from Talk`);
console.warn(
`${requiredProps
.map((v) => `TALK_${v}`)
.join(
', '
)} should be defined in the environment if you would like to send password reset emails from Talk`
);
}
}
+3 -3
View File
@@ -235,9 +235,9 @@ en:
success_bio_update: "Your biography has been updated"
success_name_update: "Your username has been updated"
success_update_settings: "The changes you have made have been applied to the comment stream on this article"
view_all_replies_unknown_number: "view all replies"
view_all_replies: "view {0} replies"
view_all_replies_initial: "view all {0} replies"
show_all_replies: Show all replies
show_1_more_reply: Show 1 more reply
show_x_more_replies: Show {0} more replies
view_more_comments: "view more comments"
view_reply: "view reply"
from_settings_page: "From the Profile Page you can see your comment history."
+3 -3
View File
@@ -232,9 +232,9 @@ es:
success_bio_update: "Tu biografia fue actualizada"
success_name_update: "Tu nombre de usuario ha sido actualizado"
success_update_settings: "La configuración de este articulo fue actualizada"
view_all_replies_unknown_number: "ver todas las respuestas"
view_all_replies: "ver {0} respuestas"
view_all_replies_initial: "ver todas las {0} respuestas"
show_all_replies: "Mostrar todas las respuestas"
show_1_more_reply: "Mostrar 1 respuestas"
show_x_more_replies: "Mostrar {0} respuestas más"
view_more_comments: "Ver más comentarios"
view_reply: "ver respuesta"
from_settings_page: "Desde la página de configuración puedes ver tu historia de comentarios."
+355
View File
@@ -0,0 +1,355 @@
fr:
bandialog:
are_you_sure: "Êtes-vous sûr de vouloir bannir {0}?"
ban_user: "Bannir l'utilisateur ?"
banned_user: "Utilisateur banni"
cancel: Annuler
note: "Remarque: bannir cet utilisateur rejettera également ce commentaire."
yes_ban_user: "Oui, bannir cet utilisateur"
bio_offensive: "Cette biographie est offensante"
cancel: Annuler
characters_remaining: "caractères restants"
comment:
anon: Anonyme
ban_user: "Bannir utilisateur"
comment: "Publier un commentaire"
flagged: signalé
view_context: "Afficher le contexte"
comment_box:
post: "Publier"
cancel: "Annuler"
reply: "Répondre"
comment: "Publier un commentaire"
name: "Nom"
comment_post_notif: "Votre commentaire a été publié."
comment_post_notif_premod: "Merci d'avoir envoyé un commentaire. Notre équipe de modération passera en revue votre commentaire sous peu."
comment_post_banned_word: "Votre commentaire contient un ou plusieurs mots qui ne sont pas autorisés, il ne sera pas publié. Si vous pensez qu'il y a une erreur, contactez notre équipe de modération."
characters_remaining: "caractères restants"
comment_is_best: "Ce commentaire est l'un des meilleurs"
comment_offensive: "Ce commentaire est offensant"
comment_plural: Commentaires
comment_post_banned_word: "Votre commentaire contient un ou plusieurs mots qui ne sont pas autorisés, il ne sera pas publié. Si vous pensez qu'il y a une erreur, contactez notre équipe de modération."
comment_post_notif: "Votre commentaire a été publié."
comment_post_notif_premod: "Merci d'avoir envoyé un commentaire. Notre équipe de modération passera en revue votre commentaire sous peu."
community:
account_creation_date: "Date de création du compte"
active: Actif
admin: Administrateur
ads_marketing: "Cela ressemble à du marketing / un message publicitaire"
are_you_sure: "Êtes-vous sûr de vouloir bannir {0} ?"
ban_user: "Bannir l'utilisateur ?"
banned: Banni
banned_user: "Utilisateur banni"
cancel: Signalé
dont_like_username: "Je n'aime pas ce nom d'utilisateur"
flaggedaccounts: "Noms d'utilisateurs signalés"
flags: Signalements
impersonating: "Cet utilisateur se fait passer pour quelqu'un d'autre"
loading: "Chargement des résultats"
moderator: Modérateur
newsroom_role: "Rôle de la salle de presse"
no_flagged_accounts: "La liste des comptes signalés est vide."
no_results: "Aucun utilisateur n'a été trouvé avec ce nom d'utilisateur ou cette adresse de messagerie. Ils se cachent !"
note: "Remarque: bannir cet utilisateur ne lui permettra pas de modifier les commentaires ou de supprimer quoi que ce soit."
offensive: "Ce commentaire est offensant"
other: Autre
people: Gens
role: "Sélectionnez le rôle ..."
select_status: "Sélectionnez l'état ..."
spam_ads: "Spam / Annonces"
staff: "Équipe"
status: Statut
username_and_email: "Nom d'utilisateur et e-mail"
yes_ban_user: "Oui bannir l'utilisateur"
configure:
apply: Appliquer
banned_word_text: "Les commentaires qui contiennent ces mots ou ces expressions (non sensibles à la casse) seront automatiquement supprimés du flux de commentaires. Tapez un mot et appuyez sur Entrée ou Tabulation pour ajouter. En option, envoyez une liste séparée par des virgules."
banned_words_title: "Liste des mots interdits"
close: "Fermer"
close_after: "Fermer les commentaires après"
close_stream: "Fermer le fil"
close_stream_configuration: "Ce fil de commentaires est actuellement fermé. En ouvrant ce fil commentaire, de nouveaux commentaires peuvent être soumis et publiés."
closed_comments_desc: "Écrivez un message à afficher lorsque votre fil de commentaires est fermé et n'accepte plus de nouveaux commentaires."
closed_comments_label: "Écrire un message..."
closed_stream_settings: "Message de fil de commentaires fermé"
comment_count_error: "S'il vous plait, entrez un nombre valide."
comment_count_header: "Limiter la longueur d'un commentaire"
comment_count_text_post: caractères
comment_count_text_pre: "Les commentaires seront limités à"
comment_settings: Paramètres
comment_stream: "Fil de commentaires"
comment_stream_will_close: "Le fil de commentaires va se fermer dans"
community: Communauté
configure: Configurer
copy_and_paste: "Copiez et collez le code ci-dessous dans votre CMS pour intégrer votre boîte de commentaires dans vos articles."
custom_css_url: "URL CSS personnalisée"
custom_css_url_desc: "URL d'une feuille de style CSS qui remplacera les styles par défaut d'intégration des commentaires. Peut être interne ou externe."
dashboard: Tableau de bord
days: Journées
description: "En tant qu'administrateur, vous pouvez personnaliser les paramètres du fil de commentaires pour cet élément."
domain_list_text: "Entrez les domaines que vous souhaitez autoriser pour Talk, par exemple vos environnements locaux de production et de production (ex : localhost: 3000 staging.domain.com domain.com)."
domain_list_title: "Domaines autorisés"
edit_comment_timeframe_heading: "Modifier l'horodatage des commentaires"
edit_comment_timeframe_text_pre: "Les commentateurs auront"
edit_comment_timeframe_text_post: "secondes pour modifier leurs commentaires."
embed_comment_stream: "Intégrer le fil"
enable_premod_links_text: "Les modérateurs doivent approuver tout commentaire contenant un lien avant sa publication."
enable_pre_moderation: "Activer modération a priori"
enable_pre_moderation_text: "Les modérateurs doivent approuver tout commentaire avant qu'il ne soit publié."
enable_premod_links: "Modérer les commentaires contenant des liens"
enable_premod: "Activer la modération a priori"
enable_premod_description: "Les modérateurs doivent approuver tout commentaire avant sa publication."
enable_premod_links_description: "Les modérateurs doivent approuver tout commentaire contenant un lien avant sa publication."
enable_questionbox: "Posez une question aux lecteurs."
enable_questionbox_description: "Cette question apparaîtra en haut de ce fil de commentaires. Demandez aux lecteurs de s'exprimer sur un sujet évoqué dans l'article ou posez les termes de la discussion, etc."
hours: Heures
include_comment_stream: "Inclure la description du fil de commentaires pour les lecteurs"
include_comment_stream_desc: "Écrivez un message à ajouter au haut de votre flux de commentaires. Publiez un sujet de discussion, avec des suggestion sur la tenue des débats, etc."
include_text: "Entrez votre texte ici."
include_question_here: "Écrivez votre question ici."
moderate: Modérer
moderation_settings: "Paramètres de la modération"
open: "Ouvert"
open_stream: "Ouvrir le fil"
open_stream_configuration: "Ce fil de commentaires est actuellement ouvert. En fermant ce fil de commentaires, aucun nouveau commentaire ne pourra être envoyé, les commentaires précédents seront toujours affichés."
require_email_verification: "Exiger une vérification de l'e-mail"
require_email_verification_text: "Les nouveaux utilisateurs doivent vérifier leur adresse e-mail avant de commenter."
save_changes: "Sauvegarder les changements"
shortcuts: Raccourcis
sign_out: "Se Déconnecter"
stories: Histoires
stream_settings: "Paramètres du fil"
suspect_word_title: "Liste des mots suspects"
suspect_word_text: "Les commentaires contenant ces mots ou expressions (non sensibles à la casse) seront mis en évidence dans le flux de commentaires. Tapez un mot et appuyez sur Entrée ou Tabulation pour ajouter. En option, entrez une liste séparée par des virgules."
tech_settings: "Paramètres techniques"
title: "Configurer le fil de commentaires"
weeks: Semaines
wordlist: "Mots interdits"
continue: Continuer
dashboard:
auto_update: "Les données sont automatiquement mises à jour toutes les cinq minutes ou lorsque vous rechargez."
comment_count: commentaires
flags: signalements
most_flags: "Articles avec le plus de signalements"
most_conversations: "Articles avec le plus de conversations"
next_update: "{0} minutes jusqu'à la prochaine mise à jour."
no_activity: "Il n'y a eu aucun commentaire nulle part dans les cinq dernières minutes."
no_flags: "Il n'y a pas eu aucun signalement au cours des 5 dernières minutes! Hooray !"
no_likes: "Il n'y a pas eu de \"J'aime\" au cours des 5 dernières minutes. Tout est calme."
done: Terminé
edit_comment:
body_input_label: "Modifier ce commentaire"
save_button: "Sauvegarder les changements"
edit_window_expired: "Vous ne pouvez plus modifier ce commentaire. La fenêtre de temps pour le faire a expiré. Pourquoi ne pas en publier un autre ?"
edit_window_expired_close: "Fermer"
edit_window_timer_prefix: "Fenêtre d'édition :"
second: "seconde"
seconds_plural: "secondes"
unexpected_error: "Erreur inattendue lors de l'enregistrement des modifications. Désolé !"
embedlink:
copy: "Copier dans le presse-papier"
error:
COMMENT_TOO_SHORT: "Votre commentaire doit contenir quelque chose"
NOT_AUTHORIZED: "Vous n'êtes pas autorisé à effectuer cette action."
NO_SPECIAL_CHARACTERS: "Les noms d'utilisateur ne peuvent contenir que des lettres, des chiffres et \"_\" seulement"
PASSWORD_LENGTH: "Le mot de passe est trop court"
PROFANITY_ERROR: "Les noms d'utilisateur ne doivent pas contenir de mots offensants. Veuillez contacter l'administrateur si vous pensez qu'il y a une erreur."
USERNAME_IN_USE: "Ce nom d'utilisateur est déjà pris"
USERNAME_REQUIRED: "Doit entrer un nom d'utilisateur"
EDIT_WINDOW_ENDED: "Vous ne pouvez plus modifier ce commentaire. La fenêtre de temps pour le faire a expiré."
EDIT_USERNAME_NOT_AUTHORIZED: "Vous n'avez pas la permission de mettre à jour votre nom d'utilisateur."
EMAIL_IN_USE: "Adresse e-mail déjà utilisée"
EMAIL_REQUIRED: "Une adresse email est requise"
LOGIN_MAXIMUM_EXCEEDED: "Vous avez effectué trop de tentatives infructueuses pour entrer votre mot de passe. S'il vous plaît, attendez."
PASSWORD_REQUIRED: "Doit entrer un mot de passe"
COMMENTING_CLOSED: "Les commentaires sont déjà fermés"
NOT_FOUND: "Ressource introuvable"
INVALID_ASSET_URL: "L'URL est invalide"
email: "Pas une adresse e-mail valide"
confirm_password: "Les mots de passe ne correspondent pas. Vérifiez à nouveau"
network_error: "Échec de connexion au serveur. Vérifiez votre connexion Internet et réessayez."
email_not_verified: "L'adresse e-mail {0} n'est pas vérifiée."
email_password: "Combinaison e-mail / mot de passe incorrecte."
organization_name: "Le nom de l'organisation ne peut contenir que des lettres ou des chiffres."
password: "Le mot de passe doit être d'au moins 8 caractères"
username: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\""
flag_comment: "Signaler un commentaire"
flag_reason: "Motif du signalement (facultatif)"
flag_username: "Signaler un nom d'utilisateur"
framework:
banned_account_msg: "Votre compte est actuellement suspendu. Cela signifie que vous ne pouvez pas Aimer Signaler ou Écrire des commentaires. Contactez-nous si vous avez des questions."
comment: commentaire
comment_is_ignored: "Ce commentaire est caché car vous avez ignoré cet utilisateur."
comments: commentaires
configure_stream: "Configure le fil"
content_not_available: "Ce contenu n'est pas disponible"
edit_name:
button: Soumettre
error: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\""
label: "Nouveau nom d'utilisateur"
msg: "Votre compte est actuellement suspendu car votre nom d'utilisateur a été jugé inapproprié. Pour restaurer votre compte, entrez un nouveau nom d'utilisateur. Contactez-nous si vous avez des questions."
ignored_users: "Utilisateurs ignorés"
my_comments: "Mes commentaires"
my_profile: "Mon profil"
new_count: "Voir {0} nouveau {1}"
profile: Profil
show_all_comments: "Afficher tous les commentaires"
success_bio_update: "Votre biographie a été mise à jour"
success_name_update: "Votre nom d'utilisateur a été mis à jour"
success_update_settings: "Les modifications que vous avez apportées ont été appliqué es au fil de commentaires sur cet article"
view_all_replies: "Voir {0} réponses"
view_all_replies_initial: "Voir toutes les {0} réponses"
view_more_comments: "Voir plus de commentaires"
view_reply: "Voir la réponse"
from_settings_page: "Dans la page Profil, vous pouvez voir l'historique de vos commentaires."
like: "J'aime"
loading_results: "Chargement des résultats"
marketing: "Cela ressemble à du marketing / une publicité"
moderate_this_stream: "Modifiez ce fil"
modqueue:
account: "Signalements du compte"
actions: Actions
all: tous
all_streams: "Tous les fils"
approve: "Approuver"
approved: "Approuvé"
ban_user: "Bannir"
billion: B
close: Fermer
dont_like_username: "N'aime pas le nom d'utilisateur"
empty_queue: "Plus de commentaires à modérer ! Vous avez terminé, allez prendre un ☕️"
flagged: signalé
impersonating: "problème d'identité"
less_detail: "Moins de détails"
likes: "J'aime"
million: M
mod_faster: "Modérer plus rapidement avec les raccourcis clavier"
moderate: "Modérer →"
more_detail: "Plus de détails"
newest_first: "Le plus récent d'abord"
navigation: Navigation
next_comment: "Aller au prochain commentaire"
offensive: Offensant
oldest_first: "Le plus ancien d'abord"
other: Autre
premod: Pre-modérer
prev_comment: "Aller au commentaire précédent"
reject: "Rejeter"
rejected: "Rejeté"
select_stream: "Sélectionnez le fil"
shift_key:
shortcuts: Raccourcis
show_shortcuts: "Afficher les raccourcis"
singleview: "Passer en mode d'édition de commentaire unique"
spam_ads: Spam / Publicités
thismenu: "Ouvrir ce menu"
thousand: k
try_these: "Essayez ces"
view_more_shortcuts: "Afficher plus de raccourcis"
my_comment_history: "Mon historique de commentaires"
name: Nom
no_agree_comment: "Je ne suis pas d'accord avec ce commentaire"
no_like_bio: "Je n'aime pas cette biographie"
no_like_username: "Je n'aime pas ce nom d'utilisateur"
other: Autre
permalink: Lien
personal_info: "Ce commentaire révèle des informations personnelles identifiables"
post: Publier
profile: Profil
profile_settings: "Paramètres du profil"
reply: Répondre
report: Signaler
report_notif: "Merci de signaler ce commentaire. Notre équipe de modération a é té informée."
report_notif_remove: "Votre signalement a été supprimé."
reported: Signalé
set_best: "Sélectionner comme le meilleur"
settings:
all_comments: "Tous les commentaires"
from_settings_page: "Dans la page Profil, vous pouvez voir l'historique de vos commentaires."
my_comment_history: "Mon historique de commentaires"
profile: Profil
profile_settings: "Paramètres du profil"
sign_in: "Se connecter"
to_access: "Accéder au profil"
user_no_comment: "Vous n'avez jamais laissé de commentaire. Rejoignez la conversation!"
stream:
temporarily_suspended: "Conformément à la charte d'utilisation des commentaires de {0}, votre compte a été temporairement suspendu. Merci de revenir dans la conversation {1}."
step_1_header: "Signaler un problème"
step_2_header: "Aidez-nous à comprendre"
step_3_header: "Merci pour votre participation"
streams:
all: Tous
article: Récit
closed: Fermé
empty_result: "Aucun contenu ne correspond à cette recherche. Peut-être essayer d'élargir votre recherche ?"
filter_streams: "Filtrer les flux"
newest: Le plus récent
oldest: Le plus ancien
open: Ouvrir
pubdate: "Date de publication"
search: Recherche
sort_by: "Trier par"
status: "Statut du fil"
stream_status: "Statut du fil"
suspenduser:
bio: Bio
cancel: "Annuler"
days: "{0} jours"
description_0: "Voulez-vous interdire temporairement cet utilisateur en raison de leur {0} ? Cela dissimulera temporairement leurs commentaires jusqu'à ce qu'ils réécrivent leurs {0}."
description_1: "Suspendre cet utilisateur désactivera temporairement son compte et cachera tous ses commentaires sur le site."
description_notify: "Suspendre cet utilisateur désactivera temporairement son compte et cachera tous ses commentaires sur le site."
description_reject: "Voulez-vous interdire temporairement cet utilisateur en raison de leur {0}? Cela dissimulera temporairement leurs commentaires jusqu'à ce qu'ils réécrivent leurs {0}."
description_suspend: "Vous suspendez {0}. Ce commentaire sera rejeté, et {0} ne sera pas autorisé à aimer, à signaler, à répondre ou à publier jusqu'à ce que la suspension soit levée."
email: "Un autre membre de la communauté a récemment signalé votre nom d'utilisateur pour examen. En raison de son contenu, votre utilisateur a été rejeté. Cela signifie que vous ne pouvez plus commenter ou signaler du contenu jusqu'à ce que vous changiez votre nom d'utilisateur. Veuillez nous envoyer un e-mail si vous avez des questions ou des préoccupations."
email_subject: "Votre compte a été suspendu"
email_message_reject: "Un autre membre de la communauté a récemment signalé votre nom d'utilisateur pour examen. En raison de son contenu, votre nom d'utilisateur a été rejeté. Cela signifie que vous ne pouvez plus commenter, ni signaler du contenu jusqu'à ce que vous changiez votre nom d'utilisateur. Veuillez nous envoyer un e-mail si vous avez des questions ou des préoccupations."
email_message_suspend: "Cher {0},\n\nConformément à la charte des commentaires de {1}, votre compte a été temporairement suspendu. Pendant cette période, vous ne pourrez pas commenter, signaler ou participer à d'autres commentaires. \n\nMerci de revenir dans la conversation {2}."
error_email_message_empty: "Vous devez spécifier un message dans l'e-mail."
hours: "{0} heures"
no_cancel: "Pas d'annulation"
notify_suspend_until: "L'utilisateur {0} a été temporairement suspendu. Cette suspension se terminera automatiquement {1}."
one_hour: "1 heure"
send: Envoyer
select_duration: "Sélectionnez la durée de la suspension"
suspend_user: "Suspendre l'utilisateur"
title: "Suspendre un utilisateur"
title_0: "Nous avons remarqué que vous avez rejeté un nom d'utilisateur"
title_1: "Aviser l'utilisateur de sa suspension temporaire"
title_notify: "Aviser l'utilisateur de sa suspension temporaire"
title_reject: "Nous avons remarqué que vous avez rejeté un nom d'utilisateur"
title_suspend: "Suspendre l'utilisateur"
username: "Nom d'utilisateur"
write_message: "Écrire un message"
yes_suspend: "Oui suspendre"
thank_you: "Nous apprécions vos commentaires. Un modérateur passera en revue votre signalement. Merci pour votre aide."
unset_best: "Déselectionner comme le meilleur"
user:
bio_flags: "Signaler pour cette biographie"
user_bio: "Bio de l'utilisateur"
username_flags: "Signaler pour ce nom d'utilisateur"
user_impersonating: "Cet utilisateur se fait passer pour quelqu'un d'autre"
user_no_comment: "Vous n'avez jamais laissé de commentaire. Rejoignez la conversation !"
username_offensive: "Ce nom d'utilisateur est offensant"
view_conversation: "Afficher la conversation"
install:
initial:
description: "Configurez votre communauté Talk en quelques petites étapes."
submit: "Commencer"
add_organization:
description: "Veuillez nous indiquer le nom de votre organisation. Cela apparaîtra dans les e-mails lors de l'invitation de nouveaux membres dans l'équipe."
label: "Nom de l'organisation"
save: "Sauvegarder"
create:
email: "Adresse e-mail"
username: "Nom d'utilisateur"
password: "Mot de passe"
confirm_password: "Confirmez Le mot de passe"
save: "Sauvegarder"
permitted_domains:
title: "Domaines autorisés"
description: "Entrez les domaines que vous souhaitez autoriser pour Talk, par ex. vos environnements locaux, de déploiement et de production (ex localhost:3000, staging.domain.com, domain.com)."
submit: "Terminer l'installation"
final:
description: "Merci d'avoir installé Talk ! Nous avons envoyé un e-mail pour vérifier votre adresse électronique. Pendant que vous terminez la configuration, vous pouvez commencer à engager vos lecteurs dès maintenant."
launch: "Lancer Talk"
close: "Fermez cet installateur"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "talk",
"version": "2.2.0",
"version": "2.3.0",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
"scripts": {
@@ -89,3 +89,47 @@ es:
username: Nombre
write_your_username: "Edita tu nombre"
your_username: "Tu nombre aparece en cada comentario que publiques."
fr:
sign_in:
email_verify_cta: "Merci de vérifier votre adresse e-mail."
request_new_verify_email: "Demander un nouvel envoi d'e-mail :"
verify_email: "Merci d'avoir créé un compte ! Nous avons envoyé un courrier électronique à l'adresse que vous avez fournie pour vérifier votre adresse e-mail."
verify_email2: "Vous devez vérifier votre adresse e-mail avant de vous engager auprès de la communauté."
not_you: "Pas vous ?"
logged_in_as: "Connecté en tant que"
facebook_sign_in: "Connectez-vous avec Facebook"
facebook_sign_up: "Inscrivez-vous avec Facebook"
logout: "Se déconnecter"
sign_in: "Se connecter"
sign_in_to_join: "Connectez-vous pour participer à la conversation"
or: "Ou"
email: "Adresse e-mail"
password: "Mot de passe"
forgot_your_pass: "Mot de passe oublié ?"
need_an_account: "Besoin d'un compte ?"
register: "Inscription"
sign_up: "S'inscrire"
confirm_password: "Confirmez Le mot de passe"
username: "Nom d'utilisateur"
already_have_an_account: "Vous avez déjà un compte ?"
recover_password: "Récupérer le mot de passe"
email_in_use: "L'adresse e-mail est déjà utilisée"
email_or_username_in_use: "Adresse e-mail ou nom d'utilisateur déjà utilisé"
required_field: "Ce champ est obligatoire"
passwords_dont_match: "Les mots de passe ne correspondent pas."
special_characters: "Les noms d'utilisateur ne peuvent contenir que des lettres, des chiffres et \"_\" seulement"
sign_in_to_comment: "Connectez-vous pour commenter"
check_the_form: "Formulaire non valide. Veuillez vérifier les champs"
createdisplay:
check_the_form: "Formulaire non valide. Veuillez vérifier les champs"
continue: "Continuez avec le même nom d'utilisateur que sur Facebook"
error_create: "Erreur lors de la modification du nom d'utilisateur"
fake_comment_body: "Ceci est un exemple de commentaire. Les lecteurs peuvent partager leurs pensées et opinions avec les rédactions dans la section des commentaires."
fake_comment_date: "Il y a 1 minute"
if_you_dont_change_your_name: "Si vous ne modifiez pas votre nom d'utilisateur à cette étape, votre nom d'affichage Facebook apparaîtra à côté de tous vos commentaires."
required_field: "champs requis"
save: Sauvegarder
special_characters: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\""
username: "Nom d'utilisateur"
write_your_username: "Modifier votre nom d'utilisateur"
your_username: "Votre nom d'utilisateur apparaît sur chaque commentaire que vous publiez."
@@ -45,7 +45,9 @@ class LikeButton extends React.Component {
className={cn(styles.button, {[styles.liked]: alreadyReacted}, `${plugin}-button`)}
onClick={this.handleClick}
>
<span>{t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')}</span>
<span className={cn(`${plugin}-label`, styles.label)}>
{t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')}
</span>
<Icon name="thumb_up" className={`${plugin}-icon`} />
<span className={`${plugin}-count`}>{count > 0 && count}</span>
</button>
@@ -23,3 +23,9 @@
}
}
}
@media (max-width: 425px) {
.label {
display: none;
}
}
@@ -45,7 +45,9 @@ class LoveButton extends React.Component {
className={cn(styles.button, {[styles.loved]: alreadyReacted}, `${plugin}-button`)}
onClick={this.handleClick}
>
<span>{t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')}</span>
<span className={cn(`${plugin}-label`, styles.label)}>
{t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')}
</span>
<Icon name="favorite" className={`${plugin}-icon`} />
<span className={`${plugin}-count`}>{count > 0 && count}</span>
</button>
@@ -23,3 +23,9 @@
}
}
}
@media (max-width: 425px) {
.label {
display: none;
}
}
@@ -45,11 +45,11 @@ class RespectButton extends React.Component {
className={cn(styles.button, {[styles.respected]: alreadyReacted}, `${plugin}-button`)}
onClick={this.handleClick}
>
<span className={`${plugin}-button-text`}>
<span className={cn(`${plugin}-label`, styles.label)}>
{t(alreadyReacted ? 'coral-plugin-respect.respected' : 'coral-plugin-respect.respect')}
</span>
<Icon className={cn(styles.icon, `${plugin}-icon`)} />
<span className={cn(styles.icon, `${plugin}-count`)}>{count > 0 && count}</span>
<span className={cn(`${plugin}-count`)}>{count > 0 && count}</span>
</button>
</div>
);
@@ -28,3 +28,9 @@
.icon {
padding: 0 5px;
}
@media (max-width: 425px) {
.label {
display: none;
}
}
@@ -66,11 +66,11 @@ export default class PermalinkButton extends React.Component {
return (
<ClickOutside onClickOutside={this.handleClickOutside}>
<div className={cn(`${name}-container`, styles.container)}>
<button
ref={(ref) => this.linkButton = ref}
onClick={this.toggle}
className={`${name}-button`}>
className={cn(`${name}-button`, styles.button)}>
{t('permalink')}
<Icon name="link" />
</button>
@@ -89,7 +89,7 @@ export default class PermalinkButton extends React.Component {
<Button
onClick={this.copyPermalink}
className={cn([
styles.button,
styles.copyButton,
`${name}-copy-button`, {
[styles.success]:copySuccessful,
[styles.failure]: copyFailure
@@ -98,7 +98,7 @@ export default class PermalinkButton extends React.Component {
{copySuccessful && 'Copied'}
{copyFailure && 'Not supported'}
</Button>
</div>
</div>
</ClickOutside>
@@ -42,7 +42,13 @@
}
.button {
margin: 5px 0px 5px 10px;
cursor: pointer;
}
.copyButton {
display: inline-block;
margin: 5px 10px 5px 0px;
float: right;
box-sizing: border-box;
margin: 0;
@@ -52,18 +58,19 @@
height: auto;
padding: 2px;
transition: background-color 0.4s ease;
cursor: pointer;
}
.button:hover{
.copyButton:hover{
color: black;
}
.button.success {
.copyButton.success {
background-color: #00897B;
color: white;
}
.button.failure {
.copyButton.failure {
background-color: #FF5252;
color: white;
}
@@ -74,4 +81,4 @@
.active {
display: block;
}
}
+4 -2
View File
@@ -2,17 +2,19 @@ const has = require('lodash/has');
const get = require('lodash/get');
const yaml = require('yamljs');
const es = yaml.load('./locales/es.yml');
const en = yaml.load('./locales/en.yml');
const fr = yaml.load('./locales/fr.yml');
const accepts = require('accepts');
// default language
let defaultLanguage = 'en';
let language = defaultLanguage;
const languages = ['en', 'es'];
const languages = ['en', 'es', 'fr'];
const translations = Object.assign(en, es);
const translations = Object.assign(en, es, fr);
/**
* Exposes a service object to allow translations.
+15
View File
@@ -0,0 +1,15 @@
const {createClient} = require('../../services/redis');
// Create a redis client to use for clearing the database.
const client = createClient();
module.exports.clearDB = () =>
new Promise((resolve, reject) =>
client.flushdb((err) => {
if (err) {
return reject(err);
}
return resolve();
})
);
+3
View File
@@ -0,0 +1,3 @@
const redis = require('../helpers/redis');
beforeEach(() => redis.clearDB());
+2 -1
View File
@@ -112,7 +112,8 @@ const config = {
}
}),
new webpack.EnvironmentPlugin({
'TALK_PLUGINS_JSON': '{}'
'TALK_PLUGINS_JSON': '{}',
'TALK_THREADING_LEVEL': '3'
})
],
resolveLoader: {