mirror of
https://github.com/wassname/talk.git
synced 2026-07-17 11:33:39 +08:00
Merge branch 'master' of https://github.com/coralproject/talk into stream-e2e
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
||||
dist
|
||||
dist
|
||||
client/lib
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as actions from '../constants/auth';
|
||||
import {base, handleResp, getInit} from '../../../coral-framework/helpers/response';
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
|
||||
// Check Login
|
||||
|
||||
@@ -9,8 +9,7 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
|
||||
|
||||
export const checkLogin = () => dispatch => {
|
||||
dispatch(checkLoginRequest());
|
||||
fetch(`${base}/auth`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
coralApi('/auth')
|
||||
.then(user => {
|
||||
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
|
||||
dispatch(checkLoginSuccess(user, isAdmin));
|
||||
@@ -26,8 +25,7 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE});
|
||||
|
||||
export const logout = () => dispatch => {
|
||||
dispatch(logOutRequest());
|
||||
fetch(`${base}/auth`, getInit('DELETE'))
|
||||
.then(handleResp)
|
||||
coralApi('/auth', {method: 'DELETE'})
|
||||
.then(() => dispatch(logOutSuccess()))
|
||||
.catch(error => dispatch(logOutFailure(error)));
|
||||
};
|
||||
|
||||
@@ -9,12 +9,11 @@ import {
|
||||
SET_ROLE
|
||||
} from '../constants/community';
|
||||
|
||||
import {base, getInit, handleResp} from '../../../coral-framework/helpers/response';
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
|
||||
export const fetchCommenters = (query = {}) => dispatch => {
|
||||
dispatch(requestFetchCommenters());
|
||||
fetch(`${base}/user?${qs.stringify(query)}`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
coralApi(`/user?${qs.stringify(query)}`)
|
||||
.then(({result, page, count, limit, totalPages}) =>
|
||||
dispatch({
|
||||
type: FETCH_COMMENTERS_SUCCESS,
|
||||
@@ -42,7 +41,7 @@ export const newPage = () => ({
|
||||
});
|
||||
|
||||
export const setRole = (id, role) => dispatch => {
|
||||
return fetch(`${base}/user/${id}/role`, getInit('POST', {role}))
|
||||
return coralApi(`/user/${id}/role`, {method: 'POST', body: {role}})
|
||||
.then(() => {
|
||||
return dispatch({type: SET_ROLE, id, role});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {base, handleResp, getInit} from '../../../coral-framework/helpers/response';
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
|
||||
export const SETTINGS_LOADING = 'SETTINGS_LOADING';
|
||||
export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED';
|
||||
@@ -12,8 +12,7 @@ export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED';
|
||||
|
||||
export const fetchSettings = () => dispatch => {
|
||||
dispatch({type: SETTINGS_LOADING});
|
||||
fetch(`${base}/settings`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
coralApi('/settings')
|
||||
.then(settings => {
|
||||
dispatch({type: SETTINGS_RECEIVED, settings});
|
||||
})
|
||||
@@ -29,8 +28,7 @@ export const updateSettings = settings => {
|
||||
export const saveSettingsToServer = () => (dispatch, getState) => {
|
||||
const settings = getState().settings.toJS().settings;
|
||||
dispatch({type: SAVE_SETTINGS_LOADING});
|
||||
fetch(`${base}/settings`, getInit('PUT', settings))
|
||||
.then(handleResp)
|
||||
coralApi('/settings', {method: 'PUT', body: settings})
|
||||
.then(() => {
|
||||
dispatch({type: SAVE_SETTINGS_SUCCESS, settings});
|
||||
})
|
||||
|
||||
@@ -12,26 +12,27 @@ const linkify = new Linkify();
|
||||
|
||||
// Render a single comment for the list
|
||||
export default props => {
|
||||
const links = linkify.getMatches(props.comment.get('body'));
|
||||
const {comment, author} = props;
|
||||
const links = linkify.getMatches(comment.get('body'));
|
||||
|
||||
return (
|
||||
<li tabIndex={props.index} className={`${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
|
||||
<div className={styles.itemHeader}>
|
||||
<div className={styles.author}>
|
||||
<i className={`material-icons ${styles.avatar}`}>person</i>
|
||||
<span>{props.comment.get('name') || lang.t('comment.anon')}</span>
|
||||
<span className={styles.created}>{timeago().format(props.comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
|
||||
{props.comment.get('flagged') ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
|
||||
<span>{author.get('displayName') || lang.t('comment.anon')}</span>
|
||||
<span className={styles.created}>{timeago().format(comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
|
||||
{comment.get('flagged') ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
|
||||
</div>
|
||||
<div>
|
||||
{links ?
|
||||
<span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
|
||||
<div className={styles.actions}>
|
||||
{props.actions.map((action, i) => canShowAction(action, props.comment) ? (
|
||||
{props.actions.map((action, i) => canShowAction(action, comment) ? (
|
||||
<FabButton icon={props.actionsMap[action].icon} className={styles.actionButton}
|
||||
cStyle={action}
|
||||
key={i}
|
||||
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
|
||||
onClick={() => props.onClickAction(props.actionsMap[action].status, comment.get('id'))}
|
||||
/>
|
||||
) : null)}
|
||||
</div>
|
||||
@@ -40,7 +41,7 @@ export default props => {
|
||||
<div className={styles.itemBody}>
|
||||
<span className={styles.body}>
|
||||
<Linkify component='span' properties={{style: linkStyles}}>
|
||||
{props.comment.get('body')}
|
||||
{comment.get('body')}
|
||||
</Linkify>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -112,13 +112,15 @@ export default class CommentList extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {singleView, commentIds, comments, hideActive, key} = this.props;
|
||||
const {singleView, commentIds, comments, users, hideActive, key} = this.props;
|
||||
const {active} = this.state;
|
||||
|
||||
return (
|
||||
<ul className={`${styles.list} ${singleView ? styles.singleView : ''}`} {...key}>
|
||||
{commentIds.map((commentId, index) => (
|
||||
<Comment comment={comments.get(commentId)}
|
||||
{commentIds.map((commentId, index) => {
|
||||
const comment = comments.get(commentId);
|
||||
return <Comment comment={comment}
|
||||
author={users.get(comment.get('author_id'))}
|
||||
ref={el => { if (el && commentId === active) { this._active = el; } }}
|
||||
key={index}
|
||||
index={index}
|
||||
@@ -126,8 +128,8 @@ export default class CommentList extends React.Component {
|
||||
actions={this.props.actions}
|
||||
actionsMap={actions}
|
||||
isActive={commentId === active}
|
||||
hideActive={hideActive} />
|
||||
)).toArray()}
|
||||
hideActive={hideActive} />;
|
||||
}).toArray()}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class CommentStream extends React.Component {
|
||||
}
|
||||
|
||||
// Render the comment box along with the CommentList
|
||||
render ({comments}, {snackbar, snackbarMsg}) {
|
||||
render ({comments, users}, {snackbar, snackbarMsg}) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<CommentBox onSubmit={this.onSubmit} />
|
||||
@@ -48,6 +48,7 @@ class CommentStream extends React.Component {
|
||||
singleView={false}
|
||||
commentIds={comments.get('ids')}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
onClickAction={this.onClickAction}
|
||||
actions={['flag']}
|
||||
loading={comments.loading} />
|
||||
@@ -57,4 +58,4 @@ class CommentStream extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({comments}) => ({comments}))(CommentStream);
|
||||
export default connect(({comments, users}) => ({comments, users}))(CommentStream);
|
||||
|
||||
@@ -61,7 +61,7 @@ class ModerationQueue extends React.Component {
|
||||
|
||||
// Render the tabbed lists moderation queues
|
||||
render () {
|
||||
const {comments} = this.props;
|
||||
const {comments, users} = this.props;
|
||||
const {activeTab, singleView, modalOpen} = this.state;
|
||||
|
||||
return (
|
||||
@@ -82,10 +82,11 @@ class ModerationQueue extends React.Component {
|
||||
commentIds={
|
||||
comments.get('ids')
|
||||
.filter(id => !comments.get('byId')
|
||||
.get(id)
|
||||
.get('status'))
|
||||
.get(id)
|
||||
.get('status'))
|
||||
}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
onClickAction={(action, id) => this.onCommentAction(action, id)}
|
||||
actions={['reject', 'approve']}
|
||||
loading={comments.loading} />
|
||||
@@ -104,6 +105,7 @@ class ModerationQueue extends React.Component {
|
||||
.get('status') === 'rejected')
|
||||
}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
onClickAction={(action, id) => this.onCommentAction(action, id)}
|
||||
actions={['approve']}
|
||||
loading={comments.loading} />
|
||||
@@ -117,6 +119,7 @@ class ModerationQueue extends React.Component {
|
||||
return !data.get('status') && data.get('flagged') === true;
|
||||
})}
|
||||
comments={comments.get('byId')}
|
||||
users={users.get('byId')}
|
||||
onClickAction={(action, id) => this.onCommentAction(action, id)}
|
||||
actions={['reject', 'approve']}
|
||||
loading={comments.loading} />
|
||||
@@ -129,6 +132,6 @@ class ModerationQueue extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({comments}) => ({comments}))(ModerationQueue);
|
||||
export default connect(({comments, users}) => ({comments, users}))(ModerationQueue);
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
export const base = '/api/v1';
|
||||
|
||||
export const getInit = (method, body) => {
|
||||
let init = {
|
||||
method,
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}),
|
||||
credentials: 'same-origin'
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import {combineReducers} from 'redux';
|
||||
import comments from 'reducers/comments';
|
||||
import settings from 'reducers/settings';
|
||||
import community from 'reducers/community';
|
||||
import users from 'reducers/users';
|
||||
import auth from 'reducers/auth';
|
||||
|
||||
// Combine all reducers into a main one
|
||||
@@ -9,6 +10,6 @@ export default combineReducers({
|
||||
settings,
|
||||
comments,
|
||||
community,
|
||||
auth
|
||||
auth,
|
||||
users
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import {Map, List, fromJS} from 'immutable';
|
||||
|
||||
const initialState = Map({
|
||||
byId: Map(),
|
||||
ids: List()
|
||||
});
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'USERS_MODERATION_QUEUE_FETCH_SUCCESS': return replaceUsers(action, state);
|
||||
default: return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Replace the comment list with a new one
|
||||
const replaceUsers = (action, state) => {
|
||||
const users = fromJS(action.users.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {}));
|
||||
return state.set('byId', users)
|
||||
.set('ids', List(users.keys()));
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import {base, handleResp, getInit} from '../../../coral-framework/helpers/response';
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
|
||||
/**
|
||||
* The adapter is a redux middleware that interecepts the actions that need
|
||||
@@ -15,9 +15,6 @@ export default store => next => action => {
|
||||
case 'COMMENTS_MODERATION_QUEUE_FETCH':
|
||||
fetchModerationQueueComments(store);
|
||||
break;
|
||||
// case 'COMMENT_STREAM_FETCH':
|
||||
// fetchCommentStream(store);
|
||||
// break;
|
||||
case 'COMMENT_UPDATE':
|
||||
updateComment(store, action.comment);
|
||||
break;
|
||||
@@ -33,24 +30,41 @@ export default store => next => action => {
|
||||
const fetchModerationQueueComments = store =>
|
||||
|
||||
Promise.all([
|
||||
fetch(`${base}/queue/comments/pending`, getInit('GET')),
|
||||
fetch(`${base}/comments?status=rejected`, getInit('GET')),
|
||||
fetch(`${base}/comments?action_type=flag`, getInit('GET'))
|
||||
coralApi('/queue/comments/pending'),
|
||||
coralApi('/comments?status=rejected'),
|
||||
coralApi('/comments?action_type=flag')
|
||||
])
|
||||
.then(res => Promise.all(res.map(handleResp)))
|
||||
.then(res => {
|
||||
res[2] = res[2].map(comment => { comment.flagged = true; return comment; });
|
||||
return res.reduce((prev, curr) => prev.concat(curr), []);
|
||||
.then(([pending, rejected, flagged]) => {
|
||||
/* Combine seperate calls into a single object */
|
||||
let all = {};
|
||||
all.comments = pending.comments
|
||||
.concat(rejected.comments)
|
||||
.concat(flagged.comments.map(comment => {
|
||||
comment.flagged = true;
|
||||
return comment;
|
||||
}));
|
||||
all.users = pending.users
|
||||
.concat(rejected.users)
|
||||
.concat(flagged.users);
|
||||
all.actions = pending.actions
|
||||
.concat(rejected.actions)
|
||||
.concat(flagged.actions);
|
||||
return all;
|
||||
})
|
||||
.then(res => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS',
|
||||
comments: res}))
|
||||
.catch(error => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_FAILED', error}));
|
||||
.then(all => {
|
||||
/* Post comments and users to redux store. Actions will be posted when they are needed. */
|
||||
store.dispatch({type: 'USERS_MODERATION_QUEUE_FETCH_SUCCESS',
|
||||
users: all.users});
|
||||
store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS',
|
||||
comments: all.comments});
|
||||
|
||||
});
|
||||
// .catch(error => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_FAILED', error}));
|
||||
|
||||
// Update a comment. Now to update a comment we need to send back the whole object
|
||||
|
||||
const updateComment = (store, comment) => {
|
||||
fetch(`${base}/comments/${comment.get('id')}/status`, getInit('PUT', {status: comment.get('status')}))
|
||||
.then(handleResp)
|
||||
coralApi(`/comments/${comment.get('id')}/status`, {method: 'PUT', body: {status: comment.get('status')}})
|
||||
.then(res => store.dispatch({type: 'COMMENT_UPDATE_SUCCESS', res}))
|
||||
.catch(error => store.dispatch({type: 'COMMENT_UPDATE_FAILED', error}));
|
||||
};
|
||||
@@ -63,8 +77,7 @@ const createComment = (store, name, comment) => {
|
||||
name: name,
|
||||
createdAt: Date.now()
|
||||
};
|
||||
return fetch(`${base}/comments`, getInit('POST', body))
|
||||
.then(handleResp)
|
||||
return coralApi('/comments', {method: 'POST', body})
|
||||
.then(res => store.dispatch({type: 'COMMENT_CREATE_SUCCESS', comment: res}))
|
||||
.catch(error => store.dispatch({type: 'COMMENT_CREATE_FAILED', error}));
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut lobortis sollicitudin eros a ornare. Curabitur dignissim vestibulum massa non rhoncus. Cras laoreet ante vel nunc hendrerit, ac imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Pellentesque interdum nec elit sed tincidunt. Donec volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non enim nec neque congue faucibus porttitor sit amet dui.</p>
|
||||
<p>Nunc pharetra orci id diam feugiat, vitae rutrum magna efficitur. Morbi porttitor blandit lorem, et facilisis tellus luctus at. Morbi tincidunt eget nisl id placerat. Nullam consectetur quam vel mauris lacinia, non consectetur est faucibus. Duis cursus auctor nulla nec sagittis. Aenean sem erat, ultrices a hendrerit consectetur, accumsan non lorem. Integer ac neque sed magna sodales vulputate at quis neque. Praesent eget ornare lacus. Donec ultricies, dolor eget commodo faucibus, arcu velit ullamcorper tellus, in cursus tellus elit sed urna. Suspendisse in consequat magna. Duis vel ullamcorper tortor, vel cursus libero. Proin et nisi luctus ligula faucibus luctus. Morbi pulvinar, justo ac feugiat elementum, libero tellus congue justo, pharetra ultrices felis felis id leo. Integer mattis quam tempus libero porta, ac pretium ligula elementum.</p>
|
||||
<div id='coralStreamEmbed'></div>
|
||||
<script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script>
|
||||
<script type='text/javascript' src='/client/js/lib/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', 'index.html', {title: 'comments'});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'})</script>
|
||||
|
||||
@@ -77,13 +77,32 @@ class CommentStream extends Component {
|
||||
|
||||
componentDidMount () {
|
||||
// Set up messaging between embedded Iframe an parent component
|
||||
const pym = new Pym.Child({polling: 100});
|
||||
this.pym = new Pym.Child({polling: 100});
|
||||
|
||||
if (/https?\:\/\/([^?]+)/.test(pym.parentUrl)) {
|
||||
this.props.getStream(pym.parentUrl);
|
||||
} else {
|
||||
this.props.getStream(window.location);
|
||||
}
|
||||
const path = this.pym.parentUrl.split('#')[0];
|
||||
|
||||
this.props.getStream(path || window.location);
|
||||
this.path = path;
|
||||
|
||||
this.pym.sendMessage('childReady');
|
||||
|
||||
this.pym.onMessage('DOMContentLoaded', hash => {
|
||||
// the comment ids can start with numbers, which is invalid for DOM id attributes
|
||||
const commentId = hash.replace('#', 'c_');
|
||||
let count = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (document.getElementById(commentId)) {
|
||||
window.clearInterval(interval);
|
||||
this.pym.scrollParentToChildEl(commentId);
|
||||
}
|
||||
|
||||
if (++count > 100) { // ~10 seconds
|
||||
// give up waiting for the comments to load.
|
||||
// it would be weird for the page to jump after that long.
|
||||
window.clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -92,11 +111,11 @@ class CommentStream extends Component {
|
||||
const {actions, users, comments} = this.props.items;
|
||||
const {loggedIn, user, showSignInDialog} = this.props.auth;
|
||||
const {activeTab} = this.state;
|
||||
|
||||
return <div className={showSignInDialog ? 'expandForSignin' : ''}>
|
||||
{
|
||||
rootItem
|
||||
? <div>
|
||||
|
||||
? <div className="commentStream">
|
||||
<TabBar onChange={this.changeTab} activeTab={activeTab}>
|
||||
<Tab><Count id={rootItemId} items={this.props.items}/></Tab>
|
||||
<Tab>Settings</Tab>
|
||||
@@ -124,7 +143,7 @@ class CommentStream extends Component {
|
||||
{
|
||||
rootItem.comments && rootItem.comments.map((commentId) => {
|
||||
const comment = comments[commentId];
|
||||
return <div className="comment" key={commentId}>
|
||||
return <div className="comment" key={commentId} id={`c_${commentId}`}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName author={users[comment.author_id]}/>
|
||||
<PubDate created_at={comment.created_at}/>
|
||||
@@ -155,8 +174,8 @@ class CommentStream extends Component {
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
comment_id={commentId}
|
||||
asset_id={comment.asset_id}/>
|
||||
commentId={commentId}
|
||||
articleURL={this.path}/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
@@ -172,7 +191,7 @@ class CommentStream extends Component {
|
||||
comment.children &&
|
||||
comment.children.map((replyId) => {
|
||||
let reply = this.props.items.comments[replyId];
|
||||
return <div className="reply" key={replyId}>
|
||||
return <div className="reply" key={replyId} id={`c_${replyId}`}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName author={users[reply.author_id]}/>
|
||||
<PubDate created_at={reply.created_at}/>
|
||||
@@ -203,8 +222,8 @@ class CommentStream extends Component {
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
comment_id={reply.parent_id}
|
||||
asset_id={rootItemId}
|
||||
commentId={reply.parent_id}
|
||||
articleURL={this.path}
|
||||
/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
|
||||
@@ -71,6 +71,11 @@ hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.commentStream {
|
||||
/* prevent absolutely positioned final permalink popover from being clipped */
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
/* Comment Box Styles */
|
||||
.coral-plugin-commentbox-container {
|
||||
display: flex;
|
||||
@@ -106,6 +111,7 @@ hr {
|
||||
/* Comment styles */
|
||||
.comment {
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.coral-plugin-commentcontent-text {
|
||||
@@ -139,9 +145,8 @@ hr {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.commentActionsLeft .material-icons,.commentActionsRight .material-icons,
|
||||
.replyActionsLeft .material-icons, .replyActionsRight .material-icons {
|
||||
font-size: 12px;
|
||||
.material-icons {
|
||||
font-size: 12px !important;
|
||||
margin-left: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -158,3 +163,33 @@ hr {
|
||||
color: #CCC;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-container {
|
||||
/*position: relative;*/
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-popover {
|
||||
display: none;
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
width: calc(100% - 15px);
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
right: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-popover.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-copy-field {
|
||||
display: block;
|
||||
width: calc(100% - 5px);
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-copied-text {
|
||||
float: right;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from './../translations';
|
||||
const lang = new I18n(translations);
|
||||
import * as actions from '../constants/auth';
|
||||
import {base, handleResp, getInit} from '../helpers/response';
|
||||
import coralApi, {base} from '../helpers/response';
|
||||
import {addItem} from './items';
|
||||
|
||||
// Dialog Actions
|
||||
@@ -25,8 +25,7 @@ const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error});
|
||||
|
||||
export const fetchSignIn = (formData) => dispatch => {
|
||||
dispatch(signInRequest());
|
||||
fetch(`${base}/auth/local`, getInit('POST', formData))
|
||||
.then(handleResp)
|
||||
coralApi('/auth/local', {method: 'POST', body: formData})
|
||||
.then(({user}) => {
|
||||
dispatch(hideSignInDialog());
|
||||
dispatch(signInSuccess(user));
|
||||
@@ -74,8 +73,7 @@ const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error});
|
||||
|
||||
export const fetchSignUp = formData => dispatch => {
|
||||
dispatch(signUpRequest());
|
||||
fetch(`${base}/user`, getInit('POST', formData))
|
||||
.then(handleResp)
|
||||
coralApi('/user', {method: 'POST', body: formData})
|
||||
.then(({user}) => {
|
||||
dispatch(signUpSuccess(user));
|
||||
setTimeout(() =>{
|
||||
@@ -93,8 +91,7 @@ const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILU
|
||||
|
||||
export const fetchForgotPassword = email => dispatch => {
|
||||
dispatch(forgotPassowordRequest(email));
|
||||
fetch(`${base}/user/request-password-reset`, getInit('POST', {email}))
|
||||
.then(handleResp)
|
||||
coralApi('/user/request-password-reset', {method: 'POST', body: {email}})
|
||||
.then(() => dispatch(forgotPassowordSuccess()))
|
||||
.catch(error => dispatch(forgotPassowordFailure(error)));
|
||||
};
|
||||
@@ -107,8 +104,7 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE});
|
||||
|
||||
export const logout = () => dispatch => {
|
||||
dispatch(logOutRequest());
|
||||
fetch(`${base}/auth`, getInit('DELETE'))
|
||||
.then(handleResp)
|
||||
coralApi('/auth', {method: 'DELETE'})
|
||||
.then(() => dispatch(logOutSuccess()))
|
||||
.catch(error => dispatch(logOutFailure(error)));
|
||||
};
|
||||
@@ -126,14 +122,13 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
|
||||
|
||||
export const checkLogin = () => dispatch => {
|
||||
dispatch(checkLoginRequest());
|
||||
fetch(`${base}/auth`, getInit('GET'))
|
||||
.then((res) => {
|
||||
if (res.status !== 200) {
|
||||
coralApi('/auth')
|
||||
.then(user => {
|
||||
if (!user) {
|
||||
throw new Error('not logged in');
|
||||
}
|
||||
|
||||
return res.json();
|
||||
dispatch(checkLoginSuccess(user));
|
||||
})
|
||||
.then(user => dispatch(checkLoginSuccess(user)))
|
||||
.catch(error => dispatch(checkLoginFailure(error)));
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {getInit, base, handleResp} from '../helpers/response';
|
||||
import coralApi from '../helpers/response';
|
||||
import {fromJS} from 'immutable';
|
||||
/* Item Actions */
|
||||
|
||||
@@ -95,8 +95,7 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) =>
|
||||
*/
|
||||
export function getStream (assetUrl) {
|
||||
return (dispatch) => {
|
||||
return fetch(`${base}/stream?asset_url=${encodeURIComponent(assetUrl)}`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
return coralApi(`/stream?asset_url=${encodeURIComponent(assetUrl)}`)
|
||||
.then((json) => {
|
||||
|
||||
/* Add items to the store */
|
||||
@@ -166,8 +165,7 @@ export function getStream (assetUrl) {
|
||||
|
||||
export function getItemsArray (ids) {
|
||||
return (dispatch) => {
|
||||
return fetch(`${base}/item/${ids}`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
return coralApi(`/item/${ids}`)
|
||||
.then((json) => {
|
||||
for (let i = 0; i < json.items.length; i++) {
|
||||
dispatch(addItem(json.items[i]));
|
||||
@@ -196,8 +194,7 @@ export function postItem (item, type, id) {
|
||||
if (id) {
|
||||
item.id = id;
|
||||
}
|
||||
return fetch(`${base}/${type}`, getInit('POST', item))
|
||||
.then(handleResp)
|
||||
return coralApi(`/${type}`, {method: 'POST', body: item})
|
||||
.then((json) => {
|
||||
dispatch(addItem({...item, id:json.id}, type));
|
||||
return json.id;
|
||||
@@ -227,8 +224,7 @@ export function postAction (item_id, action_type, user_id, item_type) {
|
||||
user_id
|
||||
};
|
||||
|
||||
return fetch(`${base}/${item_type}/${item_id}/actions`, getInit('POST', action))
|
||||
.then(handleResp);
|
||||
return coralApi(`/${item_type}/${item_id}/actions`, {method: 'POST', body: action});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -249,7 +245,6 @@ export function postAction (item_id, action_type, user_id, item_type) {
|
||||
|
||||
export function deleteAction (action_id) {
|
||||
return () => {
|
||||
return fetch(`${base}/actions/${action_id}`, {method: 'DELETE'})
|
||||
.then(handleResp);
|
||||
return coralApi(`/actions/${action_id}`, {method: 'DELETE'});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
export const base = '/api/v1';
|
||||
|
||||
export const getInit = (method, body) => {
|
||||
let init = {
|
||||
method,
|
||||
const buildOptions = (inputOptions = {}) => {
|
||||
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
};
|
||||
const options = Object.assign({}, defaultOptions, inputOptions);
|
||||
|
||||
if (method.toLowerCase() !== 'get') {
|
||||
init.body = JSON.stringify(body);
|
||||
if (options.method.toLowerCase() !== 'get') {
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
return init;
|
||||
return options;
|
||||
};
|
||||
|
||||
export const handleResp = res => {
|
||||
const handleResp = res => {
|
||||
if (res.status === 401) {
|
||||
throw new Error('Not Authorized to make this request');
|
||||
} else if (res.status > 399) {
|
||||
@@ -28,3 +30,7 @@ export const handleResp = res => {
|
||||
return res.json();
|
||||
}
|
||||
};
|
||||
|
||||
export default (url, options) => {
|
||||
return fetch(`${base}${url}`, buildOptions(options)).then(handleResp);
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ const lang = new I18n(translations);
|
||||
class PermalinkButton extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
asset_id: PropTypes.string.isRequired,
|
||||
comment_id: PropTypes.string.isRequired
|
||||
articleURL: PropTypes.string.isRequired,
|
||||
commentId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
@@ -43,29 +43,27 @@ class PermalinkButton extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const publisherUrl = `${location.protocol}//${location.host}/`;
|
||||
|
||||
return (
|
||||
<div className={`${name}-container`} style={styles}>
|
||||
<div className={`${name}-container`}>
|
||||
<button onClick={this.toggle} className={`${name}-button`}>
|
||||
<i className={`${name}-icon material-icons`} aria-hidden={true}>link</i>
|
||||
{lang.t('permalink.permalink')}
|
||||
</button>
|
||||
<div
|
||||
style={styles.popover(this.state.popoverOpen)}
|
||||
className={`${name}-popover`}>
|
||||
className={`${name}-popover ${this.state.popoverOpen ? 'active' : ''}`}>
|
||||
<input
|
||||
className={`${name}-copy-field`}
|
||||
type='text'
|
||||
ref={input => this.permalinkInput = input}
|
||||
value={`${publisherUrl}${this.props.asset_id}#${this.props.comment_id}`}
|
||||
value={`${this.props.articleURL}#${this.props.commentId}`}
|
||||
onChange={() => {}} />
|
||||
<button className={`${name}-copy-button`} onClick={this.copyPermalink}>Copy</button>
|
||||
{
|
||||
this.state.copySuccessful ? <p>copied to clipboard</p> : null
|
||||
this.state.copySuccessful ? <p className={`${name}-copied-text`}>copied to clipboard</p> : null
|
||||
}
|
||||
{
|
||||
this.state.copyFailure
|
||||
? <p>copying to clipboard not supported in this browser. Use Cmd + C.</p>
|
||||
? <p className={`${name}-copied-error`}>copying to clipboard not supported in this browser. Use Cmd + C.</p>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
@@ -75,20 +73,3 @@ class PermalinkButton extends React.Component {
|
||||
}
|
||||
|
||||
export default onClickOutside(PermalinkButton);
|
||||
|
||||
const styles = {
|
||||
position: 'relative',
|
||||
|
||||
popover: active => {
|
||||
return {
|
||||
display: active ? 'block' : 'none',
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid black',
|
||||
minWidth: 400,
|
||||
position: 'absolute',
|
||||
top: 30,
|
||||
right: 0,
|
||||
padding: 5
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ class ForgotContent extends React.Component {
|
||||
}
|
||||
{
|
||||
passwordRequestFailure
|
||||
? <p className={styles.attention}>{passwordRequestFailure}</p>
|
||||
? <p className={styles.passwordRequestFailure}>{passwordRequestFailure}</p>
|
||||
: null
|
||||
}
|
||||
</form>
|
||||
|
||||
@@ -138,5 +138,6 @@ input.error{
|
||||
|
||||
.passwordRequestFailure {
|
||||
border: 1px solid orange;
|
||||
background-color: 1px solid coral
|
||||
background-color: 1px solid coral;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -42,6 +42,7 @@ SettingSchema.statics.getSettings = function () {
|
||||
* @return {Promise} moderation the settings for how to moderate comments
|
||||
*/
|
||||
SettingSchema.statics.getModerationSetting = function () {
|
||||
console.log('Getting moderation setting');
|
||||
return this.findOne({id: '1'}).select('moderation');
|
||||
};
|
||||
|
||||
|
||||
+1
-5
@@ -1,11 +1,7 @@
|
||||
const mongoose = require('mongoose');
|
||||
const debug = require('debug')('talk:db');
|
||||
const enabled = require('debug').enabled;
|
||||
let url = process.env.TALK_MONGO_URL || 'mongodb://localhost';
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
url = 'mongodb://localhost/coral-test';
|
||||
}
|
||||
const url = process.env.TALK_MONGO_URL || (process.env.NODE_ENV === 'test' ? 'mongodb://localhost/test' : 'mongodb://localhost/talk');
|
||||
|
||||
// Use native promises
|
||||
mongoose.Promise = global.Promise;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const express = require('express');
|
||||
const Comment = require('../../../models/comment');
|
||||
const User = require('../../../models/user');
|
||||
const Action = require('../../../models/action');
|
||||
const wordlist = require('../../../services/wordlist');
|
||||
const authorization = require('../../../middleware/authorization');
|
||||
const _ = require('lodash');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -16,9 +19,22 @@ router.get('/', authorization.needed('admin'), (req, res, next) => {
|
||||
query = Comment.all();
|
||||
}
|
||||
|
||||
query.then(comments => {
|
||||
res.json(comments);
|
||||
query.then((comments) => {
|
||||
return Promise.all([
|
||||
comments,
|
||||
User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))),
|
||||
Action.getActionSummaries(_.uniq([
|
||||
...comments.map((comment) => comment.id),
|
||||
...comments.map((comment) => comment.author_id)
|
||||
]))
|
||||
]);
|
||||
})
|
||||
.then(([comments, users, actions])=>
|
||||
res.status(200).json({
|
||||
comments,
|
||||
users,
|
||||
actions
|
||||
}))
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
const express = require('express');
|
||||
const Comment = require('../../../models/comment');
|
||||
const User = require('../../../models/user');
|
||||
const Action = require('../../../models/action');
|
||||
const Setting = require('../../../models/setting');
|
||||
const _ = require('lodash');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -13,11 +16,24 @@ const router = express.Router();
|
||||
// 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('/comments/pending', (req, res, next) => {
|
||||
Setting.getModerationSetting().then(function({moderation}){
|
||||
Comment.moderationQueue(moderation).then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
});
|
||||
Setting.getModerationSetting().then(({moderation}) =>
|
||||
Comment.moderationQueue(moderation))
|
||||
.then((comments) => {
|
||||
return Promise.all([
|
||||
comments,
|
||||
User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))),
|
||||
Action.getActionSummaries(_.uniq([
|
||||
...comments.map((comment) => comment.id),
|
||||
...comments.map((comment) => comment.author_id)
|
||||
]))
|
||||
]);
|
||||
})
|
||||
.then(([comments, users, actions])=>
|
||||
res.status(200).json({
|
||||
comments,
|
||||
users,
|
||||
actions
|
||||
}))
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
|
||||
@@ -34,13 +34,13 @@ router.get('/', (req, res, next) => {
|
||||
// Merge the asset specific settings with the returned settings object in
|
||||
// the event that the asset that was returned also had settings.
|
||||
if (asset.settings) {
|
||||
settings = Object.assign(settings, asset.settings);
|
||||
settings = Object.assign({}, settings, asset.settings);
|
||||
}
|
||||
|
||||
// Fetch the appropriate comments stream.
|
||||
let comments;
|
||||
|
||||
if (settings.moderation === 'post') {
|
||||
if (settings.moderation === 'pre') {
|
||||
comments = Comment.findAcceptedByAssetId(asset.id);
|
||||
} else {
|
||||
comments = Comment.findAcceptedAndNewByAssetId(asset.id);
|
||||
|
||||
+8
-2
@@ -6,11 +6,17 @@ router.use('/admin', require('./admin'));
|
||||
router.use('/embed', require('./embed'));
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.render('article', {title: 'Coral Talk'});
|
||||
return res.render('article', {
|
||||
title: 'Coral Talk',
|
||||
basePath: '/client/embed/stream'
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/assets/:asset_title', (req, res) => {
|
||||
res.render('article', {title: req.params.asset_title.split('-').join(' ')});
|
||||
return res.render('article', {
|
||||
title: req.params.asset_title.split('-').join(' '),
|
||||
basePath: '/client/embed/stream'
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('/api/v1/comments', () => {
|
||||
id: 'hij',
|
||||
body: 'comment 30',
|
||||
asset_id: '456',
|
||||
author_id: '456',
|
||||
status: 'accepted'
|
||||
}];
|
||||
|
||||
@@ -90,7 +91,7 @@ describe('/api/v1/comments', () => {
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'def-rejected');
|
||||
expect(res.body.comments[0]).to.have.property('id', 'def-rejected');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,8 +101,8 @@ describe('/api/v1/comments', () => {
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body).to.have.length(1);
|
||||
expect(res.body[0]).to.have.property('id', 'hij');
|
||||
expect(res.body.comments).to.have.length(1);
|
||||
expect(res.body.comments[0]).to.have.property('id', 'hij');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -111,7 +112,7 @@ describe('/api/v1/comments', () => {
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body).to.have.length(2);
|
||||
expect(res.body.comments).to.have.length(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,8 +123,8 @@ describe('/api/v1/comments', () => {
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
|
||||
expect(res.body).to.have.length(1);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
expect(res.body.comments).to.have.length(1);
|
||||
expect(res.body.comments[0]).to.have.property('id', 'abc');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,63 +15,81 @@ const User = require('../../../../models/user');
|
||||
const Setting = require('../../../../models/setting');
|
||||
const settings = {id: '1', moderation: 'pre'};
|
||||
|
||||
describe('/api/v1/queue', () => {
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
asset_id: 'asset',
|
||||
author_id: '123',
|
||||
status: 'rejected'
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
asset_id: 'asset',
|
||||
author_id: '456'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 30',
|
||||
asset_id: '456',
|
||||
status: 'accepted'
|
||||
}];
|
||||
beforeEach(() => {
|
||||
return Setting.create(settings);
|
||||
});
|
||||
|
||||
const users = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
describe('Get moderation queues rejected, pending, flags', () => {
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc',
|
||||
item_type: 'comment'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij',
|
||||
item_type: 'comment'
|
||||
}];
|
||||
describe('/api/v1/queue', () => {
|
||||
let comments;
|
||||
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions),
|
||||
Setting.create(settings)
|
||||
]);
|
||||
});
|
||||
const users = [{
|
||||
id: '456',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '123',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
describe('#get', () => {
|
||||
it('should return all the pending comments', function(done){
|
||||
let actions;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
asset_id: 'asset',
|
||||
status: 'rejected'
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
asset_id: 'asset'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 30',
|
||||
asset_id: '456',
|
||||
status: 'accepted'
|
||||
}];
|
||||
|
||||
actions = [{
|
||||
action_type: 'flag',
|
||||
item_type: 'comment'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_type: 'comment'
|
||||
}];
|
||||
|
||||
return User.createLocalUsers(users)
|
||||
.then((u) => {
|
||||
comments[0].author_id = u[0].id;
|
||||
comments[1].author_id = u[1].id;
|
||||
comments[2].author_id = u[1].id;
|
||||
|
||||
return Comment.create(comments);
|
||||
})
|
||||
.then((c) => {
|
||||
actions[0].item_id = c[0].id;
|
||||
actions[1].item_id = c[1].id;
|
||||
|
||||
return Action.create(actions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the pending comments, users and actions', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/queue/comments/pending')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'def');
|
||||
expect(res.body.comments[0]).to.have.property('body');
|
||||
expect(res.body.users[0]).to.have.property('displayName');
|
||||
expect(res.body.actions[0]).to.have.property('action_type');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,90 +14,90 @@ const Asset = require('../../../../models/asset');
|
||||
const Setting = require('../../../../models/setting');
|
||||
|
||||
describe('/api/v1/stream', () => {
|
||||
describe('#get', () => {
|
||||
const settings = {
|
||||
id: '1',
|
||||
moderation: 'post'
|
||||
};
|
||||
|
||||
const settings = {
|
||||
id: '1',
|
||||
moderation: 'pre'
|
||||
};
|
||||
let comments;
|
||||
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
author_id: '',
|
||||
parent_id: '',
|
||||
status: 'accepted'
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
author_id: '',
|
||||
parent_id: '',
|
||||
status: ''
|
||||
}, {
|
||||
id: 'uio',
|
||||
body: 'comment 30',
|
||||
asset_id: 'asset',
|
||||
author_id: '456',
|
||||
parent_id: '',
|
||||
status: 'accepted'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 40',
|
||||
asset_id: '456',
|
||||
status: 'rejected'
|
||||
}];
|
||||
const users = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij'
|
||||
}];
|
||||
beforeEach(() => {
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
return Promise.all([
|
||||
User.createLocalUsers(users),
|
||||
Asset.findOrCreateByUrl('http://test.com'),
|
||||
Asset
|
||||
.findOrCreateByUrl('http://coralproject.net/asset2')
|
||||
.then((asset) => {
|
||||
return Asset
|
||||
.overrideSettings(asset.id, {moderation: 'post'})
|
||||
.then(() => asset);
|
||||
})
|
||||
])
|
||||
.then(([users, asset1, asset2]) => {
|
||||
|
||||
comments[0].author_id = users[0].id;
|
||||
comments[1].author_id = users[1].id;
|
||||
comments[2].author_id = users[0].id;
|
||||
comments[3].author_id = users[1].id;
|
||||
|
||||
comments[0].asset_id = asset1.id;
|
||||
comments[1].asset_id = asset1.id;
|
||||
comments[2].asset_id = asset2.id;
|
||||
comments[3].asset_id = asset2.id;
|
||||
comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
author_id: '',
|
||||
parent_id: '',
|
||||
status: 'accepted'
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
author_id: '',
|
||||
parent_id: '',
|
||||
status: ''
|
||||
}, {
|
||||
id: 'uio',
|
||||
body: 'comment 30',
|
||||
asset_id: 'asset',
|
||||
author_id: '456',
|
||||
parent_id: '',
|
||||
status: 'accepted'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 40',
|
||||
asset_id: '456',
|
||||
status: 'rejected'
|
||||
}];
|
||||
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
Action.create(actions),
|
||||
Setting.create(settings)
|
||||
]);
|
||||
});
|
||||
});
|
||||
User.createLocalUsers(users),
|
||||
Asset.findOrCreateByUrl('http://test.com'),
|
||||
Asset
|
||||
.findOrCreateByUrl('http://coralproject.net/asset2')
|
||||
.then((asset) => {
|
||||
return Asset
|
||||
.overrideSettings(asset.id, {moderation: 'pre'})
|
||||
.then(() => asset);
|
||||
})
|
||||
])
|
||||
.then(([users, asset1, asset2]) => {
|
||||
|
||||
describe('#get', () => {
|
||||
comments[0].author_id = users[0].id;
|
||||
comments[1].author_id = users[1].id;
|
||||
comments[2].author_id = users[0].id;
|
||||
comments[3].author_id = users[1].id;
|
||||
|
||||
comments[0].asset_id = asset1.id;
|
||||
comments[1].asset_id = asset1.id;
|
||||
comments[2].asset_id = asset2.id;
|
||||
comments[3].asset_id = asset2.id;
|
||||
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
Action.create(actions),
|
||||
Setting.init().then(() => Setting.updateSettings(settings))
|
||||
]);
|
||||
});
|
||||
});
|
||||
it('should return a stream with comments, users and actions for an existing asset', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/stream')
|
||||
@@ -108,7 +108,7 @@ describe('/api/v1/stream', () => {
|
||||
expect(res.body.comments.length).to.equal(2);
|
||||
expect(res.body.users.length).to.equal(2);
|
||||
expect(res.body.actions.length).to.equal(1);
|
||||
expect(res.body.settings).to.have.property('moderation', 'pre');
|
||||
expect(res.body.settings).to.have.property('moderation', 'post');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('/api/v1/stream', () => {
|
||||
expect(res.body.assets.length).to.equal(1);
|
||||
expect(res.body.comments.length).to.equal(1);
|
||||
expect(res.body.users.length).to.equal(1);
|
||||
expect(res.body.settings).to.have.property('moderation', 'post');
|
||||
expect(res.body.settings).to.have.property('moderation', 'pre');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+20
-8
@@ -33,13 +33,25 @@
|
||||
<div id='coralStreamEmbed'></div>
|
||||
</main>
|
||||
|
||||
<!--- Script --->
|
||||
<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', id:'coralStreamIframe'});
|
||||
pymParent.onMessage('height', function(height) {
|
||||
document.querySelector('#coralStreamEmbed iframe').height = height + 'px';
|
||||
});
|
||||
</script>
|
||||
<script type='text/javascript' src='<%= basePath %>/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var ready = false;
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream', {title: 'Talk Comments', id:'coralStreamIframe'});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'})
|
||||
pymParent.onMessage('childReady', function () {
|
||||
var interval = setInterval(function () {
|
||||
if (ready) {
|
||||
window.clearInterval(interval);
|
||||
pymParent.sendMessage('DOMContentLoaded', window.location.hash);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// wait till images and other iframes are loaded before scrolling the page.
|
||||
// or do we want to be more aggressive and scroll when we hit DOM ready?
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
ready = true;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+19
-7
@@ -19,14 +19,26 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id='coralStreamEmbed'></div>
|
||||
|
||||
<!--- Script --->
|
||||
<script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script>
|
||||
<script type='text/javascript' src='<%= basePath %>/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';
|
||||
});
|
||||
var ready = false;
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream', {title: 'Talk Comments'});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'});
|
||||
pymParent.onMessage('childReady', function () {
|
||||
var interval = setInterval(function () {
|
||||
if (ready) {
|
||||
window.clearInterval(interval);
|
||||
pymParent.sendMessage('DOMContentLoaded', window.location.hash);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// wait till images and other iframes are loaded before scrolling the page.
|
||||
// or do we want to be more aggressive and scroll when we hit DOM ready?
|
||||
document.addEventListener('DOMContentLoaded', function (e) {
|
||||
ready = true;
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-4
@@ -74,10 +74,16 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Copy(buildEmbeds.map(embed => ({
|
||||
from: path.join(__dirname, 'client', `coral-embed-${embed}`, 'style'),
|
||||
to: path.join(__dirname, 'dist', 'embed', embed)
|
||||
}))),
|
||||
new Copy([
|
||||
...buildEmbeds.map(embed => ({
|
||||
from: path.join(__dirname, 'client', `coral-embed-${embed}`, 'style'),
|
||||
to: path.join(__dirname, 'dist', 'embed', embed)
|
||||
})),
|
||||
{
|
||||
from: path.join(__dirname, 'client', 'lib'),
|
||||
to: path.join(__dirname, 'dist', 'embed', 'stream')
|
||||
}
|
||||
]),
|
||||
autoprefixer,
|
||||
precss,
|
||||
new webpack.ProvidePlugin({
|
||||
|
||||
Reference in New Issue
Block a user