mirror of https://github.com/tootsuite/mastodon
Improved how in-UI profiles look
parent
29e79f770f
commit
0634e8dee5
|
@ -0,0 +1,25 @@
|
|||
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
|
||||
|
||||
const DropdownMenu = ({ icon, items, size }) => {
|
||||
return (
|
||||
<Dropdown>
|
||||
<DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }}>
|
||||
<i className={`fa fa-fw fa-${icon}`} style={{ verticalAlign: 'middle' }} />
|
||||
</DropdownTrigger>
|
||||
|
||||
<DropdownContent style={{ lineHeight: '18px' }}>
|
||||
<ul>
|
||||
{items.map(({ text, action }, i) => <li key={i}><a href='#' onClick={e => { e.preventDefault(); action(); }}>{text}</a></li>)}
|
||||
</ul>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
DropdownMenu.propTypes = {
|
||||
icon: React.PropTypes.string.isRequired,
|
||||
items: React.PropTypes.array.isRequired,
|
||||
size: React.PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default DropdownMenu;
|
|
@ -1,7 +1,7 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import IconButton from './icon_button';
|
||||
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
|
||||
import DropdownMenu from './dropdown_menu';
|
||||
|
||||
const StatusActionBar = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -26,23 +26,16 @@ const StatusActionBar = React.createClass({
|
|||
this.props.onReblog(this.props.status);
|
||||
},
|
||||
|
||||
handleDeleteClick(e) {
|
||||
e.preventDefault();
|
||||
handleDeleteClick () {
|
||||
this.props.onDelete(this.props.status);
|
||||
},
|
||||
|
||||
render () {
|
||||
const { status, me } = this.props;
|
||||
let menu = '';
|
||||
let menu = [];
|
||||
|
||||
if (status.getIn(['account', 'id']) === me) {
|
||||
menu = (
|
||||
<ul>
|
||||
<li><a href='#' onClick={this.handleDeleteClick}>Delete</a></li>
|
||||
</ul>
|
||||
);
|
||||
} else {
|
||||
menu = <ul />;
|
||||
menu.push({ text: 'Delete', action: this.handleDeleteClick });
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -52,13 +45,7 @@ const StatusActionBar = React.createClass({
|
|||
<div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('favourited')} title='Favourite' icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
|
||||
<div onClick={e => e.stopPropagation()} style={{ width: '18px', height: '18px', float: 'left' }}>
|
||||
<Dropdown>
|
||||
<DropdownTrigger className='icon-button' style={{ fontSize: '18px', lineHeight: '18px', width: '18px', height: '18px' }}>
|
||||
<i className='fa fa-fw fa-ellipsis-h' />
|
||||
</DropdownTrigger>
|
||||
|
||||
<DropdownContent>{menu}</DropdownContent>
|
||||
</Dropdown>
|
||||
<DropdownMenu items={menu} icon='ellipsis-h' size={18} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Button from '../../../components/button';
|
||||
import DropdownMenu from '../../../components/dropdown_menu';
|
||||
|
||||
const ActionBar = React.createClass({
|
||||
|
||||
|
@ -16,47 +16,42 @@ const ActionBar = React.createClass({
|
|||
render () {
|
||||
const { account, me } = this.props;
|
||||
|
||||
let infoText = '';
|
||||
let follow = '';
|
||||
let buttonText = '';
|
||||
let block = '';
|
||||
let disabled = false;
|
||||
let menu = [];
|
||||
|
||||
if (account.get('id') === me) {
|
||||
buttonText = 'This is you!';
|
||||
disabled = true;
|
||||
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
menu.push({ text: 'Unblock', action: this.props.onBlock });
|
||||
} else if (account.getIn(['relationship', 'following'])) {
|
||||
menu.push({ text: 'Unfollow', action: this.props.onFollow });
|
||||
menu.push({ text: 'Block', action: this.props.onBlock });
|
||||
} else {
|
||||
let blockText = '';
|
||||
|
||||
if (account.getIn(['relationship', 'blocking'])) {
|
||||
buttonText = 'Blocked';
|
||||
disabled = true;
|
||||
blockText = 'Unblock';
|
||||
} else {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
buttonText = 'Unfollow';
|
||||
} else {
|
||||
buttonText = 'Follow';
|
||||
}
|
||||
|
||||
if (account.getIn(['relationship', 'followed_by'])) {
|
||||
infoText = 'Follows you!';
|
||||
}
|
||||
|
||||
blockText = 'Block';
|
||||
}
|
||||
|
||||
block = <Button text={blockText} onClick={this.props.onBlock} />;
|
||||
}
|
||||
|
||||
if (!account.getIn(['relationship', 'blocking'])) {
|
||||
follow = <Button text={buttonText} onClick={this.props.onFollow} disabled={disabled} />;
|
||||
menu.push({ text: 'Follow', action: this.props.onFollow });
|
||||
menu.push({ text: 'Block', action: this.props.onBlock });
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', padding: '10px', lineHeight: '36px', overflow: 'hidden', flex: '0 0 auto' }}>
|
||||
{follow} {block}
|
||||
<span style={{ color: '#616b86', fontWeight: '500', textTransform: 'uppercase', float: 'right', display: 'block' }}>{infoText}</span>
|
||||
<div style={{ borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', lineHeight: '36px', overflow: 'hidden', flex: '0 0 auto', display: 'flex' }}>
|
||||
<div style={{ flex: '1 1 auto', display: 'flex', lineHeight: '18px' }}>
|
||||
<div style={{ overflow: 'hidden', width: '80px', borderRight: '1px solid #363c4b', padding: '10px', paddingRight: '5px' }}>
|
||||
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Posts</span>
|
||||
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('statuses_count')}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ overflow: 'hidden', width: '80px', borderRight: '1px solid #363c4b', padding: '10px 5px' }}>
|
||||
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Follows</span>
|
||||
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('following_count')}</span>
|
||||
</div>
|
||||
|
||||
<div style={{ overflow: 'hidden', width: '80px', padding: '10px 5px', borderRight: '1px solid #363c4b' }}>
|
||||
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Followers</span>
|
||||
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('followers_count')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: '10px', flex: '1 1 auto' }}>
|
||||
<DropdownMenu items={menu} icon='bars' size={24} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -4,33 +4,41 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
const Header = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
account: ImmutablePropTypes.map.isRequired
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
me: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { account, me } = this.props;
|
||||
|
||||
let displayName = account.get('display_name');
|
||||
let info = '';
|
||||
|
||||
if (displayName.length === 0) {
|
||||
displayName = account.get('username');
|
||||
}
|
||||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
||||
info = <span style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', color: '#fff', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}>Follows you</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ flex: '0 0 auto', background: '#2f3441', textAlign: 'center', backgroundImage: `url(${account.get('header')})`, backgroundSize: 'cover' }}>
|
||||
<div style={{ flex: '0 0 auto', background: '#2f3441', textAlign: 'center', backgroundImage: `url(${account.get('header')})`, backgroundSize: 'cover', position: 'relative' }}>
|
||||
<div style={{ background: 'rgba(47, 52, 65, 0.8)', padding: '30px 10px' }}>
|
||||
<a href={account.get('url')} target='_blank' rel='noopener' style={{ display: 'block', color: 'inherit', textDecoration: 'none' }}>
|
||||
<div style={{ width: '90px', margin: '0 auto', marginBottom: '15px' }}>
|
||||
<img src={account.get('avatar')} alt='' style={{ display: 'block', width: '90px', height: '90px', borderRadius: '90px' }} />
|
||||
</div>
|
||||
|
||||
<span style={{ color: '#fff', fontSize: '20px', lineHeight: '27px', fontWeight: '500', display: 'block' }}>{displayName}</span>
|
||||
<span style={{ display: 'inline-block', color: '#fff', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }}>{displayName}</span>
|
||||
</a>
|
||||
|
||||
<span style={{ fontSize: '14px', fontWeight: '400', display: 'block', color: '#2b90d9', marginBottom: '15px' }}>@{account.get('acct')}</span>
|
||||
<p style={{ color: '#616b86', fontSize: '14px' }}>{account.get('note')}</p>
|
||||
|
||||
{info}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -75,7 +75,7 @@ const Account = React.createClass({
|
|||
return (
|
||||
<Column>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}>
|
||||
<Header account={account} />
|
||||
<Header account={account} me={me} />
|
||||
|
||||
<ActionBar account={account} me={me} onFollow={this.handleFollow} onBlock={this.handleBlock} />
|
||||
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import DropdownMenu from '../../../components/dropdown_menu';
|
||||
|
||||
const ActionBar = React.createClass({
|
||||
|
||||
|
||||
propTypes: {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onReply: React.PropTypes.func.isRequired,
|
||||
onReblog: React.PropTypes.func.isRequired,
|
||||
onFavourite: React.PropTypes.func.isRequired
|
||||
onFavourite: React.PropTypes.func.isRequired,
|
||||
onDelete: React.PropTypes.func.isRequired,
|
||||
me: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
const { status } = this.props;
|
||||
const { status, me } = this.props;
|
||||
|
||||
let menu = [];
|
||||
|
||||
if (me === status.getIn(['account', 'id'])) {
|
||||
menu.push({ text: 'Delete', action: () => this.props.onDelete(status) });
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ background: '#2f3441', display: 'flex', flexDirection: 'row', borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', padding: '10px 0' }}>
|
||||
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton title='Reply' icon='reply' onClick={() => this.props.onReply(status)} /></div>
|
||||
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('reblogged')} title='Reblog' icon='retweet' onClick={() => this.props.onReblog(status)} /></div>
|
||||
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('favourited')} title='Favourite' icon='star' onClick={() => this.props.onFavourite(status)} /></div>
|
||||
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><DropdownMenu size={18} icon='ellipsis-h' items={menu} /></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import ActionBar from './components/action_bar';
|
|||
import Column from '../ui/components/column';
|
||||
import { favourite, reblog } from '../../actions/interactions';
|
||||
import { replyCompose } from '../../actions/compose';
|
||||
import { deleteStatus } from '../../actions/statuses';
|
||||
import {
|
||||
getStatus,
|
||||
getStatusAncestors,
|
||||
|
@ -57,8 +58,12 @@ const Status = React.createClass({
|
|||
this.props.dispatch(reblog(status));
|
||||
},
|
||||
|
||||
handleDeleteClick (status) {
|
||||
this.props.dispatch(deleteStatus(status.get('id')));
|
||||
},
|
||||
|
||||
renderChildren (list) {
|
||||
return list.map(s => <EmbeddedStatus status={s} me={this.props.me} key={s.get('id')} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} />);
|
||||
return list.map(s => <EmbeddedStatus status={s} me={this.props.me} key={s.get('id')} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />);
|
||||
},
|
||||
|
||||
render () {
|
||||
|
@ -80,7 +85,7 @@ const Status = React.createClass({
|
|||
<div>{this.renderChildren(ancestors)}</div>
|
||||
|
||||
<DetailedStatus status={status} me={me} />
|
||||
<ActionBar status={status} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} />
|
||||
<ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />
|
||||
|
||||
<div>{this.renderChildren(descendants)}</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue