mirror of
https://github.com/wassname/talk.git
synced 2026-07-10 00:35:56 +08:00
Merge branch 'master' into gdpr-download
This commit is contained in:
+19
-13
@@ -13,6 +13,7 @@ const CommentModel = require('../models/comment');
|
||||
const AssetsService = require('../services/assets');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const scraper = require('../services/scraper');
|
||||
const Context = require('../graph/context');
|
||||
const inquirer = require('inquirer');
|
||||
const { URL } = require('url');
|
||||
|
||||
@@ -52,22 +53,27 @@ async function refreshAssets(ageString) {
|
||||
const ageMs = parseDuration(ageString);
|
||||
const age = new Date(now - ageMs);
|
||||
|
||||
let assets = await AssetModel.find({
|
||||
$or: [
|
||||
{
|
||||
scraped: {
|
||||
$lte: age,
|
||||
let assets = await AssetModel.find(
|
||||
{
|
||||
$or: [
|
||||
{
|
||||
scraped: {
|
||||
$lte: age,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
scraped: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
{
|
||||
scraped: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ id: 1 }
|
||||
);
|
||||
|
||||
// Create a graph context.
|
||||
const ctx = Context.forSystem();
|
||||
|
||||
// Queue all the assets for scraping.
|
||||
await Promise.all(assets.map(scraper.create));
|
||||
|
||||
await Promise.all(assets.map(({ id }) => scraper.create(ctx, id)));
|
||||
console.log('Assets were queued to be scraped');
|
||||
util.shutdown();
|
||||
} catch (e) {
|
||||
|
||||
@@ -2,10 +2,15 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Router, Route, IndexRedirect, IndexRoute } from 'react-router';
|
||||
|
||||
import Configure from 'routes/Configure';
|
||||
import Install from 'routes/Install';
|
||||
import Stories from 'routes/Stories';
|
||||
import Community from 'routes/Community';
|
||||
|
||||
import Configure from 'routes/Configure';
|
||||
import StreamSettings from './routes/Configure/containers/StreamSettings';
|
||||
import ModerationSettings from './routes/Configure/containers/ModerationSettings';
|
||||
import TechSettings from './routes/Configure/containers/TechSettings';
|
||||
|
||||
import { ModerationLayout, Moderation } from 'routes/Moderation';
|
||||
|
||||
import Layout from 'containers/Layout';
|
||||
@@ -15,7 +20,14 @@ const routes = (
|
||||
<Route exact path="/admin/install" component={Install} />
|
||||
<Route path="/admin" component={Layout}>
|
||||
<IndexRedirect to="/admin/moderate" />
|
||||
<Route path="configure" component={Configure} />
|
||||
|
||||
<Route path="configure" component={Configure}>
|
||||
<Route path="stream" component={StreamSettings} />
|
||||
<Route path="moderation" component={ModerationSettings} />
|
||||
<Route path="tech" component={TechSettings} />
|
||||
<IndexRedirect to="stream" />
|
||||
</Route>
|
||||
|
||||
<Route path="stories" component={Stories} />
|
||||
|
||||
{/* Community Routes */}
|
||||
|
||||
@@ -8,6 +8,10 @@ export const clearPending = () => {
|
||||
return { type: actions.CLEAR_PENDING };
|
||||
};
|
||||
|
||||
export const setActiveSection = section => {
|
||||
return { type: actions.SET_ACTIVE_SECTION, section };
|
||||
export const showSaveDialog = () => {
|
||||
return { type: actions.SHOW_SAVE_DIALOG };
|
||||
};
|
||||
|
||||
export const hideSaveDialog = () => {
|
||||
return { type: actions.HIDE_SAVE_DIALOG };
|
||||
};
|
||||
|
||||
@@ -2,4 +2,6 @@ const prefix = 'TALK_ADMIN_CONFIGURE';
|
||||
|
||||
export const UPDATE_PENDING = `${prefix}_UPDATE_PENDING`;
|
||||
export const CLEAR_PENDING = `${prefix}_CLEAR_PENDING`;
|
||||
export const SET_ACTIVE_SECTION = `${prefix}_SET_ACTIVE_SECTION`;
|
||||
|
||||
export const SHOW_SAVE_DIALOG = `${prefix}_SHOW_SAVE_DIALOG`;
|
||||
export const HIDE_SAVE_DIALOG = `${prefix}_HIDE_SAVE_DIALOG`;
|
||||
|
||||
@@ -6,11 +6,23 @@ const initialState = {
|
||||
canSave: false,
|
||||
pending: {},
|
||||
errors: {},
|
||||
activeSection: 'stream',
|
||||
saveDialog: false,
|
||||
};
|
||||
|
||||
export default function configure(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actions.SHOW_SAVE_DIALOG: {
|
||||
return {
|
||||
...state,
|
||||
saveDialog: true,
|
||||
};
|
||||
}
|
||||
case actions.HIDE_SAVE_DIALOG: {
|
||||
return {
|
||||
...state,
|
||||
saveDialog: false,
|
||||
};
|
||||
}
|
||||
case actions.UPDATE_PENDING: {
|
||||
let next = state;
|
||||
if (action.updater) {
|
||||
@@ -40,11 +52,8 @@ export default function configure(state = initialState, action) {
|
||||
pending: {},
|
||||
canSave: false,
|
||||
};
|
||||
case actions.SET_ACTIVE_SECTION:
|
||||
return {
|
||||
...state,
|
||||
activeSection: action.section,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,50 +1,37 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Button, List, Item } from 'coral-ui';
|
||||
import styles from './Configure.css';
|
||||
import StreamSettings from '../containers/StreamSettings';
|
||||
import ModerationSettings from '../containers/ModerationSettings';
|
||||
import TechSettings from '../containers/TechSettings';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { can } from 'coral-framework/services/perms';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { Button, List, Item } from 'coral-ui';
|
||||
import { can } from 'coral-framework/services/perms';
|
||||
import styles from './Configure.css';
|
||||
import SaveChangesDialog from './SaveChangesDialog';
|
||||
|
||||
export default class Configure extends Component {
|
||||
getSectionComponent(section) {
|
||||
switch (section) {
|
||||
case 'stream':
|
||||
return StreamSettings;
|
||||
case 'moderation':
|
||||
return ModerationSettings;
|
||||
case 'tech':
|
||||
return TechSettings;
|
||||
}
|
||||
throw new Error(`Unknown section ${section}`);
|
||||
}
|
||||
|
||||
class Configure extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
currentUser,
|
||||
canSave,
|
||||
savePending,
|
||||
setActiveSection,
|
||||
activeSection,
|
||||
} = this.props;
|
||||
const SectionComponent = this.getSectionComponent(activeSection);
|
||||
const { canSave, currentUser, root, savePending, settings } = this.props;
|
||||
|
||||
if (!can(currentUser, 'UPDATE_CONFIG')) {
|
||||
return (
|
||||
<p>
|
||||
You must be an administrator to access config settings. Please find
|
||||
the nearest Admin and ask them to level you up!
|
||||
</p>
|
||||
);
|
||||
return <p>{t('configure.access_message')}</p>;
|
||||
}
|
||||
|
||||
const passProps = {
|
||||
root,
|
||||
settings,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<SaveChangesDialog
|
||||
saveDialog={this.props.saveDialog}
|
||||
hideSaveDialog={this.props.hideSaveDialog}
|
||||
saveChanges={this.props.saveChanges}
|
||||
discardChanges={this.props.discardChanges}
|
||||
/>
|
||||
<div className={styles.leftColumn}>
|
||||
<List onChange={setActiveSection} activeItem={activeSection}>
|
||||
<List
|
||||
onChange={this.props.handleSectionChange}
|
||||
activeItem={this.props.activeSection}
|
||||
>
|
||||
<Item itemId="stream" icon="speaker_notes">
|
||||
{t('configure.stream_settings')}
|
||||
</Item>
|
||||
@@ -74,10 +61,7 @@ export default class Configure extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
<SectionComponent
|
||||
root={this.props.root}
|
||||
settings={this.props.settings}
|
||||
/>
|
||||
{React.cloneElement(this.props.children, passProps)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -86,10 +70,17 @@ export default class Configure extends Component {
|
||||
|
||||
Configure.propTypes = {
|
||||
savePending: PropTypes.func.isRequired,
|
||||
saveChanges: PropTypes.func.isRequired,
|
||||
discardChanges: PropTypes.func.isRequired,
|
||||
currentUser: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
canSave: PropTypes.bool.isRequired,
|
||||
setActiveSection: PropTypes.func.isRequired,
|
||||
handleSectionChange: PropTypes.func.isRequired,
|
||||
activeSection: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
saveDialog: PropTypes.bool,
|
||||
hideSaveDialog: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Configure;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
.buttonActions {
|
||||
padding-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
padding: 25px;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.close {
|
||||
font-size: 20px;
|
||||
line-height: 14px;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
position: absolute;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #363636;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
color: #363636;
|
||||
margin-right: 15px;
|
||||
display: inline-block;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { Button, Dialog } from 'coral-ui';
|
||||
import styles from './SaveChangesDialog.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const SaveChangesDialog = ({
|
||||
saveDialog,
|
||||
hideSaveDialog,
|
||||
saveChanges,
|
||||
discardChanges,
|
||||
}) => (
|
||||
<Dialog
|
||||
className={cn(styles.dialog, 'talk-admin-configure-save-dialog')}
|
||||
id="saveDialog"
|
||||
open={saveDialog}
|
||||
onCancel={hideSaveDialog}
|
||||
>
|
||||
<span className={styles.close} onClick={hideSaveDialog}>
|
||||
×
|
||||
</span>
|
||||
<div className={styles.title}>
|
||||
{t('configure.save_changes_dialog.unsaved_changes')}
|
||||
</div>
|
||||
{t('configure.save_changes_dialog.copy')}
|
||||
<div
|
||||
className={cn(
|
||||
styles.buttonActions,
|
||||
'talk-admin-configure-save-dialog-button-actions'
|
||||
)}
|
||||
>
|
||||
<a className={styles.cancel} onClick={hideSaveDialog}>
|
||||
Cancel
|
||||
</a>
|
||||
<Button onClick={discardChanges} className={styles.button}>
|
||||
{t('configure.save_changes_dialog.discard')}
|
||||
</Button>
|
||||
<Button onClick={saveChanges} cStyle="green" className={styles.button}>
|
||||
{t('configure.save_changes_dialog.save_settings')}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
SaveChangesDialog.propTypes = {
|
||||
saveDialog: PropTypes.bool.isRequired,
|
||||
hideSaveDialog: PropTypes.func.isRequired,
|
||||
saveChanges: PropTypes.func.isRequired,
|
||||
discardChanges: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SaveChangesDialog;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
@@ -10,15 +10,70 @@ import { getDefinitionName } from 'coral-framework/utils';
|
||||
import StreamSettings from './StreamSettings';
|
||||
import TechSettings from './TechSettings';
|
||||
import ModerationSettings from './ModerationSettings';
|
||||
import { clearPending, setActiveSection } from '../../../actions/configure';
|
||||
import {
|
||||
clearPending,
|
||||
showSaveDialog,
|
||||
hideSaveDialog,
|
||||
} from '../../../actions/configure';
|
||||
import Configure from '../components/Configure';
|
||||
import { withRouter } from 'react-router';
|
||||
|
||||
class ConfigureContainer extends React.Component {
|
||||
state = { nextRoute: '' };
|
||||
|
||||
class ConfigureContainer extends Component {
|
||||
savePending = async () => {
|
||||
await this.props.updateSettings(this.props.pending);
|
||||
this.props.clearPending();
|
||||
};
|
||||
|
||||
saveChanges = async () => {
|
||||
await this.savePending();
|
||||
this.props.hideSaveDialog();
|
||||
this.gotoNextRoute();
|
||||
};
|
||||
|
||||
discardChanges = async () => {
|
||||
await this.props.clearPending();
|
||||
this.props.hideSaveDialog();
|
||||
this.gotoNextRoute();
|
||||
};
|
||||
|
||||
gotoNextRoute = () => {
|
||||
const { nextRoute } = this.state;
|
||||
if (nextRoute) {
|
||||
this.props.router.push(nextRoute);
|
||||
this.setState({ nextRoute: '' });
|
||||
}
|
||||
};
|
||||
|
||||
handleSectionChange = async section => {
|
||||
const nextRoute = `/admin/configure/${section}`;
|
||||
|
||||
if (this.shouldShowSaveDialog()) {
|
||||
await this.setState({ nextRoute });
|
||||
this.props.showSaveDialog();
|
||||
} else {
|
||||
// Just go to the section
|
||||
this.props.router.push(nextRoute);
|
||||
}
|
||||
};
|
||||
|
||||
shouldShowSaveDialog = () => {
|
||||
return !!Object.keys(this.props.pending).length;
|
||||
};
|
||||
|
||||
routeLeave = ({ pathname }) => {
|
||||
if (this.shouldShowSaveDialog()) {
|
||||
this.setState({ nextRoute: pathname });
|
||||
this.props.showSaveDialog();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.router.setRouteLeaveHook(this.props.route, this.routeLeave);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.data.error) {
|
||||
return <div>{this.props.data.error.message}</div>;
|
||||
@@ -30,14 +85,20 @@ class ConfigureContainer extends Component {
|
||||
|
||||
return (
|
||||
<Configure
|
||||
saveChanges={this.saveChanges}
|
||||
discardChanges={this.discardChanges}
|
||||
saveDialog={this.props.saveDialog}
|
||||
activeSection={this.props.routes[3].path}
|
||||
hideSaveDialog={this.props.hideSaveDialog}
|
||||
canSave={this.props.canSave}
|
||||
currentUser={this.props.currentUser}
|
||||
root={this.props.root}
|
||||
settings={this.props.mergedSettings}
|
||||
canSave={this.props.canSave}
|
||||
handleSectionChange={this.handleSectionChange}
|
||||
savePending={this.savePending}
|
||||
setActiveSection={this.props.setActiveSection}
|
||||
activeSection={this.props.activeSection}
|
||||
/>
|
||||
>
|
||||
{this.props.children}
|
||||
</Configure>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -74,18 +135,21 @@ const mapStateToProps = state => ({
|
||||
pending: state.configure.pending,
|
||||
canSave: state.configure.canSave,
|
||||
activeSection: state.configure.activeSection,
|
||||
saveDialog: state.configure.saveDialog,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
clearPending,
|
||||
setActiveSection,
|
||||
showSaveDialog,
|
||||
hideSaveDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withUpdateSettings,
|
||||
withConfigureQuery,
|
||||
@@ -93,14 +157,20 @@ export default compose(
|
||||
)(ConfigureContainer);
|
||||
|
||||
ConfigureContainer.propTypes = {
|
||||
activeSection: PropTypes.string,
|
||||
updateSettings: PropTypes.func.isRequired,
|
||||
clearPending: PropTypes.func.isRequired,
|
||||
setActiveSection: PropTypes.func.isRequired,
|
||||
showSaveDialog: PropTypes.func.isRequired,
|
||||
hideSaveDialog: PropTypes.func.isRequired,
|
||||
saveDialog: PropTypes.bool.isRequired,
|
||||
currentUser: PropTypes.object.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
root: PropTypes.object.isRequired,
|
||||
canSave: PropTypes.bool.isRequired,
|
||||
pending: PropTypes.object.isRequired,
|
||||
mergedSettings: PropTypes.object.isRequired,
|
||||
activeSection: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
router: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
routes: PropTypes.array,
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ class Comment extends React.Component {
|
||||
defaultComponent={CommentContent}
|
||||
className={cn(styles.commentBody, 'my-comment-body')}
|
||||
passthrough={slotPassthrough}
|
||||
size={1}
|
||||
/>
|
||||
<div className={cn(styles.commentSummary, 'comment-summary')}>
|
||||
<span
|
||||
|
||||
@@ -57,6 +57,10 @@ export const setAuthToken = token => (dispatch, _, { localStorage }) => {
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
|
||||
// Dispatch the set auth token action. For some browsers and situations, we
|
||||
// may not be able to persist the auth token any other way. Keep it in redux!
|
||||
dispatch({ type: actions.SET_AUTH_TOKEN, token });
|
||||
|
||||
dispatch(checkLogin());
|
||||
};
|
||||
|
||||
@@ -87,6 +91,7 @@ export const handleSuccessfulLogin = (user, token) => (
|
||||
dispatch({
|
||||
type: actions.HANDLE_SUCCESSFUL_LOGIN,
|
||||
user,
|
||||
token,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ class IfSlotIsNotEmpty extends React.Component {
|
||||
isSlotEmpty(props = this.props) {
|
||||
const { slotElements } = props;
|
||||
return slotElements.length === 0
|
||||
? false
|
||||
? true
|
||||
: slotElements.every(elements => elements.length === 0);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class IfSlotIsNotEmpty extends React.Component {
|
||||
|
||||
IfSlotIsNotEmpty.propTypes = {
|
||||
slot: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
slotElements: PropTypes.array.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
passthrough: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const prefix = `TALK_FRAMEWORK`;
|
||||
|
||||
export const SET_AUTH_TOKEN = `${prefix}_SET_AUTH_TOKEN`;
|
||||
|
||||
export const CHECK_LOGIN_REQUEST = `${prefix}_CHECK_LOGIN_REQUEST`;
|
||||
export const CHECK_LOGIN_SUCCESS = `${prefix}_CHECK_LOGIN_SUCCESS`;
|
||||
export const CHECK_LOGIN_FAILURE = `${prefix}_CHECK_LOGIN_FAILURE`;
|
||||
|
||||
@@ -5,6 +5,7 @@ const initialState = {
|
||||
checkedInitialLogin: false,
|
||||
initialLoginError: null,
|
||||
user: null,
|
||||
token: null,
|
||||
};
|
||||
|
||||
const purge = user => {
|
||||
@@ -14,12 +15,18 @@ const purge = user => {
|
||||
|
||||
export default function auth(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actions.SET_AUTH_TOKEN:
|
||||
return {
|
||||
...state,
|
||||
token: action.token || null,
|
||||
};
|
||||
case actions.CHECK_LOGIN_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
initialLoginError: action.error,
|
||||
checkedInitialLogin: true,
|
||||
user: null,
|
||||
token: null,
|
||||
};
|
||||
case actions.CHECK_LOGIN_SUCCESS:
|
||||
return {
|
||||
@@ -31,11 +38,13 @@ export default function auth(state = initialState, action) {
|
||||
return {
|
||||
...state,
|
||||
user: action.user ? purge(action.user) : null,
|
||||
token: action.token || null,
|
||||
};
|
||||
case actions.LOGOUT:
|
||||
return {
|
||||
...state,
|
||||
user: null,
|
||||
token: null,
|
||||
};
|
||||
case actions.UPDATE_STATUS: {
|
||||
return {
|
||||
|
||||
+7
-1
@@ -47,6 +47,12 @@ const getAuthToken = (store, storage) => {
|
||||
} else if (!bowser.safari && !bowser.ios && storage) {
|
||||
// Use local storage auth tokens where there's a stable api.
|
||||
return storage.getItem('token');
|
||||
} else if (state.auth && state.auth.token) {
|
||||
// Use the redux token state if the remaining methods fall out. If the embed
|
||||
// is called with `embed.login(token)`, and the browser is not capable of
|
||||
// storing the token in localStorage, then we would have persisted it to the
|
||||
// redux state.
|
||||
return state.auth.token;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -123,7 +129,7 @@ export async function createContext({
|
||||
// Try to get the token from localStorage. If it isn't here, it may
|
||||
// be passed as a cookie.
|
||||
|
||||
// NOTE: THIS IS ONLY EVER EVALUATED ONCE, IN ORDER TO SEND A DIFFERNT
|
||||
// NOTE: THIS IS ONLY EVER EVALUATED ONCE, IN ORDER TO SEND A DIFFERENT
|
||||
// TOKEN YOU MUST DISCONNECT AND RECONNECT THE WEBSOCKET CLIENT.
|
||||
return getAuthToken(store, localStorage);
|
||||
};
|
||||
|
||||
@@ -167,14 +167,10 @@ TALK_REDIS_URL=redis://127.0.0.1:6379
|
||||
TALK_ROOT_URL=http://127.0.0.1:3000
|
||||
TALK_PORT=3000
|
||||
TALK_JWT_SECRET=password
|
||||
TALK_FACEBOOK_APP_ID=A-Facebook-App-ID
|
||||
TALK_FACEBOOK_APP_SECRET=A-Facebook-App-Secret
|
||||
```
|
||||
|
||||
This is only the bare minimum needed to run the demo, for more configuration
|
||||
variables, check out the [Configuration](/talk/configuration/) section. Facebook login above
|
||||
will definitely not work unless you change those values as well.
|
||||
|
||||
variables, check out the [Configuration](/talk/configuration/) section.
|
||||
|
||||
You can now start the application by running:
|
||||
|
||||
|
||||
@@ -57,14 +57,11 @@ TALK_REDIS_URL=redis://127.0.0.1:6379
|
||||
TALK_ROOT_URL=http://127.0.0.1:3000
|
||||
TALK_PORT=3000
|
||||
TALK_JWT_SECRET=password
|
||||
TALK_FACEBOOK_APP_ID=A-Facebook-App-ID
|
||||
TALK_FACEBOOK_APP_SECRET=A-Facebook-App-Secret
|
||||
```
|
||||
|
||||
This is the bare minimum needed to start Talk, for more configuration
|
||||
variables, check out the [Configuration](/talk/configuration/)
|
||||
section. Facebook login above will definitely not work unless you change those
|
||||
values as well.
|
||||
section.
|
||||
|
||||
|
||||
You can now start the application by running:
|
||||
|
||||
@@ -24,7 +24,7 @@ All levels of comments and replies are able to be linked to via permalink. Perma
|
||||
```text
|
||||
https://<your asset url>?commentId=<the comment id>
|
||||
```
|
||||
{:.no-copy}
|
||||
|
||||
|
||||
### Threading
|
||||
|
||||
@@ -44,7 +44,7 @@ talk-stream-comment-level-${depth}
|
||||
talk-stream-highlighted-comment
|
||||
talk-stream-pending-comment
|
||||
```
|
||||
{:.no-copy}
|
||||
|
||||
|
||||
### Automatic Updates
|
||||
|
||||
|
||||
@@ -154,12 +154,19 @@ en:
|
||||
sign_out: "Sign Out"
|
||||
stories: Stories
|
||||
stream_settings: "Stream Settings"
|
||||
access_message: "You must be an administrator to access config settings. Please find the nearest Admin and ask them to level you up!"
|
||||
suspect_word_title: "Suspect words list"
|
||||
suspect_word_text: "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list."
|
||||
tech_settings: "Tech Settings"
|
||||
title: "Configure Comment Stream"
|
||||
weeks: Weeks
|
||||
wordlist: "Banned Words"
|
||||
save_changes_dialog:
|
||||
unsaved_changes: "Unsaved changes"
|
||||
copy: "You have made one or more changes without saving. Would you like to save or discard your changes?"
|
||||
save_settings: "Save Settings"
|
||||
discard: "Discard"
|
||||
cancel: "Cancel"
|
||||
continue: "Continue"
|
||||
createdisplay:
|
||||
check_the_form: "Invalid Form. Please check the fields"
|
||||
|
||||
@@ -153,12 +153,19 @@ es:
|
||||
sign_out: "Desconectar"
|
||||
stories: Artículos
|
||||
stream_settings: "Configuración de Comentarios"
|
||||
access_message: "Usted debe ser un administrador para acceder a esta página. Encuentre a otro admin y actualice los permisos de su cuenta!"
|
||||
suspect_word_title: "Lista de palabras sospechosas"
|
||||
suspect_word_text: "Comentarios que contengan estas palabras o frases, considerando mayusculas y minúsculas, serán automáticamente destacadas en los comentarios publicados. Escribir una palabra y apretar Enter o Tabulador para agregarla. O pegar una lista de palabras separadas por coma."
|
||||
tech_settings: "Configuración Técnica"
|
||||
title: "Configurar los comentarios"
|
||||
weeks: Semanas
|
||||
wordlist: "Palabras Suspendidas"
|
||||
save_changes_dialog:
|
||||
unsaved_changes: "Cambios no guardados"
|
||||
copy: Has hecho uno o más cambios sin guardar. Deseas guardar o descartar tus cambios?"
|
||||
save_settings: "Guardar configuración"
|
||||
discard: "Descartar"
|
||||
cancel: "Cancelar"
|
||||
continue: "Continuar"
|
||||
createdisplay:
|
||||
check_the_form: "Formulario Inválido. Por favor verifica los campos"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import styles from './Comment.css';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
@@ -28,6 +29,7 @@ class Comment extends React.Component {
|
||||
fill="commentContent"
|
||||
defaultComponent={CommentContent}
|
||||
passthrough={slotPassthrough}
|
||||
size={1}
|
||||
/>
|
||||
|
||||
<div className={cn(`${pluginName}-comment-username-box`)}>
|
||||
@@ -87,4 +89,11 @@ class Comment extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
Comment.propTypes = {
|
||||
viewComment: PropTypes.func,
|
||||
comment: PropTypes.object,
|
||||
asset: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Comment;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Linkify from 'react-linkify';
|
||||
import styles from './AdminCommentContent.css';
|
||||
import { AdminCommentContent as Content } from 'plugin-api/beta/client/components';
|
||||
|
||||
class AdminCommentContent extends React.Component {
|
||||
render() {
|
||||
const { comment, suspectWords, bannedWords } = this.props;
|
||||
return (
|
||||
const content = (
|
||||
<Content
|
||||
className={styles.content}
|
||||
body={comment.richTextBody ? comment.richTextBody : comment.body}
|
||||
@@ -15,6 +16,12 @@ class AdminCommentContent extends React.Component {
|
||||
html={!!comment.richTextBody}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!!comment.richTextBody) {
|
||||
return content;
|
||||
}
|
||||
|
||||
return <Linkify properties={{ target: '_blank' }}>{content}</Linkify>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { PLUGIN_NAME } from '../constants';
|
||||
import cn from 'classnames';
|
||||
import styles from './CommentContent.css';
|
||||
import Linkify from 'react-linkify';
|
||||
|
||||
class CommentContent extends React.Component {
|
||||
render() {
|
||||
@@ -14,7 +15,9 @@ class CommentContent extends React.Component {
|
||||
dangerouslySetInnerHTML={{ __html: comment.richTextBody }}
|
||||
/>
|
||||
) : (
|
||||
<div className={className}>{comment.body}</div>
|
||||
<Linkify properties={{ target: '_blank' }}>
|
||||
<div className={className}>{comment.body}</div>
|
||||
</Linkify>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user