* Redesign profile column in web UI to match design on public pages * Make the tab links text boldermaster
@@ -1,190 +0,0 @@ | |||
import React from 'react'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import PropTypes from 'prop-types'; | |||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; | |||
import { NavLink } from 'react-router-dom'; | |||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
import { me, isStaff } from '../../../initial_state'; | |||
import { shortNumberFormat } from '../../../utils/numbers'; | |||
const messages = defineMessages({ | |||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, | |||
direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' }, | |||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, | |||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, | |||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, | |||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, | |||
block: { id: 'account.block', defaultMessage: 'Block @{name}' }, | |||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, | |||
follow: { id: 'account.follow', defaultMessage: 'Follow' }, | |||
report: { id: 'account.report', defaultMessage: 'Report @{name}' }, | |||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' }, | |||
media: { id: 'account.media', defaultMessage: 'Media' }, | |||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' }, | |||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, | |||
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' }, | |||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' }, | |||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, | |||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, | |||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, | |||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, | |||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, | |||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, | |||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, | |||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, | |||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, | |||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' }, | |||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' }, | |||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, | |||
}); | |||
export default @injectIntl | |||
class ActionBar extends React.PureComponent { | |||
static propTypes = { | |||
account: ImmutablePropTypes.map.isRequired, | |||
onFollow: PropTypes.func, | |||
onBlock: PropTypes.func.isRequired, | |||
onMention: PropTypes.func.isRequired, | |||
onDirect: PropTypes.func.isRequired, | |||
onReblogToggle: PropTypes.func.isRequired, | |||
onReport: PropTypes.func.isRequired, | |||
onMute: PropTypes.func.isRequired, | |||
onBlockDomain: PropTypes.func.isRequired, | |||
onUnblockDomain: PropTypes.func.isRequired, | |||
onEndorseToggle: PropTypes.func.isRequired, | |||
onAddToList: PropTypes.func.isRequired, | |||
intl: PropTypes.object.isRequired, | |||
}; | |||
handleShare = () => { | |||
navigator.share({ | |||
url: this.props.account.get('url'), | |||
}); | |||
} | |||
isStatusesPageActive = (match, location) => { | |||
if (!match) { | |||
return false; | |||
} | |||
return !location.pathname.match(/\/(followers|following)\/?$/); | |||
} | |||
render () { | |||
const { account, intl } = this.props; | |||
let menu = []; | |||
let extraInfo = ''; | |||
if (account.get('id') !== me) { | |||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); | |||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect }); | |||
menu.push(null); | |||
} | |||
if ('share' in navigator) { | |||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); | |||
menu.push(null); | |||
} | |||
if (account.get('id') === me) { | |||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); | |||
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' }); | |||
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' }); | |||
menu.push(null); | |||
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' }); | |||
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' }); | |||
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' }); | |||
menu.push(null); | |||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' }); | |||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' }); | |||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }); | |||
} else { | |||
if (account.getIn(['relationship', 'following'])) { | |||
if (account.getIn(['relationship', 'showing_reblogs'])) { | |||
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); | |||
} else { | |||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); | |||
} | |||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle }); | |||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList }); | |||
menu.push(null); | |||
} | |||
if (account.getIn(['relationship', 'muting'])) { | |||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute }); | |||
} else { | |||
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute }); | |||
} | |||
if (account.getIn(['relationship', 'blocking'])) { | |||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock }); | |||
} else { | |||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock }); | |||
} | |||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport }); | |||
} | |||
if (account.get('acct') !== account.get('username')) { | |||
const domain = account.get('acct').split('@')[1]; | |||
extraInfo = ( | |||
<div className='account__disclaimer'> | |||
<FormattedMessage | |||
id='account.disclaimer_full' | |||
defaultMessage="Information below may reflect the user's profile incompletely." | |||
/> | |||
{' '} | |||
<a target='_blank' rel='noopener' href={account.get('url')}> | |||
<FormattedMessage id='account.view_full_profile' defaultMessage='View full profile' /> | |||
</a> | |||
</div> | |||
); | |||
menu.push(null); | |||
if (account.getIn(['relationship', 'domain_blocking'])) { | |||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain }); | |||
} else { | |||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain }); | |||
} | |||
} | |||
if (account.get('id') !== me && isStaff) { | |||
menu.push(null); | |||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` }); | |||
} | |||
return ( | |||
<div> | |||
{extraInfo} | |||
<div className='account__action-bar'> | |||
<div className='account__action-bar-links'> | |||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}> | |||
<FormattedMessage id='account.posts' defaultMessage='Toots' /> | |||
<strong>{shortNumberFormat(account.get('statuses_count'))}</strong> | |||
</NavLink> | |||
<NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}> | |||
<FormattedMessage id='account.follows' defaultMessage='Follows' /> | |||
<strong>{shortNumberFormat(account.get('following_count'))}</strong> | |||
</NavLink> | |||
<NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}> | |||
<FormattedMessage id='account.followers' defaultMessage='Followers' /> | |||
<strong>{shortNumberFormat(account.get('followers_count'))}</strong> | |||
</NavLink> | |||
</div> | |||
<div className='account__action-bar-dropdown'> | |||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' /> | |||
</div> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -2,13 +2,15 @@ import React from 'react'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import PropTypes from 'prop-types'; | |||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
import IconButton from '../../../components/icon_button'; | |||
import Motion from '../../ui/util/optional_motion'; | |||
import spring from 'react-motion/lib/spring'; | |||
import Button from 'mastodon/components/button'; | |||
import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
import { autoPlayGif, me } from '../../../initial_state'; | |||
import { autoPlayGif, me, isStaff } from 'mastodon/initial_state'; | |||
import classNames from 'classnames'; | |||
import Icon from 'mastodon/components/icon'; | |||
import Avatar from 'mastodon/components/avatar'; | |||
import { shortNumberFormat } from 'mastodon/utils/numbers'; | |||
import { NavLink } from 'react-router-dom'; | |||
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; | |||
const messages = defineMessages({ | |||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, | |||
@@ -18,6 +20,32 @@ const messages = defineMessages({ | |||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, | |||
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' }, | |||
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' }, | |||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, | |||
direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' }, | |||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, | |||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, | |||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, | |||
block: { id: 'account.block', defaultMessage: 'Block @{name}' }, | |||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, | |||
report: { id: 'account.report', defaultMessage: 'Report @{name}' }, | |||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' }, | |||
media: { id: 'account.media', defaultMessage: 'Media' }, | |||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' }, | |||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, | |||
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' }, | |||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' }, | |||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, | |||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, | |||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, | |||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, | |||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, | |||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, | |||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, | |||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, | |||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, | |||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' }, | |||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' }, | |||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, | |||
}); | |||
const dateFormatOptions = { | |||
@@ -29,54 +57,6 @@ const dateFormatOptions = { | |||
minute: '2-digit', | |||
}; | |||
class Avatar extends ImmutablePureComponent { | |||
static propTypes = { | |||
account: ImmutablePropTypes.map.isRequired, | |||
}; | |||
state = { | |||
isHovered: false, | |||
}; | |||
handleMouseOver = () => { | |||
if (this.state.isHovered) return; | |||
this.setState({ isHovered: true }); | |||
} | |||
handleMouseOut = () => { | |||
if (!this.state.isHovered) return; | |||
this.setState({ isHovered: false }); | |||
} | |||
render () { | |||
const { account } = this.props; | |||
const { isHovered } = this.state; | |||
return ( | |||
<Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}> | |||
{({ radius }) => ( | |||
<a | |||
href={account.get('url')} | |||
className='account__header__avatar' | |||
role='presentation' | |||
target='_blank' | |||
rel='noopener' | |||
style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }} | |||
onMouseOver={this.handleMouseOver} | |||
onMouseOut={this.handleMouseOut} | |||
onFocus={this.handleMouseOver} | |||
onBlur={this.handleMouseOut} | |||
> | |||
<span style={{ display: 'none' }}>{account.get('acct')}</span> | |||
</a> | |||
)} | |||
</Motion> | |||
); | |||
} | |||
} | |||
export default @injectIntl | |||
class Header extends ImmutablePureComponent { | |||
@@ -85,64 +65,57 @@ class Header extends ImmutablePureComponent { | |||
onFollow: PropTypes.func.isRequired, | |||
onBlock: PropTypes.func.isRequired, | |||
intl: PropTypes.object.isRequired, | |||
domain: PropTypes.string.isRequired, | |||
}; | |||
openEditProfile = () => { | |||
window.open('/settings/profile', '_blank'); | |||
} | |||
isStatusesPageActive = (match, location) => { | |||
if (!match) { | |||
return false; | |||
} | |||
return !location.pathname.match(/\/(followers|following)\/?$/); | |||
} | |||
render () { | |||
const { account, intl } = this.props; | |||
const { account, intl, domain } = this.props; | |||
if (!account) { | |||
return null; | |||
} | |||
let info = ''; | |||
let mutingInfo = ''; | |||
let info = []; | |||
let actionBtn = ''; | |||
let lockedIcon = ''; | |||
let menu = []; | |||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { | |||
info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>; | |||
info.push(<span className='relationship-tag'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>); | |||
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) { | |||
info = <span className='account--follows-info'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>; | |||
info.push(<span className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>); | |||
} | |||
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) { | |||
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>; | |||
info.push(<span className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>); | |||
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) { | |||
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>; | |||
info.push(<span className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>); | |||
} | |||
if (me !== account.get('id')) { | |||
if (!account.get('relationship')) { // Wait until the relationship is loaded | |||
actionBtn = ''; | |||
} else if (account.getIn(['relationship', 'requested'])) { | |||
actionBtn = ( | |||
<div className='account--action-button'> | |||
<IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} /> | |||
</div> | |||
); | |||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; | |||
} else if (!account.getIn(['relationship', 'blocking'])) { | |||
actionBtn = ( | |||
<div className='account--action-button'> | |||
<IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} /> | |||
</div> | |||
); | |||
actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />; | |||
} else if (account.getIn(['relationship', 'blocking'])) { | |||
actionBtn = ( | |||
<div className='account--action-button'> | |||
<IconButton size={26} icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} /> | |||
</div> | |||
); | |||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />; | |||
} | |||
} else { | |||
actionBtn = ( | |||
<div className='account--action-button'> | |||
<IconButton size={26} icon='pencil' title={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} /> | |||
</div> | |||
); | |||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />; | |||
} | |||
if (account.get('moved') && !account.getIn(['relationship', 'following'])) { | |||
@@ -153,40 +126,145 @@ class Header extends ImmutablePureComponent { | |||
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />; | |||
} | |||
if (account.get('id') !== me) { | |||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); | |||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect }); | |||
menu.push(null); | |||
} | |||
if ('share' in navigator) { | |||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); | |||
menu.push(null); | |||
} | |||
if (account.get('id') === me) { | |||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); | |||
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' }); | |||
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' }); | |||
menu.push(null); | |||
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' }); | |||
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' }); | |||
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' }); | |||
menu.push(null); | |||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' }); | |||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' }); | |||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }); | |||
} else { | |||
if (account.getIn(['relationship', 'following'])) { | |||
if (account.getIn(['relationship', 'showing_reblogs'])) { | |||
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); | |||
} else { | |||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); | |||
} | |||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle }); | |||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList }); | |||
menu.push(null); | |||
} | |||
if (account.getIn(['relationship', 'muting'])) { | |||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute }); | |||
} else { | |||
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute }); | |||
} | |||
if (account.getIn(['relationship', 'blocking'])) { | |||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock }); | |||
} else { | |||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock }); | |||
} | |||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport }); | |||
} | |||
if (account.get('acct') !== account.get('username')) { | |||
const domain = account.get('acct').split('@')[1]; | |||
menu.push(null); | |||
if (account.getIn(['relationship', 'domain_blocking'])) { | |||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain }); | |||
} else { | |||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain }); | |||
} | |||
} | |||
if (account.get('id') !== me && isStaff) { | |||
menu.push(null); | |||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` }); | |||
} | |||
const content = { __html: account.get('note_emojified') }; | |||
const displayNameHtml = { __html: account.get('display_name_html') }; | |||
const fields = account.get('fields'); | |||
const badge = account.get('bot') ? (<div className='roles'><div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div></div>) : null; | |||
const badge = account.get('bot') ? (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>) : null; | |||
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); | |||
return ( | |||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} style={{ backgroundImage: `url(${autoPlayGif ? account.get('header') : account.get('header_static')})` }}> | |||
<div> | |||
<Avatar account={account} /> | |||
<div className={classNames('account__header', { inactive: !!account.get('moved') })}> | |||
<div className='account__header__image'> | |||
<div className='account__header__info'> | |||
{info} | |||
</div> | |||
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} /> | |||
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span> | |||
<img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' /> | |||
</div> | |||
{badge} | |||
<div className='account__header__bar'> | |||
<div className='account__header__tabs'> | |||
<a className='avatar' href={account.get('url')}> | |||
<Avatar account={account} size={90} /> | |||
</a> | |||
<div className='account__header__content' dangerouslySetInnerHTML={content} /> | |||
<div className='spacer' /> | |||
{fields.size > 0 && ( | |||
<div className='account__header__fields'> | |||
{fields.map((pair, i) => ( | |||
<dl key={i}> | |||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} /> | |||
<div className='account__header__tabs__buttons'> | |||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' /> | |||
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}> | |||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} /> | |||
</dd> | |||
</dl> | |||
))} | |||
{actionBtn} | |||
</div> | |||
)} | |||
</div> | |||
<div className='account__header__tabs__name'> | |||
<h1> | |||
<span dangerouslySetInnerHTML={displayNameHtml} /> {badge} | |||
<small>@{acct} {lockedIcon}</small> | |||
</h1> | |||
</div> | |||
<div className='account__header__extra'> | |||
<div className='account__header__bio'> | |||
{fields.size > 0 && ( | |||
<div className='account__header__fields'> | |||
{fields.map((pair, i) => ( | |||
<dl key={i}> | |||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} /> | |||
{info} | |||
{mutingInfo} | |||
{actionBtn} | |||
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}> | |||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} /> | |||
</dd> | |||
</dl> | |||
))} | |||
</div> | |||
)} | |||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content' dangerouslySetInnerHTML={content} />} | |||
</div> | |||
<div className='account__header__extra__links'> | |||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}> | |||
<strong>{shortNumberFormat(account.get('statuses_count'))}</strong> <FormattedMessage id='account.posts' defaultMessage='Toots' /> | |||
</NavLink> | |||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}> | |||
<strong>{shortNumberFormat(account.get('following_count'))}</strong> <FormattedMessage id='account.follows' defaultMessage='Follows' /> | |||
</NavLink> | |||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}> | |||
<strong>{shortNumberFormat(account.get('followers_count'))}</strong> <FormattedMessage id='account.followers' defaultMessage='Followers' /> | |||
</NavLink> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
); | |||
@@ -2,7 +2,6 @@ import React from 'react'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import PropTypes from 'prop-types'; | |||
import InnerHeader from '../../account/components/header'; | |||
import ActionBar from '../../account/components/action_bar'; | |||
import MissingIndicator from '../../../components/missing_indicator'; | |||
import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
import MovedNote from './moved_note'; | |||
@@ -25,6 +24,7 @@ export default class Header extends ImmutablePureComponent { | |||
onEndorseToggle: PropTypes.func.isRequired, | |||
onAddToList: PropTypes.func.isRequired, | |||
hideTabs: PropTypes.bool, | |||
domain: PropTypes.string.isRequired, | |||
}; | |||
static contextTypes = { | |||
@@ -98,20 +98,7 @@ export default class Header extends ImmutablePureComponent { | |||
account={account} | |||
onFollow={this.handleFollow} | |||
onBlock={this.handleBlock} | |||
/> | |||
<ActionBar | |||
account={account} | |||
onBlock={this.handleBlock} | |||
onMention={this.handleMention} | |||
onDirect={this.handleDirect} | |||
onReblogToggle={this.handleReblogToggle} | |||
onReport={this.handleReport} | |||
onMute={this.handleMute} | |||
onBlockDomain={this.handleBlockDomain} | |||
onUnblockDomain={this.handleUnblockDomain} | |||
onEndorseToggle={this.handleEndorseToggle} | |||
onAddToList={this.handleAddToList} | |||
domain={this.props.domain} | |||
/> | |||
{!hideTabs && ( | |||
@@ -33,6 +33,7 @@ const makeMapStateToProps = () => { | |||
const mapStateToProps = (state, { accountId }) => ({ | |||
account: getAccount(state, accountId), | |||
domain: state.getIn(['meta', 'domain']), | |||
}); | |||
return mapStateToProps; | |||
@@ -1186,57 +1186,6 @@ a .account__avatar { | |||
white-space: nowrap; | |||
} | |||
.account__header { | |||
flex: 0 0 auto; | |||
background: lighten($ui-base-color, 4%); | |||
text-align: center; | |||
background-size: cover; | |||
background-position: center; | |||
position: relative; | |||
&.inactive { | |||
opacity: 0.5; | |||
.account__header__avatar { | |||
filter: grayscale(100%); | |||
} | |||
.account__header__username { | |||
color: $secondary-text-color; | |||
} | |||
} | |||
& > div { | |||
background: rgba(lighten($ui-base-color, 4%), 0.9); | |||
padding: 20px 10px; | |||
} | |||
.account__header__content { | |||
color: $secondary-text-color; | |||
} | |||
.account__header__display-name { | |||
color: $primary-text-color; | |||
display: inline-block; | |||
width: 100%; | |||
font-size: 20px; | |||
line-height: 27px; | |||
font-weight: 500; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
.account__header__username { | |||
color: $highlight-text-color; | |||
font-size: 14px; | |||
font-weight: 400; | |||
display: block; | |||
margin-bottom: 10px; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
} | |||
.account__disclaimer { | |||
padding: 10px; | |||
border-top: 1px solid lighten($ui-base-color, 8%); | |||
@@ -1265,39 +1214,6 @@ a .account__avatar { | |||
} | |||
} | |||
.account__header__content { | |||
color: $darker-text-color; | |||
font-size: 14px; | |||
font-weight: 400; | |||
overflow: hidden; | |||
word-break: normal; | |||
word-wrap: break-word; | |||
p { | |||
margin-bottom: 20px; | |||
&:last-child { | |||
margin-bottom: 0; | |||
} | |||
} | |||
a { | |||
color: inherit; | |||
text-decoration: underline; | |||
&:hover { | |||
text-decoration: none; | |||
} | |||
} | |||
} | |||
.account__header__display-name { | |||
.emojione { | |||
width: 25px; | |||
height: 25px; | |||
} | |||
} | |||
.account__action-bar { | |||
border-top: 1px solid lighten($ui-base-color, 8%); | |||
border-bottom: 1px solid lighten($ui-base-color, 8%); | |||
@@ -1369,15 +1285,6 @@ a .account__avatar { | |||
} | |||
} | |||
.account__header__avatar { | |||
background-size: 90px 90px; | |||
display: block; | |||
height: 90px; | |||
margin: 0 auto 10px; | |||
overflow: hidden; | |||
width: 90px; | |||
} | |||
.account-authorize { | |||
padding: 14px 10px; | |||
@@ -3154,29 +3061,11 @@ a.status-card.compact:hover { | |||
} | |||
} | |||
.account--follows-info { | |||
.relationship-tag { | |||
color: $primary-text-color; | |||
position: absolute; | |||
top: 10px; | |||
left: 10px; | |||
margin-bottom: 4px; | |||
opacity: 0.7; | |||
display: inline-block; | |||
vertical-align: top; | |||
background-color: rgba($base-overlay-background, 0.4); | |||
text-transform: uppercase; | |||
font-size: 11px; | |||
font-weight: 500; | |||
padding: 4px; | |||
border-radius: 4px; | |||
} | |||
.account--muting-info { | |||
color: $primary-text-color; | |||
position: absolute; | |||
top: 40px; | |||
left: 10px; | |||
opacity: 0.7; | |||
display: inline-block; | |||
display: block; | |||
vertical-align: top; | |||
background-color: rgba($base-overlay-background, 0.4); | |||
text-transform: uppercase; | |||
@@ -3186,12 +3075,6 @@ a.status-card.compact:hover { | |||
border-radius: 4px; | |||
} | |||
.account--action-button { | |||
position: absolute; | |||
top: 10px; | |||
right: 20px; | |||
} | |||
.setting-toggle { | |||
display: block; | |||
line-height: 24px; | |||
@@ -5348,53 +5231,188 @@ noscript { | |||
} | |||
} | |||
.account__header .roles { | |||
margin-top: 20px; | |||
margin-bottom: 20px; | |||
padding: 0 15px; | |||
.account__header__content { | |||
color: $darker-text-color; | |||
font-size: 14px; | |||
font-weight: 400; | |||
overflow: hidden; | |||
word-break: normal; | |||
word-wrap: break-word; | |||
p { | |||
margin-bottom: 20px; | |||
&:last-child { | |||
margin-bottom: 0; | |||
} | |||
} | |||
a { | |||
color: inherit; | |||
text-decoration: underline; | |||
&:hover { | |||
text-decoration: none; | |||
} | |||
} | |||
} | |||
.account__header .account__header__fields { | |||
font-size: 14px; | |||
line-height: 20px; | |||
.account__header { | |||
overflow: hidden; | |||
margin: 20px -10px -20px; | |||
border-bottom: 0; | |||
border-top: 0; | |||
dl { | |||
border-top: 1px solid lighten($ui-base-color, 4%); | |||
border-bottom: 0; | |||
display: flex; | |||
&.inactive { | |||
opacity: 0.5; | |||
.account__header__image, | |||
.account__avatar { | |||
filter: grayscale(100%); | |||
} | |||
} | |||
dt, | |||
dd { | |||
box-sizing: border-box; | |||
padding: 14px 5px; | |||
text-align: center; | |||
max-height: 48px; | |||
overflow: hidden; | |||
white-space: nowrap; | |||
text-overflow: ellipsis; | |||
&__info { | |||
position: absolute; | |||
top: 10px; | |||
left: 10px; | |||
} | |||
dt { | |||
color: $darker-text-color; | |||
&__image { | |||
overflow: hidden; | |||
height: 145px; | |||
position: relative; | |||
background: darken($ui-base-color, 4%); | |||
width: 120px; | |||
flex: 0 0 auto; | |||
font-weight: 500; | |||
img { | |||
object-fit: cover; | |||
display: block; | |||
width: 100%; | |||
height: 100%; | |||
margin: 0; | |||
} | |||
} | |||
dd { | |||
flex: 1 1 auto; | |||
color: $primary-text-color; | |||
background: $ui-base-color; | |||
&__bar { | |||
position: relative; | |||
background: lighten($ui-base-color, 4%); | |||
padding: 5px; | |||
border-bottom: 1px solid lighten($ui-base-color, 12%); | |||
&.verified { | |||
border: 1px solid rgba($valid-value-color, 0.5); | |||
background: rgba($valid-value-color, 0.25); | |||
.avatar { | |||
display: block; | |||
flex: 0 0 auto; | |||
width: 90px; | |||
margin-left: -2px; | |||
.account__avatar { | |||
border: 2px solid lighten($ui-base-color, 4%); | |||
} | |||
} | |||
} | |||
&__tabs { | |||
display: flex; | |||
align-items: flex-start; | |||
padding: 7px 5px; | |||
margin-top: -55px; | |||
&__buttons { | |||
display: flex; | |||
align-items: center; | |||
padding-top: 55px; | |||
.icon-button { | |||
border: 1px solid lighten($ui-base-color, 12%); | |||
border-radius: 4px; | |||
box-sizing: content-box; | |||
padding: 2px; | |||
margin: 0 8px; | |||
} | |||
} | |||
&__name { | |||
padding: 5px; | |||
.account-role { | |||
vertical-align: top; | |||
} | |||
.emojione { | |||
width: 22px; | |||
height: 22px; | |||
} | |||
h1 { | |||
font-size: 16px; | |||
line-height: 24px; | |||
color: $primary-text-color; | |||
font-weight: 500; | |||
overflow: hidden; | |||
white-space: nowrap; | |||
text-overflow: ellipsis; | |||
small { | |||
display: block; | |||
font-size: 14px; | |||
color: $darker-text-color; | |||
font-weight: 400; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
} | |||
} | |||
.spacer { | |||
flex: 1 1 auto; | |||
} | |||
} | |||
&__bio { | |||
overflow: hidden; | |||
margin: 0 -5px; | |||
.account__header__content { | |||
padding: 20px 15px; | |||
padding-bottom: 5px; | |||
color: $primary-text-color; | |||
} | |||
.account__header__fields { | |||
margin: 0; | |||
border-top: 1px solid lighten($ui-base-color, 12%); | |||
a { | |||
color: lighten($ui-highlight-color, 8%); | |||
} | |||
dl:first-child .verified { | |||
border-radius: 0 4px 0 0; | |||
} | |||
.verified a { | |||
color: $valid-value-color; | |||
} | |||
} | |||
} | |||
&__extra { | |||
margin-top: 4px; | |||
&__links { | |||
font-size: 14px; | |||
color: $darker-text-color; | |||
a { | |||
display: inline-block; | |||
color: $darker-text-color; | |||
text-decoration: none; | |||
padding: 10px; | |||
padding-top: 20px; | |||
font-weight: 500; | |||
strong { | |||
font-weight: 700; | |||
color: $primary-text-color; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
@@ -677,6 +677,7 @@ | |||
color: $darker-text-color; | |||
text-decoration: none; | |||
padding: 15px; | |||
font-weight: 500; | |||
strong { | |||
font-weight: 700; | |||