Merge pull request #269 from coralproject/design-pass-tweaks

Design pass tweaks
This commit is contained in:
Belén Curcio
2017-02-02 12:21:47 -03:00
committed by GitHub
23 changed files with 371 additions and 187 deletions
+3 -1
View File
@@ -1 +1,3 @@
.Reply {
position: relative;
}
+9 -7
View File
@@ -16,6 +16,8 @@ import {ReplyBox, ReplyButton} from 'coral-plugin-replies';
import FlagComment from 'coral-plugin-flags/FlagComment';
import LikeButton from 'coral-plugin-likes/LikeButton';
import styles from './Comment.css';
const getAction = (type, comment) => comment.actions.filter((a) => a.type === type)[0];
class Comment extends React.Component {
@@ -87,7 +89,7 @@ class Comment extends React.Component {
return (
<div
className={parentId ? 'reply' : 'comment'}
className={parentId ? `reply ${styles.Reply}` : `comment ${styles.Comment}`}
id={`c_${comment.id}`}
style={{marginLeft: depth * 30}}>
<hr aria-hidden={true} />
@@ -103,11 +105,6 @@ class Comment extends React.Component {
<PubDate created_at={comment.created_at} />
<Content body={comment.body} />
<div className="commentActionsLeft">
<ReplyButton
onClick={() => setActiveReplyBox(comment.id)}
parentCommentId={parentId || comment.id}
currentUserId={currentUser && currentUser.id}
banned={false} />
<LikeButton
like={like}
id={comment.id}
@@ -115,8 +112,14 @@ class Comment extends React.Component {
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
currentUser={currentUser} />
<ReplyButton
onClick={() => setActiveReplyBox(comment.id)}
parentCommentId={parentId || comment.id}
currentUserId={currentUser && currentUser.id}
banned={false} />
</div>
<div className="commentActionsRight">
<PermalinkButton articleURL={asset.url} commentId={comment.id} />
<FlagComment
flag={flag}
id={comment.id}
@@ -125,7 +128,6 @@ class Comment extends React.Component {
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
currentUser={currentUser} />
<PermalinkButton articleURL={asset.url} commentId={comment.id} />
</div>
{
activeReplyBox === comment.id
+98 -102
View File
@@ -3,7 +3,7 @@ import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import isEqual from 'lodash/isEqual';
import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui';
import {TabBar, Tab, TabContent, Spinner} from 'coral-ui';
const {logout, showSignInDialog, requestConfirmEmail} = authActions;
const {addNotification, clearNotification} = notificationActions;
@@ -17,27 +17,24 @@ import Stream from './Stream';
import InfoBox from 'coral-plugin-infobox/InfoBox';
import Count from 'coral-plugin-comment-count/CommentCount';
import CommentBox from 'coral-plugin-commentbox/CommentBox';
import UserBox from '../../coral-sign-in/components/UserBox';
import SignInContainer from '../../coral-sign-in/containers/SignInContainer';
import SuspendedAccount from '../../coral-framework/components/SuspendedAccount';
import SettingsContainer from '../../coral-settings/containers/SettingsContainer';
import RestrictedContent from '../../coral-framework/components/RestrictedContent';
import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer';
import UserBox from 'coral-sign-in/components/UserBox';
import SignInContainer from 'coral-sign-in/containers/SignInContainer';
import SuspendedAccount from 'coral-framework/components/SuspendedAccount';
import SettingsContainer from 'coral-settings/containers/SettingsContainer';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer';
class Embed extends Component {
constructor (props) {
super(props);
state = {activeTab: 0, showSignInDialog: false};
this.state = {
activeTab: 0,
showSignInDialog: false
};
changeTab = (tab) => {
this.changeTab = this.changeTab.bind(this);
}
// Everytime the comes from another tab, the Stream needs to be updated.
if (tab === 0) {
this.props.data.refetch();
}
changeTab (tab) {
this.setState({
activeTab: tab
});
@@ -51,15 +48,6 @@ class Embed extends Component {
}
componentDidMount () {
// stream id, logged in user, settings
// Set up messaging between embedded Iframe an parent component
// this.props.getStream(path || window.location);
// this.path = window.location.href.split('#')[0];
//
pym.sendMessage('childReady');
pym.onMessage('DOMContentLoaded', hash => {
@@ -78,7 +66,6 @@ class Embed extends Component {
}
}, 100);
});
}
componentWillReceiveProps (nextProps) {
@@ -100,86 +87,89 @@ class Embed extends Component {
minHeight: document.body.scrollHeight + 200
} : {};
return <div style={expandForLogin}>
{
loading ? <Spinner/>
: <div className="commentStream">
if (loading) {
return <Spinner />;
}
return (
<div style={expandForLogin}>
<div className="commentStream">
<TabBar onChange={this.changeTab} activeTab={activeTab}>
<Tab><Count count={asset.commentCount}/></Tab>
<Tab>Settings</Tab>
<Tab restricted={!isAdmin}>Configure Stream</Tab>
</TabBar>
{loggedIn && <UserBox user={user} logout={this.props.logout} />}
<TabContent show={activeTab === 0}>
{
openStream
? <div id="commentBox">
<InfoBox
content={asset.settings.infoBoxContent}
enable={asset.settings.infoBoxEnable}
/>
<RestrictedContent restricted={false} restrictedComp={<SuspendedAccount />}>
{
user
? <CommentBox
commentPostedHandler={refetch}
addNotification={this.props.addNotification}
postItem={this.props.postItem}
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
assetId={asset.id}
premod={asset.settings.moderation}
isReply={false}
currentUser={this.props.auth.user}
banned={false}
authorId={user.id}
charCount={asset.settings.charCountEnable && asset.settings.charCount} />
: null
}
</RestrictedContent>
</div>
: <p>{asset.settings.closedMessage}</p>
}
{!loggedIn && <SignInContainer offset={signInOffset}/>}
<Stream
refetch={refetch}
addNotification={this.props.addNotification}
postItem={this.props.postItem}
asset={asset}
currentUser={user}
postAction={this.props.postAction}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
comments={asset.comments} />
<Notification
notifLength={4500}
clearNotification={this.props.clearNotification}
notification={{text: null}}
/>
</TabContent>
<TabContent show={activeTab === 1}>
<SettingsContainer
loggedIn={loggedIn}
userData={this.props.userData}
showSignInDialog={this.props.showSignInDialog}
/>
</TabContent>
<TabContent show={activeTab === 2}>
<RestrictedContent restricted={!loggedIn}>
<ConfigureStreamContainer
status={status}
onClick={this.toggleStatus}
/>
</RestrictedContent>
</TabContent>
<Notification
notifLength={4500}
clearNotification={this.props.clearNotification}
notification={this.props.notification}
/>
</div>
}
</div>;
{loggedIn && <UserBox user={user} logout={this.props.logout} changeTab={this.changeTab} />}
<TabContent show={activeTab === 0}>
{
openStream
? <div id="commentBox">
<InfoBox
content={asset.settings.infoBoxContent}
enable={asset.settings.infoBoxEnable}
/>
<RestrictedContent restricted={false} restrictedComp={<SuspendedAccount />}>
{
user
? <CommentBox
commentPostedHandler={refetch}
addNotification={this.props.addNotification}
postItem={this.props.postItem}
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
assetId={asset.id}
premod={asset.settings.moderation}
isReply={false}
currentUser={this.props.auth.user}
banned={false}
authorId={user.id}
charCount={asset.settings.charCountEnable && asset.settings.charCount} />
: null
}
</RestrictedContent>
</div>
: <p>{asset.settings.closedMessage}</p>
}
{!loggedIn && <SignInContainer offset={signInOffset}/>}
<Stream
refetch={refetch}
addNotification={this.props.addNotification}
postItem={this.props.postItem}
asset={asset}
currentUser={user}
postAction={this.props.postAction}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
comments={asset.comments} />
<Notification
notifLength={4500}
clearNotification={this.props.clearNotification}
notification={{text: null}}
/>
</TabContent>
<TabContent show={activeTab === 1}>
<SettingsContainer
loggedIn={loggedIn}
userData={this.props.userData}
showSignInDialog={this.props.showSignInDialog}
/>
</TabContent>
<TabContent show={activeTab === 2}>
<RestrictedContent restricted={!loggedIn}>
<ConfigureStreamContainer
status={status}
onClick={this.toggleStatus}
/>
</RestrictedContent>
</TabContent>
<Notification
notifLength={4500}
clearNotification={this.props.clearNotification}
notification={this.props.notification}
/>
</div>
</div>
);
}
}
@@ -193,7 +183,13 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
requestConfirmEmail: () => dispatch(requestConfirmEmail()),
loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)),
addNotification: (type, text) => dispatch(addNotification(type, text)),
addNotification: (type, text) => {
pym.sendMessage('getPosition');
pym.onMessage('position', position => {
dispatch(addNotification(type, text, position));
});
},
clearNotification: () => dispatch(clearNotification()),
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
logout: () => dispatch(logout()),
+8 -2
View File
@@ -1,3 +1,8 @@
html, body {
width:auto;
height:auto;
}
body {
font-family: 'Lato', sans-serif;
font-family: 'Open Sans', sans-serif;
@@ -46,7 +51,6 @@ hr {
/* Notification styles */
#coral-notif {
position: fixed;
bottom: 0;
border: 0;
background: rgb(105,105,105);
color: white;
@@ -76,6 +80,8 @@ hr {
.commentStream {
/* prevent absolutely positioned final permalink popover from being clipped */
padding-bottom: 50px;
min-height: 600px;
position: relative;
}
/* Comment Box Styles */
@@ -219,8 +225,8 @@ hr {
.coral-plugin-flags-popup span {
min-width: 280px;
bottom: 36px;
left: -190px;
position: absolute;
right: 10px;
}
.coral-plugin-flags-popup-form {
@@ -1,11 +1,12 @@
export const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
export const CLEAR_NOTIFICATION = 'CLEAR_NOTIFICATION';
export const addNotification = (notifType, text) => {
export const addNotification = (notifType, text, position) => {
return {
type: ADD_NOTIFICATION,
notifType,
text
text,
position
};
};
@@ -1,4 +1,5 @@
import React from 'react';
import {SnackBar} from 'coral-ui';
const Notification = (props) => {
if (props.notification.text) {
@@ -6,14 +7,16 @@ const Notification = (props) => {
props.clearNotification();
}, props.notifLength);
}
return <div>
{
props.notification.text &&
<dialog open id='coral-notif' className={`coral-notif-${ props.notification.type}`}>
{props.notification.text}
</dialog>
}
</div>;
return (
<div>
{
props.notification.text &&
<SnackBar id='coral-notif' className={`coral-notif-${props.notification.type}`} position={props.notification.position}>
{props.notification.text}
</SnackBar>
}
</div>
);
};
export default Notification;
@@ -1,14 +1,21 @@
/* Items Notifications */
import * as actions from '../actions/notification';
import {fromJS} from 'immutable';
import {Map} from 'immutable';
const initialState = fromJS({});
const initialState = Map({
text: '',
type: '',
position: 400
});
export default (state = initialState, action) => {
switch (action.type) {
case actions.ADD_NOTIFICATION:
return state.set('text', action.text).set('type', action.notifType);
return state
.merge({
type: action.notifType,
text: action.text,
position: action.position
});
case actions.CLEAR_NOTIFICATION:
return initialState;
default:
+23 -27
View File
@@ -1,51 +1,47 @@
import React, {Component} from 'react';
import {Tooltip} from 'coral-ui';
import FlagBio from '../coral-plugin-flags/FlagBio';
import FlagBio from 'coral-plugin-flags/FlagBio';
const packagename = 'coral-plugin-author-name';
import styles from './styles.css';
export default class AuthorName extends Component {
constructor (props) {
super(props);
this.state = {
showTooltip: false
};
state = {showTooltip: false}
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
handleClick = () => {
this.setState(state => ({
showTooltip: !state.showTooltip
}));
}
handleMouseOver () {
this.setState({
showTooltip: true
});
}
handleMouseLeave () {
this.setState({
showTooltip: false
});
handleMouseLeave = () => {
setTimeout(() => {
this.setState({
showTooltip: false
});
}, 500);
}
render () {
const {author} = this.props;
const {showTooltip} = this.state;
return (
<div
className={`${packagename}-text`}
onMouseOver={this.handleMouseOver}
onMouseLeave={this.handleMouseLeave}
>
{author && author.name}
{ showTooltip && author.settings.bio && <Tooltip>
<div className={`${packagename}-text ${styles.container}`} onClick={this.handleClick} onMouseLeave={this.handleMouseLeave}>
<a className={`${styles.authorName} ${author.settings.bio ? styles.hasBio : ''}`}>
{author && author.name}
{author.settings.bio ? <span className={`${styles.arrowDown} ${showTooltip ? styles.arrowUp : ''}`} /> : null}
</a>
{showTooltip && author.settings.bio
&& (
<Tooltip>
<div className={`${packagename}-bio`}>
{author.settings.bio}
</div>
<div className={`${packagename}-bio-flag`}>
<FlagBio {...this.props}/>
</div>
</Tooltip>
}
</Tooltip>
)}
</div>
);
}
@@ -0,0 +1,34 @@
.authorName {
color: black;
display: inline-block;
margin: 10px 0;
}
.hasBio {
&:hover {
cursor: pointer;
}
}
.arrowDown {
top: 0;
width: 0;
height: 0;
margin-top: -2px;
margin-left: 2px;
display: inline-block;
vertical-align: middle;
border-bottom: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 3px solid #000000;
}
.arrowUp {
width: 0;
height: 0;
border-top: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-bottom: 3px solid black;
}
+2 -2
View File
@@ -10,8 +10,8 @@ const getPopupMenu = [
return {
header: lang.t('step-1-header'),
options: [
{val: 'USERS', text: lang.t('flag-username')},
{val: 'COMMENTS', text: lang.t('flag-comment')}
{val: 'COMMENTS', text: lang.t('flag-comment')},
{val: 'USERS', text: lang.t('flag-username')}
],
button: lang.t('continue'),
sets: 'itemType'
@@ -3,6 +3,8 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from './translations';
import onClickOutside from 'react-onclickoutside';
const name = 'coral-plugin-permalinks';
import {Button} from 'coral-ui';
import styles from './styles.css';
const lang = new I18n(translations);
@@ -32,40 +34,37 @@ class PermalinkButton extends React.Component {
this.permalinkInput.select();
try {
document.execCommand('copy');
this.setState({copySuccessful: true});
this.setState({copySuccessful: true, copyFailure: null});
} catch (err) {
this.setState({copyFailure: true});
this.setState({copyFailure: true, copySuccessful: null});
}
setTimeout(() => {
this.setState({copyFailure: null, copySuccessful: null});
}, 4500);
}, 3000);
}
render () {
const {copySuccessful, copyFailure} = this.state;
return (
<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
className={`${name}-popover ${this.state.popoverOpen ? 'active' : ''}`}>
<div className={`${name}-popover ${styles.container} ${this.state.popoverOpen ? 'active' : ''}`}>
<input
className={`${name}-copy-field`}
type='text'
ref={input => this.permalinkInput = input}
value={`${this.props.articleURL}#${this.props.commentId}`}
onChange={() => {}} />
<button className={`${name}-copy-button`} onClick={this.copyPermalink}>Copy</button>
{
this.state.copySuccessful ? <p className={`${name}-copied-text`}>copied to clipboard</p> : null
}
{
this.state.copyFailure
? <p className={`${name}-copied-error`}>copying to clipboard not supported in this browser. Use Cmd + C.</p>
: null
}
<Button className={`${name}-copy-button ${copySuccessful ? styles.success : ''} ${copyFailure ? styles.failure : ''}`}
onClick={this.copyPermalink} >
{!copyFailure && !copySuccessful && 'Copy'}
{copySuccessful && 'Copied'}
{copyFailure && 'Not supported'}
</Button>
</div>
</div>
);
+74
View File
@@ -0,0 +1,74 @@
.container {
border-radius: 3px;
padding: 15px 10px;
box-sizing: border-box;
/* box-shadow: 3px 3px 5px 0 rgba(0, 0, 0, 0.3); */
border: solid 1px rgba(153, 153, 153, 0.33);
width: auto;
margin: 0 auto;
left: 0;
margin: 0 5px;
top: 0;
left: 0;
width: 100%;
margin: 0;
margin-top: -13px;
&::before {
content: '';
border: 10px solid transparent;
border-top-color: white;
position: absolute;
right: 8.69em;
bottom: -20px;
z-index: 2;
}
&::after{
content: '';
border: 10px solid transparent;
border-top-color: rgba(153, 153, 153, 0.33);
position: absolute;
right: 8.69em;
bottom: -21px;
z-index: 1;
}
input {
display: inline-block;
width: calc(100% - 78px);
padding: 8px;
border-radius: 3px;
border: solid 1px #e0e0e0;
height: 32px;
box-sizing: border-box;
font-size: 1em;
}
button {
display: inline-block;
float: right;
box-sizing: border-box;
margin: 0;
background-color: #e0e0e0;
font-size: 1em;
width: auto;
height: auto;
padding: 2px;
transition: background-color 0.4s ease;
&:hover{
color: black;
}
&.success {
background-color: #00897B;
color: white;
}
&.failure {
background-color: #FF5252;
color: white;
}
}
}
@@ -1,12 +1,12 @@
{
"en": {
"permalink": {
"permalink": "Permalink"
"permalink": "Link"
}
},
"es": {
"permalink": {
"permalink": "Enlace permanente"
"permalink": "Enlace"
}
}
}
+4 -7
View File
@@ -4,14 +4,11 @@ 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 : ''}`}
{...props}
>
const UserBox = ({className, user, logout, changeTab}) => (
<div className={`${styles.userBox} ${className ? className : ''}`}>
{lang.t('signIn.loggedInAs')}
<a>{user.displayName}</a>. {lang.t('signIn.notYou')}
<a onClick={logout} id='logout'>{lang.t('signIn.logout')}</a>
<a onClick={() => changeTab(1)}>{user.displayName}</a>. {lang.t('signIn.notYou')}
<a className={styles.logout} onClick={logout} id='logout'>{lang.t('signIn.logout')}</a>
</div>
);
+4 -1
View File
@@ -93,7 +93,10 @@ input.error{
margin: 0px;
margin-left: 4px;
padding-bottom: 2px;
border-bottom: solid 1px black;
}
.userBox .logout {
border-bottom: solid 1px black;
}
.attention {
+2 -2
View File
@@ -1,9 +1,9 @@
import React from 'react';
import styles from './Checkbox.css';
export default ({name, cStyle = 'base', onChange, label, className, info, ...props}) => (
export default ({name, cStyle = 'base', onChange, label, className, info, ...attrs}) => (
<label className={`${styles.label} ${styles[`type--${cStyle}`]} ${className}`} htmlFor={name}>
<input type="checkbox" id={name} name={name} onChange={onChange} {...props} />
<input type="checkbox" id={name} name={name} onChange={onChange} {...attrs} />
<span className={styles.checkbox}></span>
{label && <span>{label}</span>}
{info && (
+19
View File
@@ -0,0 +1,19 @@
.SnackBar {
position: fixed;
cursor: default;
background-color: #323232;
z-index: 3;
display: flex;
will-change: transform;
transition: transform .25s cubic-bezier(.4,0,1,1);
pointer-events: none;
padding: 18px 14px;
vertical-align: middle;
color: #fff;
border-radius: 3px;
text-align: center;
left: 50%;
transform: translate(-50%, 0);
top: 0;
bottom: auto;
}
+16
View File
@@ -0,0 +1,16 @@
import React from 'react';
import styles from './SnackBar.css';
const SnackBar = ({children, className, position, ...attrs}) => {
return (
<div className={`${styles.SnackBar} ${className}`}
style={ position ? {top: `${position}px`} : fixedStyle}
{...attrs} >
{children}
</div>
);
};
const fixedStyle = {bottom: '200px', top: 'auto'};
export default SnackBar;
+1 -1
View File
@@ -3,7 +3,7 @@
position: absolute;
width: 100%;
border: solid 1px #2376D8;
top: 33px;
top: 48px;
left: 0;
box-sizing: border-box;
background: white;
+1
View File
@@ -16,3 +16,4 @@ export {default as Card} from './components/Card';
export {default as FormField} from './components/FormField';
export {default as Success} from './components/Success';
export {default as Pager} from './components/Pager';
export {default as SnackBar} from './components/SnackBar';
+5 -1
View File
@@ -73,7 +73,7 @@ const getCountsByParentID = (context, parent_ids) => {
* @param {Object} context graph context
* @param {Object} query query terms to apply to the comments query
*/
const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, limit, cursor, sort}) => {
const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sort}) => {
let comments = CommentModel.find();
// Only administrators can search for comments with statuses that are not
@@ -100,6 +100,10 @@ const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, limit,
});
}
if (user && (user.hasRoles('ADMIN') || user.id === author_id)) {
comments = comments.where({author_id});
}
if (asset_id) {
comments = comments.where({asset_id});
}
@@ -28,8 +28,8 @@ describe ('notificationsReducer', () => {
type: 'test'
});
const result = notificationReducer(store, action);
expect(result.get('text')).to.equal(undefined);
expect(result.get('type')).to.equal(undefined);
expect(result.get('text')).to.equal('');
expect(result.get('type')).to.equal('');
});
});
});
+25 -1
View File
@@ -28,6 +28,7 @@
<script type='text/javascript' src='<%= basePath %>/pym.v1.min.js'></script>
<script>
var ready = false;
var notificationOffset = 200;
// default to using the window.location
var url = window.location.protocol + '//' + window.location.host + window.location.pathname;
@@ -38,7 +39,21 @@
<%}%>
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream?asset_url=' + encodeURIComponent(url), {title: 'Talk Comments', id:'coralStreamIframe', name: 'coralStreamIframe', asset_url: url});
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'})
pymParent.onMessage('height', function(height) {
document.querySelector('#coralStreamEmbed iframe').height = height + 'px';
})
pymParent.onMessage('getPosition', function(notification) {
var position = viewport().height + document.body.scrollTop;
if (position > notificationOffset) {
position = position - notificationOffset;
}
pymParent.sendMessage('position', position);
});
pymParent.onMessage('childReady', function () {
var interval = setInterval(function () {
if (ready) {
@@ -65,6 +80,15 @@
document.addEventListener('DOMContentLoaded', function () {
ready = true;
});
function viewport() {
var e = window, a = 'inner';
if ( !( 'innerWidth' in window ) ){
a = 'client';
e = document.documentElement || document.body;
}
return { width : e[ a+'Width' ] , height : e[ a+'Height' ] }
}
</script>
</body>
</html>