mirror of
https://github.com/wassname/talk.git
synced 2026-07-16 11:22:16 +08:00
Merge branch 'master' into ban-user
This commit is contained in:
@@ -24,10 +24,7 @@ export default function auth (state = initialState, action) {
|
||||
.set('isAdmin', action.isAdmin)
|
||||
.set('user', action.user);
|
||||
case actions.LOGOUT_SUCCESS:
|
||||
return state
|
||||
.set('loggedIn', false)
|
||||
.set('user', null)
|
||||
.set('isAdmin', false);
|
||||
return initialState;
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import Pym from 'pym.js';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {
|
||||
itemActions,
|
||||
Notification,
|
||||
notificationActions,
|
||||
authActions,
|
||||
} from '../../coral-framework';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import CommentBox from '../../coral-plugin-commentbox/CommentBox';
|
||||
import InfoBox from '../../coral-plugin-infobox/InfoBox';
|
||||
import Content from '../../coral-plugin-commentcontent/CommentContent';
|
||||
@@ -13,44 +16,20 @@ import PubDate from '../../coral-plugin-pubdate/PubDate';
|
||||
import Count from '../../coral-plugin-comment-count/CommentCount';
|
||||
import AuthorName from '../../coral-plugin-author-name/AuthorName';
|
||||
import {ReplyBox, ReplyButton} from '../../coral-plugin-replies';
|
||||
import Pym from 'pym.js';
|
||||
import FlagButton from '../../coral-plugin-flags/FlagButton';
|
||||
import LikeButton from '../../coral-plugin-likes/LikeButton';
|
||||
import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton';
|
||||
import SignInContainer from '../../coral-sign-in/containers/SignInContainer';
|
||||
import UserBox from '../../coral-sign-in/components/UserBox';
|
||||
import {TabBar, Tab, TabContent} from '../../coral-ui';
|
||||
import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui';
|
||||
import SettingsContainer from '../../coral-settings/containers/SettingsContainer';
|
||||
import RestrictedContent from '../../coral-framework/components/RestrictedContent';
|
||||
import SuspendedAccount from '../../coral-framework/components/SuspendedAccount';
|
||||
|
||||
const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions;
|
||||
const {addNotification, clearNotification} = notificationActions;
|
||||
const {logout} = authActions;
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
config: state.config.toJS(),
|
||||
items: state.items.toJS(),
|
||||
notification: state.notification.toJS(),
|
||||
auth: state.auth.toJS()
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
addItem: (item, itemType) => dispatch(addItem(item, itemType)),
|
||||
updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)),
|
||||
postItem: (data, type, id) => dispatch(postItem(data, type, id)),
|
||||
getStream: (rootId) => dispatch(getStream(rootId)),
|
||||
addNotification: (type, text) => dispatch(addNotification(type, text)),
|
||||
clearNotification: () => dispatch(clearNotification()),
|
||||
postAction: (item, action, user, itemType) => dispatch(postAction(item, action, user, itemType)),
|
||||
deleteAction: (item, action, user, itemType) => {
|
||||
return dispatch(deleteAction(item, action, user, itemType));
|
||||
},
|
||||
appendItemArray: (item, property, value, addToFront, itemType) =>
|
||||
dispatch(appendItemArray(item, property, value, addToFront, itemType)),
|
||||
logout: () => dispatch(logout()),
|
||||
});
|
||||
|
||||
class CommentStream extends Component {
|
||||
|
||||
constructor (props) {
|
||||
@@ -78,47 +57,22 @@ class CommentStream extends Component {
|
||||
componentDidMount () {
|
||||
// Set up messaging between embedded Iframe an parent component
|
||||
// Using recommended Pym init code which violates .eslint standards
|
||||
this.pym = new Pym.Child({polling: 100});
|
||||
const pym = new Pym.Child({polling: 100});
|
||||
|
||||
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);
|
||||
});
|
||||
if (/https?\:\/\/([^?]+)/.test(pym.parentUrl)) {
|
||||
this.props.getStream(pym.parentUrl);
|
||||
} else {
|
||||
this.props.getStream(window.location);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
if (Object.keys(this.props.items).length === 0) {
|
||||
// Loading mock asset
|
||||
// Loading mock asset
|
||||
this.props.postItem({
|
||||
comments: [],
|
||||
url: 'http://coralproject.net'
|
||||
}, 'asset', 'assetTest');
|
||||
|
||||
// Loading mock user
|
||||
//this.props.postItem({name: 'Ban Ki-Moon'}, 'user', 'user_8989')
|
||||
// .then((id) => {
|
||||
// this.props.setLoggedInUser(id);
|
||||
// });
|
||||
}
|
||||
|
||||
// TODO: Replace teststream id with id from params
|
||||
@@ -132,68 +86,69 @@ class CommentStream extends Component {
|
||||
return <div className={showSignInDialog ? 'expandForSignin' : ''}>
|
||||
{
|
||||
rootItem
|
||||
? <div className="commentStream">
|
||||
? <div className="commentStream">
|
||||
<TabBar onChange={this.changeTab} activeTab={activeTab}>
|
||||
<Tab><Count id={rootItemId} items={this.props.items}/></Tab>
|
||||
<Tab>Settings</Tab>
|
||||
</TabBar>
|
||||
|
||||
<TabContent show={activeTab === 0}>
|
||||
<div id="commentBox">
|
||||
<InfoBox
|
||||
content={this.props.config.infoBoxContent}
|
||||
enable={this.props.config.infoBoxEnable}
|
||||
/>
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} />}
|
||||
<CommentBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
premod={this.props.config.moderation}
|
||||
reply={false}
|
||||
author={user}
|
||||
/>
|
||||
{!loggedIn && <SignInContainer />}
|
||||
</div>
|
||||
{
|
||||
rootItem.comments && rootItem.comments.map((commentId) => {
|
||||
const comment = comments[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}/>
|
||||
<Content body={comment.body}/>
|
||||
<div className="commentActionsLeft">
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={commentId}
|
||||
showReply={comment.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
like={actions[comment.like]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
</div>
|
||||
<div className="commentActionsRight">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
flag={actions[comment.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} />}
|
||||
{/* Add to the restricted param a boolean if the user is suspended*/}
|
||||
<RestrictedContent restricted={false} restrictedComp={<SuspendedAccount />}>
|
||||
<TabContent show={activeTab === 0}>
|
||||
<div id="commentBox">
|
||||
<InfoBox
|
||||
content={this.props.config.infoBoxContent}
|
||||
enable={this.props.config.infoBoxEnable}
|
||||
/>
|
||||
<CommentBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
premod={this.props.config.moderation}
|
||||
reply={false}
|
||||
author={user}
|
||||
/>
|
||||
{!loggedIn && <SignInContainer />}
|
||||
</div>
|
||||
{
|
||||
rootItem.comments && rootItem.comments.map((commentId) => {
|
||||
const comment = comments[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}/>
|
||||
<Content body={comment.body}/>
|
||||
<div className="commentActionsLeft">
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={commentId}
|
||||
showReply={comment.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
like={actions[comment.like]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
</div>
|
||||
<div className="commentActionsRight">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
flag={actions[comment.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
commentId={commentId}
|
||||
articleURL={this.path}/>
|
||||
</div>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
@@ -214,66 +169,95 @@ class CommentStream extends Component {
|
||||
<PubDate created_at={reply.created_at}/>
|
||||
<Content body={reply.body}/>
|
||||
<div className="replyActionsLeft">
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={replyId}
|
||||
showReply={reply.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
like={this.props.items.actions[reply.like]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
</div>
|
||||
<div className="replyActionsRight">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
flag={this.props.items.actions[reply.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
commentId={reply.parent_id}
|
||||
articleURL={this.path}
|
||||
/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
child_id={replyId}
|
||||
premod={this.props.config.moderation}
|
||||
id={replyId}
|
||||
showReply={reply.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
like={this.props.items.actions[reply.like]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
</div>
|
||||
<div className="replyActionsRight">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
flag={this.props.items.actions[reply.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
commentId={reply.parent_id}
|
||||
articleURL={this.path}
|
||||
/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
child_id={replyId}
|
||||
premod={this.props.config.moderation}
|
||||
showReply={reply.showReply}/>
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<SettingsContainer/>
|
||||
</TabContent>
|
||||
}
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<SettingsContainer
|
||||
loggedIn={loggedIn}
|
||||
userData={this.props.userData}
|
||||
showSignInDialog={this.props.handleSignInDialog}
|
||||
/>
|
||||
{!loggedIn && <SignInContainer noButton/>}
|
||||
</TabContent>
|
||||
</RestrictedContent>
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}
|
||||
/>
|
||||
</div>
|
||||
: 'Loading'
|
||||
:
|
||||
<Spinner/>
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
config: state.config.toJS(),
|
||||
items: state.items.toJS(),
|
||||
notification: state.notification.toJS(),
|
||||
auth: state.auth.toJS(),
|
||||
userData: state.user.toJS()
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
addItem: (item, itemType) => dispatch(addItem(item, itemType)),
|
||||
updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)),
|
||||
postItem: (data, type, id) => dispatch(postItem(data, type, id)),
|
||||
getStream: (rootId) => dispatch(getStream(rootId)),
|
||||
addNotification: (type, text) => dispatch(addNotification(type, text)),
|
||||
clearNotification: () => dispatch(clearNotification()),
|
||||
postAction: (item, action, user, itemType) => dispatch(postAction(item, action, user, itemType)),
|
||||
deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)),
|
||||
appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)),
|
||||
handleSignInDialog: () => dispatch(authActions.showSignInDialog()),
|
||||
logout: () => dispatch(logout()),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentStream);
|
||||
|
||||
@@ -84,6 +84,7 @@ hr {
|
||||
.coral-plugin-commentbox-textarea {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.coral-plugin-commentbox-button-container {
|
||||
@@ -110,6 +111,7 @@ hr {
|
||||
|
||||
/* Comment styles */
|
||||
.comment {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as actions from '../constants/user';
|
||||
import {addNotification} from '../actions/notification';
|
||||
import coralApi from '../helpers/response';
|
||||
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from './../translations';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const saveBioRequest = () => ({type: actions.SAVE_BIO_REQUEST});
|
||||
const saveBioSuccess = settings => ({type: actions.SAVE_BIO_SUCCESS, settings});
|
||||
const saveBioFailure = error => ({type: actions.SAVE_BIO_FAILURE, error});
|
||||
|
||||
export const saveBio = (user_id, formData) => dispatch => {
|
||||
dispatch(saveBioRequest());
|
||||
coralApi(`/user/${user_id}/bio`, {method: 'PUT', body: formData})
|
||||
.then(({settings}) => {
|
||||
dispatch(addNotification('success', lang.t('successBioUpdate')));
|
||||
dispatch(saveBioSuccess(settings));
|
||||
})
|
||||
.catch(error => dispatch(saveBioFailure(error)));
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
.message {
|
||||
background: #D8D8D8;
|
||||
padding: 25px;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import styles from './RestrictedContent.css';
|
||||
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from 'coral-framework/translations.json';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
export default ({children, restricted, message = lang.t('contentNotAvailable'), restrictedComp}) => {
|
||||
if (restricted) {
|
||||
return restrictedComp ? restrictedComp : messageBox(message);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const messageBox = (message) => <div className={styles.message}>{message}</div>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from 'coral-framework/translations.json';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
export default () => (
|
||||
<span>{lang.t('suspendedAccountMsg')}</span>
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
export const SAVE_BIO_REQUEST = 'SAVE_BIO_REQUEST';
|
||||
export const SAVE_BIO_SUCCESS = 'SAVE_BIO_SUCCESS';
|
||||
export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE';
|
||||
@@ -13,6 +13,11 @@ const initialState = Map({
|
||||
successSignUp: false
|
||||
});
|
||||
|
||||
const purge = user => {
|
||||
const {settings, profiles, ...userData} = user; // eslint-disable-line
|
||||
return userData;
|
||||
};
|
||||
|
||||
export default function auth (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actions.SHOW_SIGNIN_DIALOG :
|
||||
@@ -44,11 +49,11 @@ export default function auth (state = initialState, action) {
|
||||
case actions.CHECK_LOGIN_SUCCESS:
|
||||
return state
|
||||
.set('loggedIn', true)
|
||||
.set('user', action.user);
|
||||
.set('user', purge(action.user));
|
||||
case actions.FETCH_SIGNIN_SUCCESS:
|
||||
return state
|
||||
.set('loggedIn', true)
|
||||
.set('user', action.user);
|
||||
.set('user', purge(action.user));
|
||||
case actions.FETCH_SIGNIN_FAILURE:
|
||||
return state
|
||||
.set('isLoading', false)
|
||||
@@ -56,7 +61,7 @@ export default function auth (state = initialState, action) {
|
||||
.set('user', null);
|
||||
case actions.FETCH_SIGNIN_FACEBOOK_SUCCESS:
|
||||
return state
|
||||
.set('user', action.user)
|
||||
.set('user', purge(action.user))
|
||||
.set('loggedIn', true);
|
||||
case actions.FETCH_SIGNIN_FACEBOOK_FAILURE:
|
||||
return state
|
||||
|
||||
@@ -5,6 +5,7 @@ import config from './config';
|
||||
import items from './items';
|
||||
import notification from './notification';
|
||||
import auth from './auth';
|
||||
import user from './user';
|
||||
|
||||
/**
|
||||
* Expose the combined main reducer
|
||||
@@ -15,4 +16,5 @@ export default combineReducers({
|
||||
items,
|
||||
notification,
|
||||
auth,
|
||||
user
|
||||
});
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import {Map} from 'immutable';
|
||||
import * as authActions from '../constants/auth';
|
||||
import * as actions from '../constants/user';
|
||||
|
||||
const initialState = Map({
|
||||
displayName: '',
|
||||
profiles: [],
|
||||
settings: {}
|
||||
});
|
||||
|
||||
const purge = user => {
|
||||
const {_id, created_at, updated_at, __v, roles, ...userData} = user; // eslint-disable-line
|
||||
return userData;
|
||||
};
|
||||
|
||||
export default function user (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case authActions.CHECK_LOGIN_SUCCESS:
|
||||
return state.merge(Map(purge(action.user)));
|
||||
case authActions.CHECK_LOGIN_FAILURE:
|
||||
return initialState;
|
||||
case authActions.FETCH_SIGNIN_SUCCESS:
|
||||
return state.merge(Map(purge(action.user)));
|
||||
case authActions.FETCH_SIGNIN_FAILURE:
|
||||
return initialState;
|
||||
case authActions.FETCH_SIGNIN_FACEBOOK_SUCCESS:
|
||||
return state.merge(Map(purge(action.user)));
|
||||
case authActions.FETCH_SIGNIN_FACEBOOK_FAILURE:
|
||||
return initialState;
|
||||
case actions.SAVE_BIO_SUCCESS:
|
||||
return state
|
||||
.set('settings', action.settings);
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"en": {
|
||||
"successBioUpdate": "Your Bio has been updated",
|
||||
"contentNotAvailable": "This content is not available",
|
||||
"suspendedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information",
|
||||
"error": {
|
||||
"email": "Not a valid E-Mail",
|
||||
"password": "Password must be at least 8 characters",
|
||||
@@ -10,6 +13,9 @@
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"successBioUpdate": "Tu bio fue actualizada",
|
||||
"contentNotAvailable": "El contenido no se encuentra disponible",
|
||||
"suspendedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information",
|
||||
"error": {
|
||||
"email": "No es un email válido",
|
||||
"password": "La contraseña debe tener por lo menos 8 caracteres",
|
||||
@@ -19,4 +25,4 @@
|
||||
"emailInUse": "Email address already in use"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,43 @@
|
||||
import React from 'react';
|
||||
import React, {Component} from 'react';
|
||||
import {Tooltip} from 'coral-ui';
|
||||
const packagename = 'coral-plugin-author-name';
|
||||
|
||||
const AuthorName = ({author}) =>
|
||||
<div className={`${packagename}-text`}>
|
||||
{author && author.displayName}
|
||||
</div>;
|
||||
export default class AuthorName extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
export default AuthorName;
|
||||
this.state = {
|
||||
showTooltip: false
|
||||
};
|
||||
|
||||
this.handleMouseOver = this.handleMouseOver.bind(this);
|
||||
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||
}
|
||||
|
||||
handleMouseOver () {
|
||||
this.setState({
|
||||
showTooltip: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseLeave () {
|
||||
this.setState({
|
||||
showTooltip: false
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const {author} = this.props;
|
||||
const {showTooltip} = this.state;
|
||||
return (
|
||||
<div
|
||||
className={`${packagename}-text`}
|
||||
onMouseOver={this.handleMouseOver}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
{author && author.displayName}
|
||||
{ showTooltip && <Tooltip>{author.settings.bio}</Tooltip>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {I18n} from '../coral-framework';
|
||||
import translations from './translations.json';
|
||||
import {Button} from 'coral-ui';
|
||||
|
||||
const name = 'coral-plugin-commentbox';
|
||||
|
||||
@@ -75,12 +76,11 @@ class CommentBox extends Component {
|
||||
</div>
|
||||
<div className={`${name}-button-container`}>
|
||||
{ author && (
|
||||
<button
|
||||
className={`${name}-button`}
|
||||
style={styles && styles.button}
|
||||
<Button
|
||||
cStyle='darkGrey'
|
||||
onClick={this.postComment}>
|
||||
{lang.t('post')}
|
||||
</button>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -2,15 +2,17 @@ import React from 'react';
|
||||
import styles from './Bio.css';
|
||||
import {Button} from '../../coral-ui';
|
||||
|
||||
export default () => (
|
||||
export default ({bio, handleSave, handleInput, handleCancel}) => (
|
||||
<div className={styles.bio}>
|
||||
<h1>Bio</h1>
|
||||
<p>Tell the community about yourself</p>
|
||||
<textarea />
|
||||
<div className={styles.actions}>
|
||||
<Button cStyle='cancel' raised>Cancel</Button>
|
||||
<Button cStyle='success'>Save Changes</Button>
|
||||
</div>
|
||||
<form>
|
||||
<textarea value={bio} onChange={handleInput} />
|
||||
<div className={styles.actions}>
|
||||
<Button cStyle='cancel' type="button" onClick={handleCancel} raised>Cancel</Button>
|
||||
<Button cStyle='success' type="submit" onClick={handleSave}>Save Changes</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
.message {
|
||||
padding: 10px 0 20px;
|
||||
letter-spacing: 0.1px;
|
||||
font-size: 13px;
|
||||
line-height: 33px;
|
||||
}
|
||||
|
||||
.message a {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom: solid 1px black;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import styles from './NotLoggedIn.css';
|
||||
|
||||
export default ({showSignInDialog}) => (
|
||||
<div className={styles.message}>
|
||||
<div>
|
||||
<a onClick={showSignInDialog}>Sign In</a> to access Settings
|
||||
</div>
|
||||
<div>
|
||||
From the Settings Page you can
|
||||
<ul>
|
||||
<li>See your comment history</li>
|
||||
<li>Write a bio about yourself to display to the community</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import styles from './SettingsHeader.css';
|
||||
|
||||
export default () => (
|
||||
export default ({userData}) => (
|
||||
<div className={styles.header}>
|
||||
<h1>Jackson</h1>
|
||||
<h2>jackson_persona@gmail.com</h2>
|
||||
<h1>{userData.displayName}</h1>
|
||||
<h2>{userData.profiles.map(profile => profile.id)}</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import React, {Component} from 'react';
|
||||
import Bio from '../components/Bio';
|
||||
|
||||
export default class BioContainer extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
bio: props.bio
|
||||
};
|
||||
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.handleInput = this.handleInput.bind(this);
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
}
|
||||
|
||||
handleInput(e) {
|
||||
this.setState({
|
||||
bio: e.target.value
|
||||
});
|
||||
}
|
||||
|
||||
handleSave (e) {
|
||||
e.preventDefault();
|
||||
const {userData, saveBio} = this.props;
|
||||
const {bio} = this.state;
|
||||
saveBio(userData.id, {bio});
|
||||
}
|
||||
|
||||
handleCancel () {
|
||||
this.setState({
|
||||
bio: this.props.bio
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
return <Bio
|
||||
bio={this.state.bio}
|
||||
userData={this.props.userData}
|
||||
handleSave={this.handleSave}
|
||||
handleInput={this.handleInput}
|
||||
handleCancel={this.handleCancel}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {TabBar, Tab} from '../../coral-ui';
|
||||
import {saveBio} from 'coral-framework/actions/user';
|
||||
|
||||
import Bio from '../components/Bio';
|
||||
import BioContainer from './BioContainer';
|
||||
import NotLoggedIn from '../components/NotLoggedIn';
|
||||
import {TabBar, Tab, TabContent} from '../../coral-ui';
|
||||
import CommentHistory from '../components/CommentHistory';
|
||||
import SettingsHeader from '../components/SettingsHeader';
|
||||
import RestrictedContent from 'coral-framework/components/RestrictedContent';
|
||||
|
||||
class SignInContainer extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeTab: 0
|
||||
activeTab: 0,
|
||||
};
|
||||
|
||||
this.handleTabChange = this.handleTabChange.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
// Get Bio
|
||||
// Fetch commentHistory
|
||||
}
|
||||
|
||||
@@ -29,18 +31,22 @@ class SignInContainer extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
//const {embedStream} = this.props;
|
||||
const {loggedIn, userData, showSignInDialog} = this.props;
|
||||
const {activeTab} = this.state;
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader />
|
||||
<RestrictedContent restricted={!loggedIn} restrictedComp={<NotLoggedIn showSignInDialog={showSignInDialog} />}>
|
||||
<SettingsHeader {...this.props} />
|
||||
<TabBar onChange={this.handleTabChange} activeTab={activeTab} cStyle='material'>
|
||||
<Tab>All Comments (120)</Tab>
|
||||
<Tab>Profile Settings</Tab>
|
||||
</TabBar>
|
||||
{ activeTab === 0 && <CommentHistory {...this.props}/> }
|
||||
{ activeTab === 1 && <Bio {...this.props}/> }
|
||||
</div>
|
||||
<TabContent show={activeTab === 0}>
|
||||
<CommentHistory {...this.props}/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<BioContainer bio={userData.settings.bio} handleSave={this.handleSave} {...this.props} />
|
||||
</TabContent>
|
||||
</RestrictedContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -49,10 +55,8 @@ const mapStateToProps = () => ({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
getBio: () => dispatch(),
|
||||
saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData)),
|
||||
getHistory: () => dispatch(),
|
||||
handleSaveChanges: () => dispatch(),
|
||||
handleCancel: () => dispatch()
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const UserBox = ({className, user, logout, ...props}) => (
|
||||
<div
|
||||
className={`${styles.userBox} ${className}`}
|
||||
className={`${styles.userBox} ${className ? className : ''}`}
|
||||
{...props}
|
||||
>
|
||||
Signed in as <a>{user.displayName}</a>. Not you? <a onClick={logout}>Sign out</a>
|
||||
{lang.t('signIn.loggedInAs')} <a>{user.displayName}</a>. {lang.t('signIn.notYou')} <a onClick={logout}>{lang.t('signIn.logout')}</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -103,10 +103,18 @@ input.error{
|
||||
color: #B71C1C;
|
||||
}
|
||||
|
||||
.userBox {
|
||||
padding: 10px 0 20px;
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
|
||||
.userBox a {
|
||||
color: #2c69b6;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom: solid 1px black;
|
||||
}
|
||||
|
||||
.attention {
|
||||
|
||||
@@ -129,12 +129,10 @@ class SignInContainer extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {auth, showSignInDialog} = this.props;
|
||||
const {auth, showSignInDialog, noButton} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={showSignInDialog} full>
|
||||
Sign in to comment
|
||||
</Button>
|
||||
{!noButton && <Button onClick={showSignInDialog} full> Sign in to comment</Button>}
|
||||
<SignDialog
|
||||
open={auth.showSignInDialog}
|
||||
view={auth.view}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export default {
|
||||
en: {
|
||||
'signIn': {
|
||||
notYou: 'Not you?',
|
||||
loggedInAs: 'Logged in as',
|
||||
facebookSignIn: 'Sign in with Facebook',
|
||||
facebookSignUp: 'Sign up with Facebook',
|
||||
logout: 'Logout',
|
||||
@@ -24,6 +26,8 @@ export default {
|
||||
},
|
||||
es: {
|
||||
'signIn': {
|
||||
notYou: 'No eres tu?',
|
||||
loggedInAs: 'Entraste como',
|
||||
facebookSignIn: 'Entrar con Facebook',
|
||||
facebookSignUp: 'Regístrate con Facebook',
|
||||
logout: 'Salir',
|
||||
|
||||
@@ -67,6 +67,16 @@
|
||||
background: #4f5c67;
|
||||
}
|
||||
|
||||
.type--darkGrey {
|
||||
color: white;
|
||||
background: #696969;
|
||||
}
|
||||
|
||||
.type--darkGrey:hover {
|
||||
color: white;
|
||||
background: #696969;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
padding: 8px 10px;
|
||||
margin-right: -1px;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.base li:hover {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
.tooltip {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
border: solid 1px #2376D8;
|
||||
top: 33px;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
padding: 20px 10px;
|
||||
z-index: 3;
|
||||
/*box-shadow: 1px 1px 4px rgba(0,105,255,.5);*/
|
||||
}
|
||||
|
||||
.tooltip:before{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: white;
|
||||
position: absolute;
|
||||
left: 2em;
|
||||
top: -19px;
|
||||
transform: rotate(180deg);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tooltip:after{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: #2376D8;
|
||||
position: absolute;
|
||||
left: 2em;
|
||||
top: -20px;
|
||||
transform: rotate(180deg);
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import styles from './Tooltip.css';
|
||||
|
||||
export default ({children}) => (
|
||||
<span className={styles.tooltip}>{children}</span>
|
||||
);
|
||||
@@ -5,3 +5,5 @@ export {default as TabBar} from './components/TabBar';
|
||||
export {default as Tab} from './components/Tab';
|
||||
export {default as TabContent} from './components/TabContent';
|
||||
export {default as Button} from './components/Button';
|
||||
export {default as Spinner} from './components/Spinner';
|
||||
export {default as Tooltip} from './components/Tooltip';
|
||||
|
||||
+25
-1
@@ -86,7 +86,14 @@ const UserSchema = new mongoose.Schema({
|
||||
|
||||
// Status provides a string that says in which state the account is.
|
||||
// When the account is banned, the user login is disabled.
|
||||
status: {type: String, enum: USER_STATUS, default: 'active'}
|
||||
status: {type: String, enum: USER_STATUS, default: 'active'},
|
||||
// User's settings
|
||||
settings: {
|
||||
bio: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
}, {
|
||||
|
||||
// This will ensure that we have proper timestamps available on this model.
|
||||
@@ -575,3 +582,20 @@ UserService.count = () => {
|
||||
UserService.all = () => {
|
||||
return UserModel.find();
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new User bio
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
UserService.addBio = (id, bio) => (
|
||||
UserModel.findOneAndUpdate({
|
||||
id
|
||||
}, {
|
||||
$set: {
|
||||
'settings.bio': bio
|
||||
}
|
||||
}, {
|
||||
new: true
|
||||
})
|
||||
);
|
||||
|
||||
@@ -150,4 +150,21 @@ router.post('/request-password-reset', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/:user_id/bio', (req, res, next) => {
|
||||
const {user_id} = req.params;
|
||||
const {bio} = req.body;
|
||||
|
||||
if (!bio) {
|
||||
return next('You must submit a new bio');
|
||||
}
|
||||
|
||||
User
|
||||
.addBio(user_id, bio)
|
||||
.then(user => res.status(200).send(user))
|
||||
.catch(error => {
|
||||
const errorMsg = typeof error === 'string' ? error : error.message;
|
||||
res.status(500).json({error: errorMsg});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user