mirror of
https://github.com/wassname/talk.git
synced 2026-08-04 13:23:35 +08:00
Merge master into load-settings
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
dist
|
||||
dist
|
||||
+2
-1
@@ -59,6 +59,7 @@
|
||||
"no-multiple-empty-lines": [
|
||||
"error",
|
||||
{"max": 1}
|
||||
]
|
||||
],
|
||||
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,8 @@ app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
// Routes.
|
||||
app.use('/api/v1', require('./routes/api'));
|
||||
app.use('/client', express.static(path.join(__dirname, 'dist')));
|
||||
app.use('/admin', require('./routes/admin'));
|
||||
app.use('/', require('./routes'));
|
||||
|
||||
//==============================================================================
|
||||
// ERROR HANDLING
|
||||
|
||||
+4
-2
@@ -24,11 +24,13 @@ program
|
||||
const Setting = require('../models/setting');
|
||||
const defaults = {id: '1', moderation: 'pre'};
|
||||
|
||||
Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
|
||||
Setting
|
||||
.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
|
||||
.then(() => {
|
||||
console.log('Created settings object.');
|
||||
mongoose.disconnect();
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`failed to create the settings object ${JSON.stringify(err)}`);
|
||||
throw new Error(err); // just to be safe
|
||||
});
|
||||
|
||||
+6
-3
@@ -71,10 +71,12 @@ function createUser(options) {
|
||||
})
|
||||
.then((result) => {
|
||||
return User.createLocalUser(result.email.trim(), result.password.trim(), result.displayName.trim());
|
||||
}).then((user) => {
|
||||
})
|
||||
.then((user) => {
|
||||
console.log(`Created user ${user.id}.`);
|
||||
mongoose.disconnect();
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
mongoose.disconnect();
|
||||
});
|
||||
@@ -249,7 +251,8 @@ function mergeUsers(dstUserID, srcUserID) {
|
||||
.then(() => {
|
||||
console.log(`User ${srcUserID} was merged into user ${dstUserID}.`);
|
||||
mongoose.disconnect();
|
||||
}).catch((err) => {
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
mongoose.disconnect();
|
||||
});
|
||||
|
||||
@@ -13,27 +13,33 @@ process.env.DEBUG = process.env.TALK_DEBUG;
|
||||
const app = require('../app');
|
||||
const debug = require('debug')('talk:server');
|
||||
const http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
const initPromise = require('../init');
|
||||
const port = normalizePort(process.env.TALK_PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
let server;
|
||||
|
||||
const server = http.createServer(app);
|
||||
initPromise
|
||||
.then(() => {
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
app.set('port', port);
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
});
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
|
||||
@@ -3,16 +3,14 @@ import {Router, Route, IndexRoute, browserHistory} from 'react-router';
|
||||
|
||||
import ModerationQueue from 'containers/ModerationQueue';
|
||||
import CommentStream from 'containers/CommentStream';
|
||||
import EmbedLink from 'components/EmbedLink';
|
||||
import Configure from 'containers/Configure';
|
||||
import CommunityContainer from 'containers/CommunityContainer';
|
||||
import CommunityContainer from 'containers/Community/CommunityContainer';
|
||||
import LayoutContainer from 'containers/LayoutContainer';
|
||||
|
||||
const routes = (
|
||||
<Route path='admin' component={LayoutContainer}>
|
||||
<IndexRoute component={ModerationQueue} />
|
||||
<Route path='embed' component={CommentStream} />
|
||||
<Route path='embedlink' component={EmbedLink} />
|
||||
<Route path='community' component={CommunityContainer} />
|
||||
<Route path='configure' component={Configure} />
|
||||
</Route>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import qs from 'qs';
|
||||
|
||||
import {
|
||||
FETCH_COMMENTERS_REQUEST,
|
||||
FETCH_COMMENTERS_SUCCESS,
|
||||
FETCH_COMMENTERS_FAILURE,
|
||||
SORT_UPDATE,
|
||||
COMMENTERS_NEW_PAGE
|
||||
} from '../constants/community';
|
||||
|
||||
import {base, getInit, handleResp} from '../helpers/response';
|
||||
|
||||
export const fetchCommenters = (query = {}) => dispatch => {
|
||||
dispatch(requestFetchCommenters());
|
||||
fetch(`${base}/user?${qs.stringify(query)}`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
.then(({result, page, count, limit, totalPages}) =>
|
||||
dispatch({
|
||||
type: FETCH_COMMENTERS_SUCCESS,
|
||||
commenters: result,
|
||||
page,
|
||||
count,
|
||||
limit,
|
||||
totalPages
|
||||
})
|
||||
)
|
||||
.catch(error => dispatch({type: FETCH_COMMENTERS_FAILURE, error}));
|
||||
};
|
||||
|
||||
const requestFetchCommenters = () => ({
|
||||
type: FETCH_COMMENTERS_REQUEST
|
||||
});
|
||||
|
||||
export const updateSorting = sort => ({
|
||||
type: SORT_UPDATE,
|
||||
sort
|
||||
});
|
||||
|
||||
export const newPage = () => ({
|
||||
type: COMMENTERS_NEW_PAGE
|
||||
});
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#embedLink {
|
||||
width:400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.embedTextarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
margin-top: 20px;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import styles from './EmbedLink.css';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
import {Button} from 'react-mdl';
|
||||
|
||||
const embedText =
|
||||
`<div id='coralStreamEmbed'></div><script type='text/javascript' src='http://pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/embedScript/index.html', {});</script>`;
|
||||
|
||||
const copyToClipBoard = () => {
|
||||
const copyTextarea = document.querySelector(`.${ styles.embedTextarea}`);
|
||||
copyTextarea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} catch (err) {
|
||||
console.error('Unable to copy');
|
||||
}
|
||||
};
|
||||
|
||||
const EmbedLink = () => <div id={styles.embedLink}>
|
||||
<h3>Embed Comment Stream</h3>
|
||||
<textarea
|
||||
className={styles.embedTextarea}
|
||||
onClick={copyToClipBoard}
|
||||
rows={4}
|
||||
value={embedText} />
|
||||
<div className={styles.copyButton}>
|
||||
<Button onClick={copyToClipBoard} raised>
|
||||
{lang.t('embedlink.copy')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
export default EmbedLink;
|
||||
|
||||
const lang = new I18n(translations);
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Layout} from 'react-mdl';
|
||||
import 'material-design-lite';
|
||||
import Header from 'components/Header';
|
||||
|
||||
export default (props) => (
|
||||
<Layout>
|
||||
<Header>
|
||||
{props.children}
|
||||
</Header>
|
||||
</Layout>
|
||||
);
|
||||
@@ -9,6 +9,9 @@ export default () => (
|
||||
<Link className={styles.navLink} to="/admin">Moderate</Link>
|
||||
<Link className={styles.navLink} to="/admin/community">Community</Link>
|
||||
<Link className={styles.navLink} to="/admin/configure">Configure</Link>
|
||||
<span>
|
||||
{`v${process.env.VERSION}`}
|
||||
</span>
|
||||
</Navigation>
|
||||
</Header>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.layout {
|
||||
max-width: 1170px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@@ -2,11 +2,14 @@ import React from 'react';
|
||||
import {Layout as LayoutMDL} from 'react-mdl';
|
||||
import Header from './Header';
|
||||
import Drawer from './Drawer';
|
||||
import styles from './Layout.css';
|
||||
|
||||
export const Layout = ({children}) => (
|
||||
<LayoutMDL fixedDrawer>
|
||||
<Header />
|
||||
<Drawer />
|
||||
{children}
|
||||
<div className={styles.layout} >
|
||||
{children}
|
||||
</div>
|
||||
</LayoutMDL>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const FETCH_COMMENTERS_REQUEST = 'FETCH_COMMENTERS_REQUEST';
|
||||
export const FETCH_COMMENTERS_SUCCESS = 'FETCH_COMMENTERS_SUCCESS';
|
||||
export const FETCH_COMMENTERS_FAILURE = 'FETCH_COMMENTERS_FAILURE';
|
||||
export const SORT_UPDATE = 'SORT_UPDATE';
|
||||
export const COMMENTERS_NEW_PAGE = 'COMMENTERS_NEW_PAGE';
|
||||
@@ -0,0 +1,22 @@
|
||||
.dataTable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.roleButton {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
display: block;
|
||||
padding-left: 40px;
|
||||
/*border: none;*/
|
||||
}
|
||||
|
||||
.searchBox {
|
||||
/*border: 1px solid rgba(0,0,0,.12);*/
|
||||
background: white;
|
||||
}
|
||||
|
||||
.email {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../../translations';
|
||||
import {Grid, Cell} from 'react-mdl';
|
||||
|
||||
import styles from './Community.css';
|
||||
import Table from './Table';
|
||||
import Loading from './Loading';
|
||||
import NoResults from './NoResults';
|
||||
import Pager from './Pager';
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const tableHeaders = [
|
||||
{
|
||||
title: lang.t('community.username_and_email'),
|
||||
field: 'displayName'
|
||||
},
|
||||
{
|
||||
title: lang.t('community.account_creation_date'),
|
||||
field: 'created_at'
|
||||
}
|
||||
];
|
||||
|
||||
const Community = ({isFetching, commenters, ...props}) => {
|
||||
const hasResults = !isFetching && !!commenters.length;
|
||||
return (
|
||||
<Grid>
|
||||
<Cell col={4}>
|
||||
<form action="">
|
||||
<div className={`mdl-textfield ${styles.searchBox}`}>
|
||||
<label className="mdl-button mdl-js-button mdl-button--icon" htmlFor="commenters-search">
|
||||
<i className="material-icons">search</i>
|
||||
</label>
|
||||
<div className="">
|
||||
<input
|
||||
id="commenters-search"
|
||||
className={`mdl-textfield__input ${styles.searchInput}`}
|
||||
type="text"
|
||||
value={props.searchValue}
|
||||
onKeyDown={props.onKeyDownHandler}
|
||||
onChange={props.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Cell>
|
||||
<Cell col={8}>
|
||||
{ isFetching && <Loading /> }
|
||||
{ !hasResults && <NoResults /> }
|
||||
{ hasResults &&
|
||||
<Table
|
||||
headers={tableHeaders}
|
||||
data={commenters}
|
||||
onHeaderClickHandler={props.onHeaderClickHandler}
|
||||
/>
|
||||
}
|
||||
<Pager
|
||||
totalPages={props.totalPages}
|
||||
page={props.page}
|
||||
onNewPageHandler={props.onNewPageHandler}
|
||||
/>
|
||||
</Cell>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default Community;
|
||||
@@ -0,0 +1,80 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
fetchCommenters,
|
||||
updateSorting,
|
||||
newPage,
|
||||
} from '../../actions/community';
|
||||
|
||||
import Community from './Community';
|
||||
|
||||
class CommunityContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
searchValue: ''
|
||||
};
|
||||
|
||||
this.onKeyDownHandler = this.onKeyDownHandler.bind(this);
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
this.onHeaderClickHandler = this.onHeaderClickHandler.bind(this);
|
||||
this.onNewPageHandler = this.onNewPageHandler.bind(this);
|
||||
}
|
||||
|
||||
onKeyDownHandler(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
onChangeHandler(e) {
|
||||
this.setState({
|
||||
searchValue: e.target.value
|
||||
});
|
||||
}
|
||||
|
||||
search(query = {}) {
|
||||
const {community} = this.props;
|
||||
|
||||
this.props.dispatch(fetchCommenters({
|
||||
value: this.state.searchValue,
|
||||
field: community.get('field'),
|
||||
asc: community.get('asc'),
|
||||
...query
|
||||
}));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.search();
|
||||
}
|
||||
|
||||
onHeaderClickHandler(sort) {
|
||||
this.props.dispatch(updateSorting(sort));
|
||||
this.search();
|
||||
}
|
||||
|
||||
onNewPageHandler(page) {
|
||||
this.props.dispatch(newPage(page));
|
||||
this.search({page});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {searchValue} = this.state;
|
||||
const {community} = this.props;
|
||||
return (
|
||||
<Community
|
||||
searchValue={searchValue}
|
||||
commenters={community.get('commenters')}
|
||||
isFetching={community.get('isFetching')}
|
||||
error={community.get('error')}
|
||||
totalPages={community.get('totalPages')}
|
||||
page={community.get('page')}
|
||||
{...this}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({community}) => ({community}))(CommunityContainer);
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const Loading = () => (
|
||||
<h1> Loading results </h1>
|
||||
);
|
||||
|
||||
export default Loading;
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
const NoResults = () => (
|
||||
<div>
|
||||
No users found with that user name or email address
|
||||
</div>
|
||||
);
|
||||
|
||||
export default NoResults;
|
||||
@@ -0,0 +1,10 @@
|
||||
.li {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
padding: 0;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.current {
|
||||
background: #e3edf3;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import styles from './Pager.css';
|
||||
|
||||
const Rows = (curr, total, onClickHandler) => Array.from(Array(total)).map((e, i) =>
|
||||
<li className={`mdl-button mdl-js-button ${styles.li} ${curr === i ? styles.current : ''}`}
|
||||
key={i} onClick={() => onClickHandler(i + 1)}>
|
||||
{i + 1}
|
||||
</li>
|
||||
);
|
||||
|
||||
const Pager = ({totalPages, page, onNewPageHandler}) => (
|
||||
<div className="pager">
|
||||
<ul>
|
||||
{
|
||||
(totalPages > page) ?
|
||||
<li
|
||||
className={`mdl-button mdl-js-button ${styles.li}`}
|
||||
onClick={() => onNewPageHandler(page - 1)}>
|
||||
Prev
|
||||
</li>
|
||||
:
|
||||
null
|
||||
}
|
||||
{Rows(page, totalPages, onNewPageHandler)}
|
||||
{
|
||||
(page < totalPages) ?
|
||||
<li
|
||||
className={`mdl-button mdl-js-button ${styles.li}`}
|
||||
onClick={() => onNewPageHandler(page + 1)}>
|
||||
Next
|
||||
</li>
|
||||
:
|
||||
null
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
Pager.propTypes = {
|
||||
totalPages: PropTypes.number.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default Pager;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import styles from './Community.css';
|
||||
|
||||
const Table = ({headers, data, onHeaderClickHandler}) => (
|
||||
<table className={`mdl-data-table ${styles.dataTable}`}>
|
||||
<thead>
|
||||
<tr>
|
||||
{headers.map((header, i) =>(
|
||||
<th
|
||||
key={i}
|
||||
className="mdl-data-table__cell--non-numeric"
|
||||
onClick={() => onHeaderClickHandler({field: header.field})}>
|
||||
{header.title}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, i)=> (
|
||||
<tr key={i}>
|
||||
<td className="mdl-data-table__cell--non-numeric">
|
||||
{row.displayName}
|
||||
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
|
||||
</td>
|
||||
<td className="mdl-data-table__cell--non-numeric">
|
||||
{row.created_at}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
export default Table;
|
||||
@@ -1,11 +0,0 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
export default class CommunityContainer extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Community</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -80,8 +80,7 @@ class Configure extends React.Component {
|
||||
}
|
||||
|
||||
getEmbed () {
|
||||
const embedText =
|
||||
`<div id='coralStreamEmbed'></div><script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/client/embed/stream/bundle.js', {title: 'comments'});</script>`;
|
||||
const embedText = `<div id='coralStreamEmbed'></div><script type='text/javascript' src='http://pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/embed/stream', {});</script>`;
|
||||
|
||||
return <List>
|
||||
<ListItem className={styles.configSettingEmbed}>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import React, {Component}from 'react';
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {Layout} from '../components/ui/Layout';
|
||||
|
||||
class LayoutContainer extends Component {
|
||||
render () {
|
||||
return <Layout { ...this.props } />;
|
||||
return <Layout {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
LayoutContainer.propTypes = {};
|
||||
|
||||
const mapStateToProps = () => ({data: {}});
|
||||
const mapStateToProps = () => ({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({dispatch});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer);
|
||||
|
||||
|
||||
@@ -73,7 +73,12 @@ class ModerationQueue extends React.Component {
|
||||
<CommentList
|
||||
isActive={activeTab === 'pending'}
|
||||
singleView={singleView}
|
||||
commentIds={comments.get('ids').filter(id => !comments.get('byId').get(id).get('status'))}
|
||||
commentIds={
|
||||
comments.get('ids')
|
||||
.filter(id => !comments.get('byId')
|
||||
.get(id)
|
||||
.get('status'))
|
||||
}
|
||||
comments={comments.get('byId')}
|
||||
onClickAction={(action, id) => this.onCommentAction(action, id)}
|
||||
actions={['reject', 'approve']}
|
||||
@@ -83,7 +88,15 @@ class ModerationQueue extends React.Component {
|
||||
<CommentList
|
||||
isActive={activeTab === 'rejected'}
|
||||
singleView={singleView}
|
||||
commentIds={comments.get('ids').filter(id => comments.get('byId').get(id).get('status') === 'rejected')}
|
||||
commentIds={
|
||||
comments
|
||||
.get('ids')
|
||||
.filter(id =>
|
||||
comments
|
||||
.get('byId')
|
||||
.get(id)
|
||||
.get('status') === 'rejected')
|
||||
}
|
||||
comments={comments.get('byId')}
|
||||
onClickAction={(action, id) => this.onCommentAction(action, id)}
|
||||
actions={['approve']}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export const base = '/api/v1';
|
||||
|
||||
export const getInit = (method, body) => {
|
||||
const headers = new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
});
|
||||
|
||||
const init = {method, headers};
|
||||
if (method.toLowerCase() !== 'get') {
|
||||
init.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
return init;
|
||||
};
|
||||
|
||||
export const handleResp = res => {
|
||||
if (res.status === 401) {
|
||||
throw new Error('Not Authorized to make this request');
|
||||
} else if (res.status > 399) {
|
||||
throw new Error('Error! Status ', res.status);
|
||||
} else if (res.status === 204) {
|
||||
return res.text();
|
||||
} else {
|
||||
return res.json();
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './components/App';
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import {Map} from 'immutable';
|
||||
|
||||
import {
|
||||
FETCH_COMMENTERS_REQUEST,
|
||||
FETCH_COMMENTERS_FAILURE,
|
||||
FETCH_COMMENTERS_SUCCESS,
|
||||
SORT_UPDATE
|
||||
} from '../constants/community';
|
||||
|
||||
const initialState = Map({
|
||||
community: Map(),
|
||||
isFetching: false,
|
||||
error: '',
|
||||
commenters: [],
|
||||
field: 'created_at',
|
||||
asc: false,
|
||||
totalPages: 0,
|
||||
page: 0
|
||||
});
|
||||
|
||||
export default function community (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case FETCH_COMMENTERS_REQUEST :
|
||||
return state
|
||||
.set('isFetching', true);
|
||||
case FETCH_COMMENTERS_FAILURE :
|
||||
return state
|
||||
.set('isFetching', false)
|
||||
.set('error', action.error);
|
||||
case FETCH_COMMENTERS_SUCCESS : {
|
||||
const {commenters, type, ...rest} = action; // eslint-disable-line
|
||||
return state
|
||||
.merge({
|
||||
isFetching: false,
|
||||
error: '',
|
||||
...rest
|
||||
})
|
||||
.set('commenters', commenters); // Sets to normal array
|
||||
}
|
||||
case SORT_UPDATE :
|
||||
return state
|
||||
.set('field', action.sort.field)
|
||||
.set('asc', !state.get('asc'));
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
|
||||
import {combineReducers} from 'redux';
|
||||
import comments from 'reducers/comments';
|
||||
import settings from 'reducers/settings';
|
||||
import community from 'reducers/community';
|
||||
|
||||
// Combine all reducers into a main one
|
||||
export default combineReducers({
|
||||
settings,
|
||||
comments
|
||||
comments,
|
||||
community
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
export default {
|
||||
en: {
|
||||
'community': {
|
||||
username_and_email: 'Username and Email',
|
||||
account_creation_date: 'Account Creation Date'
|
||||
},
|
||||
'modqueue': {
|
||||
'pending': 'pending',
|
||||
'rejected': 'rejected',
|
||||
@@ -24,6 +28,10 @@ export default {
|
||||
}
|
||||
},
|
||||
es: {
|
||||
'community': {
|
||||
username_and_email: 'Usuario y E-mail',
|
||||
account_creation_date: 'Fecha de creación de la cuenta'
|
||||
},
|
||||
'modqueue': {
|
||||
'pending': 'pendiente',
|
||||
'rejected': 'rechazado',
|
||||
|
||||
@@ -241,7 +241,8 @@ export function postAction (item_id, action_type, user_id, item_type) {
|
||||
return response.ok ? response.json()
|
||||
: Promise.reject(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
).then((json)=>{
|
||||
)
|
||||
.then((json)=>{
|
||||
return json;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
const Setting = require('./models/setting');
|
||||
|
||||
const defaults = {id: '1', moderation: 'pre'};
|
||||
module.exports = Setting.init(defaults);
|
||||
|
||||
// presumably this file will grow, which is why I've broken it out.
|
||||
+21
-6
@@ -86,9 +86,13 @@ CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) {
|
||||
* @param {String} action_type the type of action that was performed on the comment
|
||||
*/
|
||||
CommentSchema.statics.findByActionType = function(action_type) {
|
||||
return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => {
|
||||
return Comment.find({'id': {'$in': actions.map(function(a){return a.item_id;})}});
|
||||
});
|
||||
return Action
|
||||
.findCommentsIdByActionType(action_type, 'comment')
|
||||
.then((actions) => {
|
||||
return Comment.find({'id': {'$in': actions.map(function(a){
|
||||
return a.item_id;})}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -97,9 +101,20 @@ CommentSchema.statics.findByActionType = function(action_type) {
|
||||
* @param {String} status the status of the comment to search for
|
||||
*/
|
||||
CommentSchema.statics.findByStatusByActionType = function(status, action_type) {
|
||||
return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => {
|
||||
return Comment.find({'status': status, 'id': {'$in': actions.map(function(a){return a.item_id;})}});
|
||||
});
|
||||
return Action
|
||||
.findCommentsIdByActionType(action_type, 'comment')
|
||||
.then((actions) => {
|
||||
|
||||
return Comment.find({
|
||||
'status': status,
|
||||
'id': {
|
||||
'$in': actions.map(a => {
|
||||
return a.item_id;
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+13
-4
@@ -12,7 +12,15 @@ const SettingSchema = new Schema({
|
||||
});
|
||||
|
||||
/**
|
||||
* gets the entire settings record and sends it back
|
||||
* this is run once when the app starts to ensure settings are populated
|
||||
* @return {Promise} null initialize the global settings object
|
||||
*/
|
||||
SettingSchema.statics.init = function (defaults) {
|
||||
return this.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the entire settings record and sends it back
|
||||
* @return {Promise} settings the whole settings record
|
||||
*/
|
||||
SettingSchema.statics.getSettings = function () {
|
||||
@@ -20,7 +28,7 @@ SettingSchema.statics.getSettings = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* gets the moderation settings and sends it back
|
||||
* Gets the moderation settings and sends it back
|
||||
* @return {Promise} moderation the settings for how to moderate comments
|
||||
*/
|
||||
SettingSchema.statics.getModerationSetting = function () {
|
||||
@@ -28,12 +36,13 @@ SettingSchema.statics.getModerationSetting = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* this will update the settings object with whatever you pass in
|
||||
* This will update the settings object with whatever you pass in
|
||||
* @param {object} setting a hash of whatever settings you want to update
|
||||
* @return {Promise} settings Promise that resolves to the entire (updated) settings object.
|
||||
*/
|
||||
SettingSchema.statics.updateSettings = function (setting) {
|
||||
// there should only ever be one record unless something has gone wrong.
|
||||
// There should only ever be one record unless something has gone wrong.
|
||||
// In the future we may have multiple records for custom settings for objects/users.
|
||||
return this.findOneAndUpdate({id: '1'}, {$set: setting}, {new: true});
|
||||
};
|
||||
|
||||
|
||||
+59
-34
@@ -25,6 +25,11 @@ const UserSchema = new mongoose.Schema({
|
||||
}
|
||||
}],
|
||||
roles: [String]
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
}
|
||||
});
|
||||
|
||||
// Add the indixies on the user profile data.
|
||||
@@ -52,6 +57,22 @@ UserSchema.options.toJSON.transform = (doc, ret, options) => {
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* toObject overrides to remove the password field from the toObject
|
||||
* output.
|
||||
*/
|
||||
UserSchema.options.toObject = {};
|
||||
UserSchema.options.toObject.hide = 'password';
|
||||
UserSchema.options.toObject.transform = (doc, ret, options) => {
|
||||
if (options.hide) {
|
||||
options.hide.split(' ').forEach((prop) => {
|
||||
delete ret[prop];
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a user given their email address that we have for them in the system
|
||||
* and ensures that the retuned user matches the password passed in as well.
|
||||
@@ -100,19 +121,22 @@ UserSchema.statics.findLocalUser = function(email, password) {
|
||||
UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
|
||||
let srcUser, dstUser;
|
||||
|
||||
return Promise.all([
|
||||
User.findOne({id: dstUserID}).exec(),
|
||||
User.findOne({id: srcUserID}).exec()
|
||||
]).then((users) => {
|
||||
dstUser = users[0];
|
||||
srcUser = users[1];
|
||||
return Promise
|
||||
.all([
|
||||
User.findOne({id: dstUserID}).exec(),
|
||||
User.findOne({id: srcUserID}).exec()
|
||||
])
|
||||
.then((users) => {
|
||||
dstUser = users[0];
|
||||
srcUser = users[1];
|
||||
|
||||
srcUser.profiles.forEach((profile) => {
|
||||
dstUser.profiles.push(profile);
|
||||
});
|
||||
srcUser.profiles.forEach((profile) => {
|
||||
dstUser.profiles.push(profile);
|
||||
});
|
||||
|
||||
return srcUser.remove();
|
||||
}).then(() => dstUser.save());
|
||||
return srcUser.remove();
|
||||
})
|
||||
.then(() => dstUser.save());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -122,33 +146,34 @@ UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
|
||||
* @param {Function} done [description]
|
||||
*/
|
||||
UserSchema.statics.findOrCreateExternalUser = function(profile) {
|
||||
return User.findOne({
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
id: profile.id,
|
||||
provider: profile.provider
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((user) => {
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
|
||||
// The user was not found, lets create them!
|
||||
user = new User({
|
||||
displayName: profile.displayName,
|
||||
roles: [],
|
||||
profiles: [
|
||||
{
|
||||
return User
|
||||
.findOne({
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
id: profile.id,
|
||||
provider: profile.provider
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
})
|
||||
.then((user) => {
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
|
||||
return user.save();
|
||||
});
|
||||
// The user was not found, lets create them!
|
||||
user = new User({
|
||||
displayName: profile.displayName,
|
||||
roles: [],
|
||||
profiles: [
|
||||
{
|
||||
id: profile.id,
|
||||
provider: profile.provider
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return user.save();
|
||||
});
|
||||
};
|
||||
|
||||
UserSchema.statics.changePassword = function(id, password) {
|
||||
|
||||
@@ -12,17 +12,17 @@ const router = express.Router();
|
||||
router.get('/', (req, res, next) => {
|
||||
Comment.find({}).then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
router.get('/:comment_id', (req, res, next) => {
|
||||
Comment.findById(req.params.comment_id).then((comment) => {
|
||||
res.status(200).json(comment);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
Comment
|
||||
.findById(req.params.comment_id)
|
||||
.then(comment => {
|
||||
res.status(200).json(comment);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
@@ -31,20 +31,22 @@ router.get('/:comment_id', (req, res, next) => {
|
||||
|
||||
// Get all the comments that have that action_type over them.
|
||||
router.get('/action/:action_type', (req, res, next) => {
|
||||
Comment.findByActionType(req.params.action_type).then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
Comment
|
||||
.findByActionType(req.params.action_type)
|
||||
.then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// Get all the comments that were rejected.
|
||||
router.get('/status/rejected', (req, res, next) => {
|
||||
Comment.findByStatus('rejected').then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
Comment
|
||||
.findByStatus('rejected')
|
||||
.then(comments => {
|
||||
res.status(200).json(comments);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated,
|
||||
@@ -52,17 +54,21 @@ router.get('/status/rejected', (req, res, next) => {
|
||||
// Pre-moderation: New comments are shown in the moderator queues immediately.
|
||||
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
|
||||
router.get('/status/pending', (req, res, next) => {
|
||||
Setting.getModerationSetting().then(function({moderation}){
|
||||
let moderationValue = req.query.moderation;
|
||||
if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
|
||||
moderationValue = moderation;
|
||||
}
|
||||
Comment.moderationQueue(moderationValue).then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
});
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
|
||||
Setting
|
||||
.getModerationSetting()
|
||||
.then(({moderation}) => {
|
||||
let moderationValue = req.query.moderation;
|
||||
if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
|
||||
moderationValue = moderation;
|
||||
}
|
||||
Comment
|
||||
.moderationQueue(moderationValue)
|
||||
.then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
});
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
@@ -70,27 +76,36 @@ router.get('/status/pending', (req, res, next) => {
|
||||
//==============================================================================
|
||||
|
||||
router.post('/', (req, res, next) => {
|
||||
|
||||
const {body, author_id, asset_id, parent_id, status, username} = req.body;
|
||||
Comment.new(body, author_id, asset_id, parent_id, status, username).then((comment) => {
|
||||
res.status(200).send({'id': comment.id});
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
|
||||
Comment
|
||||
.new(body, author_id, asset_id, parent_id, status, username)
|
||||
.then((comment) => {
|
||||
res.status(200).send({'id': comment.id});
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:comment_id', (req, res, next) => {
|
||||
Comment.findById(req.params.comment_id).then((comment) => {
|
||||
comment.body = req.body.body;
|
||||
comment.author_id = req.body.author_id;
|
||||
comment.asset_id = req.body.asset_id;
|
||||
comment.parent_id = req.body.parent_id;
|
||||
comment.status = req.body.status;
|
||||
return comment.save();
|
||||
}).then((comment) => {
|
||||
res.status(200).send(comment);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
Comment
|
||||
.findById(req.params.comment_id)
|
||||
.then((comment) => {
|
||||
comment.body = req.body.body;
|
||||
comment.author_id = req.body.author_id;
|
||||
comment.asset_id = req.body.asset_id;
|
||||
comment.parent_id = req.body.parent_id;
|
||||
comment.status = req.body.status;
|
||||
return comment.save();
|
||||
})
|
||||
.then((comment) => {
|
||||
res.status(200).send(comment);
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:comment_id/status', (req, res, next) => {
|
||||
@@ -103,11 +118,14 @@ router.post('/:comment_id/status', (req, res, next) => {
|
||||
});
|
||||
|
||||
router.post('/:comment_id/actions', (req, res, next) => {
|
||||
Comment.addAction(req.params.comment_id, req.body.user_id, req.body.action_type).then((action) => {
|
||||
res.status(200).send(action);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
Comment
|
||||
.addAction(req.params.comment_id, req.body.user_id, req.body.action_type)
|
||||
.then((action) => {
|
||||
res.status(200).send(action);
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
@@ -115,11 +133,14 @@ router.post('/:comment_id/actions', (req, res, next) => {
|
||||
//==============================================================================
|
||||
|
||||
router.delete('/:comment_id', (req, res, next) => {
|
||||
Comment.removeById(req.params.comment_id).then(() => {
|
||||
res.status(201).send('OK. Removed');
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
Comment
|
||||
.removeById(req.params.comment_id)
|
||||
.then(() => {
|
||||
res.status(201).send('OK. Removed');
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -6,5 +6,6 @@ router.use('/asset', require('./asset'));
|
||||
router.use('/comments', require('./comments'));
|
||||
router.use('/settings', require('./settings'));
|
||||
router.use('/stream', require('./stream'));
|
||||
router.use('/user', require('./user'));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -3,11 +3,17 @@ const router = express.Router();
|
||||
const Setting = require('../../../models/setting');
|
||||
|
||||
router.get('/', (req, res, next) => {
|
||||
Setting.getSettings().then(settings => res.json(settings)).catch(next);
|
||||
Setting
|
||||
.getSettings()
|
||||
.then(settings => res.json(settings))
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
router.put('/', (req, res, next) => {
|
||||
Setting.updateSettings(req.body).then(() => res.status(204).end()).catch(next);
|
||||
Setting
|
||||
.updateSettings(req.body)
|
||||
.then(() => res.status(204).end())
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -36,7 +36,8 @@ router.get('/', (req, res, next) => {
|
||||
users,
|
||||
actions
|
||||
});
|
||||
}).catch(error => {
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const User = require('../../../models/user');
|
||||
|
||||
router.get('/', (req, res, next) => {
|
||||
const {
|
||||
value = '',
|
||||
field = 'created_at',
|
||||
page = 1,
|
||||
asc = 'false',
|
||||
limit = 50 // Total Per Page
|
||||
} = req.query;
|
||||
|
||||
let q = {
|
||||
$or: [
|
||||
{
|
||||
'displayName': {
|
||||
$regex: new RegExp(`^${value}`),
|
||||
$options: 'i'
|
||||
},
|
||||
'profiles': {
|
||||
$elemMatch: {
|
||||
id: {
|
||||
$regex: new RegExp(`^${value}`),
|
||||
$options: 'i'
|
||||
},
|
||||
provider: 'local'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Promise.all([
|
||||
User.find(q)
|
||||
.sort({[field]: (asc === 'true') ? 1 : -1})
|
||||
.skip((page - 1) * limit)
|
||||
.limit(limit),
|
||||
User.count()
|
||||
])
|
||||
.then(([data, count]) => {
|
||||
const users = data.map((user) => {
|
||||
const {displayName, created_at} = user;
|
||||
return {
|
||||
displayName,
|
||||
created_at,
|
||||
profiles: user.toObject().profiles
|
||||
};
|
||||
});
|
||||
|
||||
res.json({
|
||||
result: users,
|
||||
limit: Number(limit),
|
||||
count,
|
||||
page: Number(page),
|
||||
totalPages: Math.ceil(count / limit)
|
||||
});
|
||||
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,14 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
router.use('/:embed', (req, res, next) => {
|
||||
switch (req.params.embed) {
|
||||
case 'stream':
|
||||
return res.render('embed/stream', {});
|
||||
default:
|
||||
// will return a 404.
|
||||
return next();
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,12 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
router.use('/api/v1', require('./api'));
|
||||
router.use('/admin', require('./admin'));
|
||||
router.use('/embed', require('./embed'));
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
return res.render('home', {});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -43,11 +43,13 @@ describe('update settings', () => {
|
||||
|
||||
return Setting.getSettings();
|
||||
|
||||
}).then(settings => {
|
||||
})
|
||||
.then(settings => {
|
||||
// confirm updated settings in db
|
||||
expect(settings).to.have.property('moderation');
|
||||
expect(settings.moderation).to.equal('post');
|
||||
}).catch(err => {
|
||||
})
|
||||
.catch(err => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="/client/embed/stream/default.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato|Open+Sans" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="coralStream"/>
|
||||
<script src="/client/embed/stream/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div style="margin-left:auto; margin-right:auto; width:500px">
|
||||
<h1>Corem ipsal</h1>
|
||||
<p>Lorem ipsum dolor sponge amet, consectetur adipiscing clam. Ut lobortis sollicitudin pillar a ornare. Curabitur dignissim vestibulum cay non rhoncus. Cras laoreet ante vel nunc hendrerit, shelf imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Talk volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non trust nec neque congue faucibus porttitor sit amet elkhorn.</p>
|
||||
|
||||
<div id='coralStreamEmbed'></div>
|
||||
<script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream', {title: 'Talk Comments'});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'})</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -82,6 +82,12 @@ module.exports = {
|
||||
precss,
|
||||
new webpack.ProvidePlugin({
|
||||
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': `"${'development'}"`,
|
||||
'VERSION': `"${require('./package.json').version}"`
|
||||
}
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ devConfig.plugins = devConfig.plugins.concat([
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': `"${'production'}"`,
|
||||
'VERSION': `"${require('./package.json')}"`
|
||||
'VERSION': `"${require('./package.json').version}"`
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
|
||||
Reference in New Issue
Block a user