mirror of
https://github.com/wassname/talk.git
synced 2026-07-20 12:40:47 +08:00
Merge branch 'master' into extendable-tab-menu
Conflicts: client/coral-embed-stream/src/components/Comment.js
This commit is contained in:
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
Executable
+59
@@ -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});
|
||||
|
||||
@@ -400,7 +400,7 @@ export default class Comment extends React.Component {
|
||||
return false;
|
||||
});
|
||||
|
||||
const rootClassNames = [
|
||||
const rootClassName = cn(
|
||||
'talk-stream-comment-wrapper',
|
||||
`talk-stream-comment-wrapper-level-${depth}`,
|
||||
styles.root,
|
||||
@@ -409,27 +409,27 @@ export default class Comment extends React.Component {
|
||||
...conditionalClassNames,
|
||||
[styles.enter]: this.state.animateEnter,
|
||||
},
|
||||
];
|
||||
);
|
||||
|
||||
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(...rootClassNames)}
|
||||
className={rootClassName}
|
||||
id={`c_${comment.id}`}
|
||||
>
|
||||
<div
|
||||
className={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,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className={commentClassName}>
|
||||
<AuthorName author={comment.user} className={'talk-stream-comment-user-name'} />
|
||||
{isStaff(comment.tags) ? <TagLabel>Staff</TagLabel> : null}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
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, setActiveTab, viewAllComments} from '../actions/stream';
|
||||
import Stream from '../components/Stream';
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
insertCommentIntoEmbedQuery,
|
||||
removeCommentFromEmbedQuery,
|
||||
insertFetchedCommentsIntoEmbedQuery,
|
||||
nest,
|
||||
} from '../graphql/utils';
|
||||
|
||||
const {showSignInDialog} = authActions;
|
||||
@@ -152,18 +154,6 @@ class StreamContainer extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const nest = (def, level) => {
|
||||
let result = '';
|
||||
for (let x = 0; x < level; x++) {
|
||||
if (x === 0) {
|
||||
result += def;
|
||||
continue;
|
||||
}
|
||||
result = result.replace('...nest', def);
|
||||
}
|
||||
return result.replace('...nest', '');
|
||||
};
|
||||
|
||||
const commentFragment = gql`
|
||||
fragment CoralEmbedStream_Stream_comment on Comment {
|
||||
id
|
||||
|
||||
@@ -4,13 +4,11 @@ import {THREADING_LEVEL} from '../constants/stream';
|
||||
function applyToCommentsOrigin(root, callback) {
|
||||
if (root.comment) {
|
||||
let comment = root.comment;
|
||||
console.log(comment);
|
||||
for (let depth = 0; depth <= THREADING_LEVEL; depth++) {
|
||||
let changes = {$apply: (node) => node ? callback(node) : node};
|
||||
for (let i = 0; i < depth; i++) {
|
||||
changes = {parent: changes};
|
||||
}
|
||||
console.log(changes);
|
||||
comment = update(comment, changes);
|
||||
}
|
||||
|
||||
@@ -183,3 +181,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', '');
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ 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';
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
+1
-1
@@ -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,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';
|
||||
|
||||
@@ -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`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talk",
|
||||
"version": "2.2.1",
|
||||
"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": {
|
||||
|
||||
@@ -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();
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
const redis = require('../helpers/redis');
|
||||
|
||||
beforeEach(() => redis.clearDB());
|
||||
Reference in New Issue
Block a user