Add advanced mode to the account column
This commit is contained in:
parent
4b3f4be472
commit
2bee3e3fdb
24 changed files with 1296 additions and 165 deletions
34
app/javascript/mastodon/actions/featured_tags.js
Normal file
34
app/javascript/mastodon/actions/featured_tags.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import api from '../api';
|
||||
|
||||
export const FEATURED_TAGS_FETCH_REQUEST = 'FEATURED_TAGS_FETCH_REQUEST';
|
||||
export const FEATURED_TAGS_FETCH_SUCCESS = 'FEATURED_TAGS_FETCH_SUCCESS';
|
||||
export const FEATURED_TAGS_FETCH_FAIL = 'FEATURED_TAGS_FETCH_FAIL';
|
||||
|
||||
export const fetchFeaturedTags = (id) => (dispatch, getState) => {
|
||||
if (getState().getIn(['user_lists', 'featured_tags', id, 'items'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchFeaturedTagsRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}/featured_tags`)
|
||||
.then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data)))
|
||||
.catch(err => dispatch(fetchFeaturedTagsFail(id, err)));
|
||||
};
|
||||
|
||||
export const fetchFeaturedTagsRequest = (id) => ({
|
||||
type: FEATURED_TAGS_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const fetchFeaturedTagsSuccess = (id, tags) => ({
|
||||
type: FEATURED_TAGS_FETCH_SUCCESS,
|
||||
id,
|
||||
tags,
|
||||
});
|
||||
|
||||
export const fetchFeaturedTagsFail = (id, error) => ({
|
||||
type: FEATURED_TAGS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
|
@ -182,9 +182,9 @@ export const expandLimitedTimeline = ({ maxId, visibilities } = {}, done
|
|||
export const expandPublicTimeline = ({ maxId, onlyMedia, withoutMedia, withoutBot, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, without_bot: !!withoutBot }, done);
|
||||
export const expandDomainTimeline = (domain, { maxId, onlyMedia, withoutMedia, withoutBot } = {}, done = noOp) => expandTimeline(`domain${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}:${domain}`, '/api/v1/timelines/public', { local: false, domain: domain, max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, without_bot: !!withoutBot }, done);
|
||||
export const expandGroupTimeline = (id, { maxId, onlyMedia, withoutMedia, tagged } = {}, done = noOp) => expandTimeline(`group:${id}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/timelines/group/${id}`, { max_id: maxId, only_media: !!onlyMedia, without_media: !!withoutMedia, tagged: tagged }, done);
|
||||
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
||||
export const expandAccountTimeline = (accountId, { maxId, withReplies, withoutReblogs, tagged } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${withoutReblogs ? ':without_reblogs' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, exclude_reblogs: withoutReblogs, tagged, max_id: maxId });
|
||||
export const expandAccountCoversations = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:conversations`, `/api/v1/accounts/${accountId}/conversations`, { max_id: maxId });
|
||||
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
||||
export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
|
||||
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
|
||||
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
|
||||
|
|
|
@ -10,6 +10,7 @@ import ScrollableList from './scrollable_list';
|
|||
import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
|
||||
import { isIOS } from 'mastodon/is_mobile';
|
||||
import { showReloadButton } from '../initial_state';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
export default class StatusList extends ImmutablePureComponent {
|
||||
|
||||
|
@ -112,7 +113,7 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
showCard={showCard}
|
||||
/>
|
||||
))
|
||||
) : null;
|
||||
) : ImmutableList();
|
||||
|
||||
if (scrollableContent && featuredStatusIds) {
|
||||
scrollableContent = featuredStatusIds.map(statusId => (
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Permalink from 'mastodon/components/permalink';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
const messages = defineMessages({
|
||||
hashtag_all: { id: 'account.hashtag_all', defaultMessage: 'All' },
|
||||
hashtag_all_description: { id: 'account.hashtag_all_description', defaultMessage: 'All posts (deselect hashtags)' },
|
||||
hashtag_select_description: { id: 'account.hashtag_select_description', defaultMessage: 'Select hashtag #{name}' },
|
||||
statuses_counter: { id: 'account.statuses_counter', defaultMessage: '{count, plural, one {{counter} Post} other {{counter} Posts}}' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { account }) => ({
|
||||
featuredTags: state.getIn(['user_lists', 'featured_tags', account.get('id'), 'items'], ImmutableList()),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class FeaturedTags extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
featuredTags: ImmutablePropTypes.list,
|
||||
tagged: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account, featuredTags, tagged, intl } = this.props;
|
||||
|
||||
if (!account || featuredTags.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const suspended = account.get('suspended');
|
||||
const accountId = account.get('id');
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', 'advanced', { inactive: !!account.get('moved') })}>
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__extra__hashtag-links'>
|
||||
<Permalink key='all' className={classNames('account__hashtag-link', { active: !tagged })} title={intl.formatMessage(messages.hashtag_all_description)} href={account.get('url')} to={`/accounts/${accountId}/posts`}>{intl.formatMessage(messages.hashtag_all)}</Permalink>
|
||||
{!suspended && featuredTags.map(featuredTag => {
|
||||
const name = featuredTag.get('name');
|
||||
const url = featuredTag.get('url');
|
||||
const to = `/accounts/${accountId}/posts/${name}`;
|
||||
const desc = intl.formatMessage(messages.hashtag_select_description, { name });
|
||||
const count = featuredTag.get('statuses_count');
|
||||
|
||||
return (
|
||||
<Permalink key={`#${name}`} className={classNames('account__hashtag-link', { active: this.context.router.history.location.pathname === to })} title={desc} href={url} to={to}>
|
||||
#{name} <span title={intl.formatMessage(messages.statuses_counter, { count: count, counter: intl.formatNumber(count) })}>({<ShortNumber value={count} />})</span>
|
||||
</Permalink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -93,6 +93,7 @@ class Header extends ImmutablePureComponent {
|
|||
onEditAccountNote: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hideProfile: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
openEditProfile = () => {
|
||||
|
@ -154,7 +155,7 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, domain, identity_proofs } = this.props;
|
||||
const { account, intl, domain, identity_proofs, hideProfile } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
|
@ -390,60 +391,62 @@ class Header extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__bio'>
|
||||
{(fields.size > 0 || identity_proofs.size > 0) && (
|
||||
<div className='account__header__fields'>
|
||||
{identity_proofs.map((proof, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} />
|
||||
{!hideProfile && (
|
||||
<div className='account__header__bio'>
|
||||
{(fields.size > 0 || identity_proofs.size > 0) && (
|
||||
<div className='account__header__fields'>
|
||||
{identity_proofs.map((proof, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} />
|
||||
|
||||
<dd className='verified'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener noreferrer'><span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
||||
<Icon id='check' className='verified__mark' />
|
||||
</span></a>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener noreferrer'><span dangerouslySetInnerHTML={{ __html: ' '+proof.get('provider_username') }} /></a>
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
|
||||
<dd className='verified'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener noreferrer'><span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
||||
<Icon id='check' className='verified__mark' />
|
||||
</span></a>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener noreferrer'><span dangerouslySetInnerHTML={{ __html: ' '+proof.get('provider_username') }} /></a>
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
|
||||
|
||||
<dd className={`${pair.get('verified_at') ? 'verified' : ''} translate`} 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>
|
||||
))}
|
||||
<dd className={`${pair.get('verified_at') ? 'verified' : ''} translate`} 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('id') !== me && !suspended && <AccountNoteContainer account={account} />}
|
||||
|
||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
||||
|
||||
<div className='account__header__personal--wrapper'>
|
||||
<table className='account__header__personal'>
|
||||
<tbody>
|
||||
{location && <tr>
|
||||
<th><Icon id='map-marker' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.location' defaultMessage='Location' /></th>
|
||||
<td>{location}</td>
|
||||
</tr>}
|
||||
{birthday && <tr>
|
||||
<th><Icon id='birthday-cake' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.birthday' defaultMessage='Birthday' /></th>
|
||||
<td><FormattedDate value={birthday} hour12={false} year='numeric' month='short' day='2-digit' />(<FormattedMessage id='account.age' defaultMessage='{age} years old}' values={{age: age(birthday)}} />)</td>
|
||||
</tr>}
|
||||
<tr>
|
||||
<th><Icon id='calendar' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.joined' defaultMessage='Joined' /></th>
|
||||
<td><FormattedDate value={joined} hour12={false} year='numeric' month='short' day='2-digit' /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{account.get('id') !== me && !suspended && <AccountNoteContainer account={account} />}
|
||||
|
||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
||||
|
||||
<div className='account__header__personal--wrapper'>
|
||||
<table className='account__header__personal'>
|
||||
<tbody>
|
||||
{location && <tr>
|
||||
<th><Icon id='map-marker' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.location' defaultMessage='Location' /></th>
|
||||
<td>{location}</td>
|
||||
</tr>}
|
||||
{birthday && <tr>
|
||||
<th><Icon id='birthday-cake' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.birthday' defaultMessage='Birthday' /></th>
|
||||
<td><FormattedDate value={birthday} hour12={false} year='numeric' month='short' day='2-digit' />(<FormattedMessage id='account.age' defaultMessage='{age} years old}' values={{age: age(birthday)}} />)</td>
|
||||
</tr>}
|
||||
<tr>
|
||||
<th><Icon id='calendar' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.joined' defaultMessage='Joined' /></th>
|
||||
<td><FormattedDate value={joined} hour12={false} year='numeric' month='short' day='2-digit' /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!suspended && (
|
||||
{!hideProfile && !suspended && (
|
||||
<div className='account__header__extra__links'>
|
||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}`} title={hide_statuses_count ? intl.formatMessage(messages.secret) : intl.formatNumber(account.get('statuses_count'))}>
|
||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}/posts`} title={hide_statuses_count ? intl.formatMessage(messages.secret) : intl.formatNumber(account.get('statuses_count'))}>
|
||||
<ShortNumber
|
||||
hide={hide_statuses_count}
|
||||
value={account.get('statuses_count')}
|
||||
|
|
|
@ -0,0 +1,366 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Button from 'mastodon/components/button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { autoPlayGif, me, isStaff, show_followed_by, follow_button_to_list_adder } from 'mastodon/initial_state';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import Avatar from 'mastodon/components/avatar';
|
||||
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
unsubscribe: { id: 'account.unsubscribe', defaultMessage: 'Unsubscribe' },
|
||||
subscribe: { id: 'account.subscribe', defaultMessage: 'Subscribe' },
|
||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' },
|
||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
|
||||
conversations: { id: 'account.conversations', defaultMessage: 'Show conversations with @{name}' },
|
||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
||||
direct: { id: 'account.direct', defaultMessage: 'Direct message @{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: 'Block domain {domain}' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
||||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
||||
enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' },
|
||||
disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' },
|
||||
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' },
|
||||
circles: { id: 'navigation_bar.circles', defaultMessage: 'Circles' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked 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' },
|
||||
add_or_remove_from_circle: { id: 'account.add_or_remove_from_circle', defaultMessage: 'Add or Remove from circles' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class HeaderCommon extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onSubscribe: PropTypes.func.isRequired,
|
||||
onAddToList: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
onDirect: PropTypes.func.isRequired,
|
||||
onConversations: PropTypes.func.isRequired,
|
||||
onReblogToggle: PropTypes.func.isRequired,
|
||||
onNotifyToggle: 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,
|
||||
onEditAccountNote: 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)\/?$/);
|
||||
}
|
||||
|
||||
handleMouseEnter = ({ currentTarget }) => {
|
||||
if (autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
emoji.src = emoji.getAttribute('data-original');
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseLeave = ({ currentTarget }) => {
|
||||
if (autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
emoji.src = emoji.getAttribute('data-static');
|
||||
}
|
||||
}
|
||||
|
||||
handleFollow = (e) => {
|
||||
if ((e && e.shiftKey) ^ !follow_button_to_list_adder) {
|
||||
this.props.onFollow(this.props.account);
|
||||
} else {
|
||||
this.props.onAddToList(this.props.account);
|
||||
}
|
||||
}
|
||||
|
||||
handleSubscribe = (e) => {
|
||||
if ((e && e.shiftKey) ^ !follow_button_to_list_adder) {
|
||||
this.props.onSubscribe(this.props.account);
|
||||
} else {
|
||||
this.props.onAddToList(this.props.account);
|
||||
}
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, domain } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const suspended = account.get('suspended');
|
||||
|
||||
let info = [];
|
||||
let actionBtn = '';
|
||||
let bellBtn = '';
|
||||
let lockedIcon = '';
|
||||
let menu = [];
|
||||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
||||
info.push(<span key='followed_by' className='relationship-tag'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>);
|
||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
|
||||
info.push(<span key='blocked' className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>);
|
||||
}
|
||||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
|
||||
info.push(<span key='muted' className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
|
||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
|
||||
info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
|
||||
}
|
||||
|
||||
if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
|
||||
bellBtn = <IconButton icon='bell-o' size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
|
||||
}
|
||||
|
||||
if (me !== account.get('id')) {
|
||||
if (!account.get('relationship')) { // Wait until the relationship is loaded
|
||||
actionBtn = '';
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
actionBtn = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
|
||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
|
||||
}
|
||||
} else {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
|
||||
}
|
||||
|
||||
if (account.get('moved') && !account.getIn(['relationship', 'following'])) {
|
||||
actionBtn = '';
|
||||
}
|
||||
|
||||
if (account.get('locked')) {
|
||||
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);
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.conversations, { name: account.get('username') }), action: this.props.onConversations });
|
||||
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({ text: intl.formatMessage(messages.circles), to: '/circles' });
|
||||
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', 'muting'])) {
|
||||
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(null);
|
||||
}
|
||||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
||||
menu.push(null);
|
||||
|
||||
if (account.getIn(['relationship', 'followed_by'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_circle), action: this.props.onAddToCircle });
|
||||
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 displayNameHtml = { __html: account.get('display_name_html') };
|
||||
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
||||
|
||||
let badge;
|
||||
|
||||
if (account.get('bot')) {
|
||||
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>);
|
||||
} else if (account.get('group')) {
|
||||
badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>);
|
||||
} else {
|
||||
badge = null;
|
||||
}
|
||||
|
||||
const following = account.getIn(['relationship', 'following']);
|
||||
const delivery = account.getIn(['relationship', 'delivery_following']);
|
||||
const followed_by = account.getIn(['relationship', 'followed_by']) && show_followed_by;
|
||||
const subscribing = account.getIn(['relationship', 'subscribing'], new Map).size > 0;
|
||||
const subscribing_home = account.getIn(['relationship', 'subscribing', '-1'], new Map).size > 0;
|
||||
const blockd_by = account.getIn(['relationship', 'blocked_by']);
|
||||
let buttons;
|
||||
|
||||
if(me !== account.get('id') && !blockd_by) {
|
||||
let following_buttons, subscribing_buttons;
|
||||
if(!account.get('moved') || subscribing) {
|
||||
subscribing_buttons = (
|
||||
<IconButton
|
||||
icon='rss-square'
|
||||
title={intl.formatMessage(
|
||||
subscribing ? messages.unsubscribe : messages.subscribe
|
||||
)}
|
||||
onClick={this.handleSubscribe}
|
||||
active={subscribing}
|
||||
no_delivery={subscribing && !subscribing_home}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if(!account.get('moved') || following) {
|
||||
following_buttons = (
|
||||
<IconButton
|
||||
icon={following ? 'user-times' : 'user-plus'}
|
||||
title={intl.formatMessage(
|
||||
following ? messages.unfollow : messages.follow
|
||||
)}
|
||||
onClick={this.handleFollow}
|
||||
active={following}
|
||||
passive={followed_by}
|
||||
no_delivery={following && !delivery}
|
||||
/>
|
||||
);
|
||||
}
|
||||
buttons = <Fragment>{subscribing_buttons}{following_buttons}</Fragment>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', 'advanced', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className='account__header__image'>
|
||||
<div className='account__header__info'>
|
||||
{!suspended && info}
|
||||
</div>
|
||||
|
||||
<img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />
|
||||
</div>
|
||||
|
||||
<div className='account__header__bar'>
|
||||
<div className='account__header__tabs'>
|
||||
<a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'>
|
||||
<Avatar account={account} size={90} />
|
||||
</a>
|
||||
|
||||
<div className='spacer' />
|
||||
|
||||
{!suspended && (
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{actionBtn}
|
||||
{bellBtn}
|
||||
|
||||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='account__header__tabs__name'>
|
||||
<h1>
|
||||
<span dangerouslySetInnerHTML={displayNameHtml} /> {badge}
|
||||
<small>@{acct} {lockedIcon}</small>
|
||||
</h1>
|
||||
<div className='account__header__tabs__name__relationship account__relationship'>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import AccountNoteContainer from '../containers/account_note_container';
|
||||
import age from 's-age';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
|
||||
});
|
||||
|
||||
const dateFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
};
|
||||
|
||||
export default @injectIntl
|
||||
class HeaderExtra extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
identity_proofs: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
isStatusesPageActive = (match, location) => {
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !location.pathname.match(/\/(followers|following)\/?$/);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, identity_proofs } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const suspended = account.get('suspended');
|
||||
|
||||
const content = { __html: account.get('note_emojified') };
|
||||
const fields = account.get('fields');
|
||||
|
||||
const location = account.getIn(['other_settings', 'location']);
|
||||
const birthday = account.getIn(['other_settings', 'birthday']);
|
||||
const joined = account.get('created_at');
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', 'advanced', { inactive: !!account.get('moved') })}>
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__bio'>
|
||||
{(fields.size > 0 || identity_proofs.size > 0) && (
|
||||
<div className='account__header__fields'>
|
||||
{identity_proofs.map((proof, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: proof.get('provider') }} />
|
||||
|
||||
<dd className='verified'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener noreferrer'><span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
||||
<Icon id='check' className='verified__mark' />
|
||||
</span></a>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener noreferrer'><span dangerouslySetInnerHTML={{ __html: ' '+proof.get('provider_username') }} /></a>
|
||||
</dd>
|
||||
</dl>
|
||||
))}
|
||||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
|
||||
|
||||
<dd className={`${pair.get('verified_at') ? 'verified' : ''} translate`} 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('id') !== me && !suspended && <AccountNoteContainer account={account} />}
|
||||
|
||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
||||
|
||||
<div className='account__header__personal--wrapper'>
|
||||
<table className='account__header__personal'>
|
||||
<tbody>
|
||||
{location && <tr>
|
||||
<th><Icon id='map-marker' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.location' defaultMessage='Location' /></th>
|
||||
<td>{location}</td>
|
||||
</tr>}
|
||||
{birthday && <tr>
|
||||
<th><Icon id='birthday-cake' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.birthday' defaultMessage='Birthday' /></th>
|
||||
<td><FormattedDate value={birthday} hour12={false} year='numeric' month='short' day='2-digit' />(<FormattedMessage id='account.age' defaultMessage='{age} years old}' values={{ age: age(birthday) }} />)</td>
|
||||
</tr>}
|
||||
<tr>
|
||||
<th><Icon id='calendar' fixedWidth aria-hidden='true' /> <FormattedMessage id='account.joined' defaultMessage='Joined' /></th>
|
||||
<td><FormattedDate value={joined} hour12={false} year='numeric' month='short' day='2-digit' /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { counterRenderer } from 'mastodon/components/common_counter';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
secret: { id: 'account.secret', defaultMessage: 'Secret' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class HeaderExtraLinks extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
isStatusesPageActive = (match, location) => {
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !location.pathname.match(/\/(followers|following|subscribing)\/?$/);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, intl } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const suspended = account.get('suspended');
|
||||
|
||||
const hide_statuses_count = account.getIn(['other_settings', 'hide_statuses_count'], false);
|
||||
const hide_following_count = account.getIn(['other_settings', 'hide_following_count'], false);
|
||||
const hide_followers_count = account.getIn(['other_settings', 'hide_followers_count'], false);
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', 'advanced', { inactive: !!account.get('moved') })}>
|
||||
<div className='account__header__extra'>
|
||||
{!suspended && (
|
||||
<div className='account__header__extra__links'>
|
||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}/posts`} title={hide_statuses_count ? intl.formatMessage(messages.secret) : intl.formatNumber(account.get('statuses_count'))}>
|
||||
<ShortNumber
|
||||
hide={hide_statuses_count}
|
||||
value={account.get('statuses_count')}
|
||||
renderer={counterRenderer('statuses')}
|
||||
/>
|
||||
</NavLink>
|
||||
|
||||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/following`} title={hide_following_count ? intl.formatMessage(messages.secret) : intl.formatNumber(account.get('following_count'))}>
|
||||
<ShortNumber
|
||||
hide={hide_following_count}
|
||||
value={account.get('following_count')}
|
||||
renderer={counterRenderer('following')}
|
||||
/>
|
||||
</NavLink>
|
||||
|
||||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/followers`} title={hide_followers_count ? intl.formatMessage(messages.secret) : intl.formatNumber(account.get('followers_count'))}>
|
||||
<ShortNumber
|
||||
hide={hide_followers_count}
|
||||
value={account.get('followers_count')}
|
||||
renderer={counterRenderer('followers')}
|
||||
/>
|
||||
</NavLink>
|
||||
|
||||
{ (me === account.get('id')) && (
|
||||
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/subscribing`} title={intl.formatNumber(account.get('subscribing_count'))}>
|
||||
<ShortNumber
|
||||
value={account.get('subscribing_count')}
|
||||
renderer={counterRenderer('subscribers')}
|
||||
/>
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,30 +6,37 @@ import { fetchAccount } from '../../actions/accounts';
|
|||
import { expandAccountCoversations } from '../../actions/timelines';
|
||||
import StatusList from '../../components/status_list';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import Column from '../ui/components/column';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ColumnSettingsContainer from '../account_timeline/containers/column_settings_container';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||
import { new_features_policy } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.account', defaultMessage: 'Account' },
|
||||
});
|
||||
|
||||
const emptyList = ImmutableList();
|
||||
|
||||
const mapStateToProps = (state, { params: { accountId } }) => {
|
||||
return {
|
||||
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
|
||||
remoteUrl: state.getIn(['accounts', accountId, 'url']),
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
statusIds: state.getIn(['timelines', `account:${accountId}:conversations`, 'items'], emptyList),
|
||||
isLoading: state.getIn(['timelines', `account:${accountId}:conversations`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${accountId}:conversations`, 'hasMore']),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
};
|
||||
};
|
||||
const mapStateToProps = (state, { params: { accountId } }) => ({
|
||||
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
|
||||
remoteUrl: state.getIn(['accounts', accountId, 'url']),
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
statusIds: state.getIn(['timelines', `account:${accountId}:conversations`, 'items'], emptyList),
|
||||
isLoading: state.getIn(['timelines', `account:${accountId}:conversations`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${accountId}:conversations`, 'hasMore']),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
|
||||
hideRelation: state.getIn(['settings', 'account', 'other', 'hideRelation'], false),
|
||||
});
|
||||
|
||||
const RemoteHint = ({ url }) => (
|
||||
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.statuses' defaultMessage='Older toots' />} />
|
||||
|
@ -40,6 +47,7 @@ RemoteHint.propTypes = {
|
|||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class AccountConversations extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -49,11 +57,11 @@ class AccountConversations extends ImmutablePureComponent {
|
|||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
withReplies: PropTypes.bool,
|
||||
advancedMode: PropTypes.bool,
|
||||
hideRelation: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
suspended: PropTypes.bool,
|
||||
remote: PropTypes.bool,
|
||||
remoteUrl: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
@ -81,8 +89,16 @@ class AccountConversations extends ImmutablePureComponent {
|
|||
this.props.dispatch(expandAccountCoversations(this.props.params.accountId, { maxId }));
|
||||
}
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl } = this.props;
|
||||
const { statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, hideRelation, intl } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -107,22 +123,26 @@ class AccountConversations extends ImmutablePureComponent {
|
|||
emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
|
||||
} else if (blockedBy) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
||||
} else if (remote && statusIds.isEmpty()) {
|
||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||
} else {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
|
||||
emptyMessage = <FormattedMessage id='empty_column.conversation_unavailable' defaultMessage='No conversation with this user yet' />;
|
||||
}
|
||||
|
||||
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='user'
|
||||
active={false}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={false}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
<StatusList
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} hideProfile hideRelation={hideRelation} hideFeaturedTags />}
|
||||
alwaysPrepend
|
||||
append={remoteMessage}
|
||||
scrollKey='account_conversations'
|
||||
statusIds={(suspended || blockedBy) ? emptyList : statusIds}
|
||||
isLoading={isLoading}
|
||||
|
|
|
@ -5,8 +5,9 @@ import PropTypes from 'prop-types';
|
|||
import { fetchAccount } from 'mastodon/actions/accounts';
|
||||
import { expandAccountMediaTimeline } from '../../actions/timelines';
|
||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButton from 'mastodon/components/column_back_button';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ColumnSettingsContainer from '../account_timeline/containers/column_settings_container';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { getAccountGallery } from 'mastodon/selectors';
|
||||
import MediaItem from './components/media_item';
|
||||
|
@ -15,7 +16,12 @@ import ScrollContainer from 'mastodon/containers/scroll_container';
|
|||
import LoadMore from 'mastodon/components/load_more';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { new_features_policy } from 'mastodon/initial_state';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.account', defaultMessage: 'Account' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||
|
@ -24,6 +30,8 @@ const mapStateToProps = (state, props) => ({
|
|||
hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
|
||||
suspended: state.getIn(['accounts', props.params.accountId, 'suspended'], false),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
|
||||
hideRelation: state.getIn(['settings', 'account', 'other', 'hideRelation'], false),
|
||||
});
|
||||
|
||||
class LoadMoreMedia extends ImmutablePureComponent {
|
||||
|
@ -49,6 +57,7 @@ class LoadMoreMedia extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class AccountGallery extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -58,6 +67,8 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
advancedMode: PropTypes.bool,
|
||||
hideRelation: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
suspended: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
|
@ -125,8 +136,16 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props;
|
||||
const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended, hideRelation, intl } = this.props;
|
||||
const { width } = this.state;
|
||||
|
||||
if (!isAccount) {
|
||||
|
@ -160,12 +179,21 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='user'
|
||||
active={false}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={false}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
<ScrollContainer scrollKey='account_gallery'>
|
||||
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
||||
<HeaderContainer accountId={this.props.params.accountId} />
|
||||
<HeaderContainer accountId={this.props.params.accountId} hideProfile hideRelation={hideRelation} hideFeaturedTags />
|
||||
|
||||
{(suspended || blockedBy) ? (
|
||||
<div className='empty-column-indicator'>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import SettingToggle from '../../notifications/components/setting_toggle';
|
||||
|
||||
export default @injectIntl
|
||||
class ColumnSettings extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
advancedMode: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { settings, advancedMode, onChange } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle settings={settings} settingPath={['other', 'advancedMode']} onChange={onChange} label={<FormattedMessage id='account.column_settings.advanced_mode' defaultMessage='Advanced mode' />} />
|
||||
{advancedMode && (
|
||||
<Fragment>
|
||||
<span className='column-settings__section'><FormattedMessage id='account.column_settings.advanced_settings' defaultMessage='Advanced settings' /></span>
|
||||
|
||||
<SettingToggle settings={settings} settingPath={['other', 'openPostsFirst']} onChange={onChange} label={<FormattedMessage id='account.column_settings.open_posts_first' defaultMessage='Open posts first' />} />
|
||||
<SettingToggle settings={settings} settingPath={['other', 'withoutReblogs']} onChange={onChange} label={<FormattedMessage id='account.column_settings.without_reblogs' defaultMessage='Without boosts' />} />
|
||||
<SettingToggle settings={settings} settingPath={['other', 'showPostsInAbout']} onChange={onChange} label={<FormattedMessage id='account.column_settings.show_posts_in_about' defaultMessage='Show posts in about' />} />
|
||||
<SettingToggle settings={settings} settingPath={['other', 'hideFeaturedTags']} onChange={onChange} label={<FormattedMessage id='account.column_settings.hide_featured_tags' defaultMessage='Hide featuread tags selection' />} />
|
||||
<SettingToggle settings={settings} settingPath={['other', 'hideRelation']} onChange={onChange} label={<FormattedMessage id='account.column_settings.hide_relation' defaultMessage='Hide post and follow counters (except about)' />} />
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,17 @@
|
|||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import InnerHeader from '../../account/components/header';
|
||||
import InnerHeaderCommon from '../../account/components/header_common';
|
||||
import InnerHeaderExtra from '../../account/components/header_extra';
|
||||
import InnerHeaderExtraLinks from '../../account/components/header_extra_links';
|
||||
import FeaturedTags from '../../account/components/featured_tags';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { follow_button_to_list_adder } from 'mastodon/initial_state';
|
||||
import MovedNote from './moved_note';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class Header extends ImmutablePureComponent {
|
||||
|
||||
|
@ -28,10 +33,18 @@ export default class Header extends ImmutablePureComponent {
|
|||
onEndorseToggle: PropTypes.func.isRequired,
|
||||
onAddToList: PropTypes.func.isRequired,
|
||||
onAddToCircle: PropTypes.func.isRequired,
|
||||
hideTabs: PropTypes.bool,
|
||||
hideRelation: PropTypes.bool,
|
||||
hideProfile: PropTypes.bool,
|
||||
advancedMode: PropTypes.bool,
|
||||
tagged: PropTypes.string,
|
||||
hideFeaturedTags: PropTypes.bool,
|
||||
domain: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
hideProfile: false,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
@ -117,7 +130,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, hideTabs, identity_proofs } = this.props;
|
||||
const { account, hideRelation, identity_proofs, hideProfile, advancedMode, tagged, hideFeaturedTags } = this.props;
|
||||
|
||||
if (account === null) {
|
||||
return null;
|
||||
|
@ -127,34 +140,72 @@ export default class Header extends ImmutablePureComponent {
|
|||
<div className='account-timeline__header'>
|
||||
{account.get('moved') && <MovedNote from={account} to={account.get('moved')} />}
|
||||
|
||||
<InnerHeader
|
||||
account={account}
|
||||
identity_proofs={identity_proofs}
|
||||
onFollow={this.handleFollow}
|
||||
onSubscribe={this.handleSubscribe}
|
||||
onBlock={this.handleBlock}
|
||||
onMention={this.handleMention}
|
||||
onDirect={this.handleDirect}
|
||||
onConversations={this.handleConversations}
|
||||
onReblogToggle={this.handleReblogToggle}
|
||||
onNotifyToggle={this.handleNotifyToggle}
|
||||
onReport={this.handleReport}
|
||||
onMute={this.handleMute}
|
||||
onBlockDomain={this.handleBlockDomain}
|
||||
onUnblockDomain={this.handleUnblockDomain}
|
||||
onEndorseToggle={this.handleEndorseToggle}
|
||||
onAddToList={this.handleAddToList}
|
||||
onAddToCircle={this.handleAddToCircle}
|
||||
onEditAccountNote={this.handleEditAccountNote}
|
||||
domain={this.props.domain}
|
||||
/>
|
||||
{advancedMode ? (
|
||||
<Fragment>
|
||||
<InnerHeaderCommon
|
||||
account={account}
|
||||
onFollow={this.handleFollow}
|
||||
onSubscribe={this.handleSubscribe}
|
||||
onBlock={this.handleBlock}
|
||||
onMention={this.handleMention}
|
||||
onDirect={this.handleDirect}
|
||||
onConversations={this.handleConversations}
|
||||
onReblogToggle={this.handleReblogToggle}
|
||||
onNotifyToggle={this.handleNotifyToggle}
|
||||
onReport={this.handleReport}
|
||||
onMute={this.handleMute}
|
||||
onBlockDomain={this.handleBlockDomain}
|
||||
onUnblockDomain={this.handleUnblockDomain}
|
||||
onEndorseToggle={this.handleEndorseToggle}
|
||||
onAddToList={this.handleAddToList}
|
||||
onAddToCircle={this.handleAddToCircle}
|
||||
onEditAccountNote={this.handleEditAccountNote}
|
||||
domain={this.props.domain}
|
||||
/>
|
||||
|
||||
{!hideTabs && (
|
||||
<div className='account__section-headline'>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots and replies' /></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
|
||||
</div>
|
||||
<div className='account__section-headline with-short-label'>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/posts`}><Icon id='home' fixedWidth /><span className='account__section-headline__short-label'><FormattedMessage id='account.short.posts' defaultMessage='Posts' children={msg=> <>{msg}</>} /></span></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><Icon id='reply-all' fixedWidth /><span className='account__section-headline__short-label'><FormattedMessage id='account.short.with_replies' defaultMessage='Posts & Replies' children={msg=> <>{msg}</>} /></span></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/media`}><Icon id='picture-o' fixedWidth /><span className='account__section-headline__short-label'><FormattedMessage id='account.short.media' defaultMessage='Media' children={msg=> <>{msg}</>} /></span></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/conversations`}><Icon id='at' fixedWidth /><span className='account__section-headline__short-label'><FormattedMessage id='account.short.conversations' defaultMessage='Conversations' children={msg=> <>{msg}</>} /></span></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/about`}><Icon id='address-card-o' fixedWidth /><span className='account__section-headline__short-label'><FormattedMessage id='account.short.about' defaultMessage='About' children={msg=> <>{msg}</>} /></span></NavLink>
|
||||
</div>
|
||||
|
||||
{hideProfile && !hideFeaturedTags && <FeaturedTags account={account} tagged={tagged} />}
|
||||
{!hideRelation && <InnerHeaderExtraLinks account={account} />}
|
||||
{!hideProfile && <InnerHeaderExtra account={account} identity_proofs={identity_proofs} />}
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<InnerHeader
|
||||
account={account}
|
||||
identity_proofs={identity_proofs}
|
||||
onFollow={this.handleFollow}
|
||||
onSubscribe={this.handleSubscribe}
|
||||
onBlock={this.handleBlock}
|
||||
onMention={this.handleMention}
|
||||
onDirect={this.handleDirect}
|
||||
onConversations={this.handleConversations}
|
||||
onReblogToggle={this.handleReblogToggle}
|
||||
onNotifyToggle={this.handleNotifyToggle}
|
||||
onReport={this.handleReport}
|
||||
onMute={this.handleMute}
|
||||
onBlockDomain={this.handleBlockDomain}
|
||||
onUnblockDomain={this.handleUnblockDomain}
|
||||
onEndorseToggle={this.handleEndorseToggle}
|
||||
onAddToList={this.handleAddToList}
|
||||
onAddToCircle={this.handleAddToCircle}
|
||||
onEditAccountNote={this.handleEditAccountNote}
|
||||
domain={this.props.domain}
|
||||
hideProfile={hideProfile}
|
||||
/>
|
||||
|
||||
<div className='account__section-headline'>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots and replies' /></NavLink>
|
||||
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ColumnSettings from '../components/column_settings';
|
||||
import { changeSetting } from '../../../actions/settings';
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
settings: state.getIn(['settings', 'account']),
|
||||
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], false),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
onChange (key, checked) {
|
||||
dispatch(changeSetting(['account', ...key], checked));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
|
|
@ -34,10 +34,12 @@ const messages = defineMessages({
|
|||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = (state, { accountId }) => ({
|
||||
const mapStateToProps = (state, { accountId, hideTabs, hideProfile }) => ({
|
||||
account: getAccount(state, accountId),
|
||||
domain: state.getIn(['meta', 'domain']),
|
||||
identity_proofs: state.getIn(['identity_proofs', accountId], ImmutableList()),
|
||||
hideProfile: hideTabs || hideProfile,
|
||||
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], false),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
|
|
|
@ -6,33 +6,54 @@ import { fetchAccount } from '../../actions/accounts';
|
|||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
|
||||
import StatusList from '../../components/status_list';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import Column from '../ui/components/column';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import HeaderContainer from './containers/header_container';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { me, new_features_policy } from 'mastodon/initial_state';
|
||||
import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines';
|
||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
||||
|
||||
const emptyList = ImmutableList();
|
||||
|
||||
const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
|
||||
const path = withReplies ? `${accountId}:with_replies` : accountId;
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.account', defaultMessage: 'Account' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { params: { accountId, tagged }, about, withReplies, posts }) => {
|
||||
posts = tagged ? false : posts;
|
||||
withReplies = tagged ? true : withReplies;
|
||||
const advancedMode = state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true);
|
||||
const hideFeaturedTags = state.getIn(['settings', 'account', 'other', 'hideFeaturedTags'], false);
|
||||
const withoutReblogs = advancedMode && state.getIn(['settings', 'account', 'other', 'withoutReblogs'], false);
|
||||
const showPostsInAbout = state.getIn(['settings', 'account', 'other', 'showPostsInAbout'], true);
|
||||
const hideRelation = state.getIn(['settings', 'account', 'other', 'hideRelation'], false);
|
||||
const path = `${accountId}${withReplies ? ':with_replies' : ''}${withoutReblogs ? ':without_reblogs' : ''}${tagged ? `:${tagged}` : ''}`;
|
||||
|
||||
return {
|
||||
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
|
||||
remoteUrl: state.getIn(['accounts', accountId, 'url']),
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
|
||||
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList),
|
||||
statusIds: advancedMode && about && !showPostsInAbout ? emptyList : state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
|
||||
featuredStatusIds: (withReplies || posts) ? emptyList : state.getIn(['timelines', `account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, 'items'], emptyList),
|
||||
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
advancedMode,
|
||||
hideFeaturedTags,
|
||||
posts,
|
||||
withReplies,
|
||||
withoutReblogs,
|
||||
showPostsInAbout,
|
||||
hideRelation,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -45,16 +66,24 @@ RemoteHint.propTypes = {
|
|||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class AccountTimeline extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
statusIds: ImmutablePropTypes.list,
|
||||
featuredStatusIds: ImmutablePropTypes.list,
|
||||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
about: PropTypes.bool,
|
||||
withReplies: PropTypes.bool,
|
||||
withoutReblogs: PropTypes.bool,
|
||||
posts: PropTypes.bool,
|
||||
advancedMode: PropTypes.bool,
|
||||
hideFeaturedTags: PropTypes.bool,
|
||||
hideRelation: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
suspended: PropTypes.bool,
|
||||
|
@ -63,17 +92,29 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
about: false,
|
||||
withReplies: false,
|
||||
posts: false,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { params: { accountId }, withReplies, dispatch } = this.props;
|
||||
const { params: { accountId, tagged }, about, withReplies, posts, advancedMode, hideFeaturedTags, withoutReblogs, showPostsInAbout, dispatch } = this.props;
|
||||
|
||||
dispatch(fetchAccount(accountId));
|
||||
dispatch(fetchAccountIdentityProofs(accountId));
|
||||
|
||||
if (!withReplies) {
|
||||
dispatch(expandAccountFeaturedTimeline(accountId));
|
||||
if (!withReplies && !posts) {
|
||||
dispatch(expandAccountFeaturedTimeline(accountId, { tagged }));
|
||||
}
|
||||
|
||||
dispatch(expandAccountTimeline(accountId, { withReplies }));
|
||||
if (!about || !advancedMode || showPostsInAbout) {
|
||||
dispatch(expandAccountTimeline(accountId, { withReplies, tagged, withoutReblogs }));
|
||||
}
|
||||
|
||||
if (tagged || !hideFeaturedTags) {
|
||||
dispatch(fetchFeaturedTags(accountId));
|
||||
}
|
||||
|
||||
if (accountId === me) {
|
||||
dispatch(connectTimeline(`account:${me}`));
|
||||
|
@ -83,18 +124,29 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
componentWillReceiveProps (nextProps) {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
|
||||
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId)
|
||||
|| (nextProps.params.tagged !== this.props.params.tagged)
|
||||
|| nextProps.withReplies !== this.props.withReplies
|
||||
|| nextProps.withoutReblogs !== this.props.withoutReblogs
|
||||
|| nextProps.showPostsInAbout !== this.props.showPostsInAbout
|
||||
) {
|
||||
dispatch(fetchAccount(nextProps.params.accountId));
|
||||
dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
|
||||
|
||||
if (!nextProps.withReplies) {
|
||||
dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
|
||||
if (!nextProps.withReplies && !nextProps.posts) {
|
||||
dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId, { tagged: nextProps.params.tagged }));
|
||||
}
|
||||
|
||||
dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies }));
|
||||
if (!nextProps.about || nextProps.showPostsInAbout) {
|
||||
dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.withReplies, tagged: nextProps.params.tagged, withoutReblogs: nextProps.withoutReblogs }));
|
||||
}
|
||||
|
||||
if (nextProps.params.tagged || !nextProps.hideFeaturedTags) {
|
||||
dispatch(fetchFeaturedTags(nextProps.params.accountId));
|
||||
}
|
||||
}
|
||||
|
||||
if (nextProps.params.accountId === me && this.props.params.accountId !== me) {
|
||||
if ((!nextProps.about || !this.props.advancedMode) && nextProps.params.accountId === me && this.props.params.accountId !== me) {
|
||||
dispatch(connectTimeline(`account:${me}`));
|
||||
} else if (this.props.params.accountId === me && nextProps.params.accountId !== me) {
|
||||
dispatch(disconnectTimeline(`account:${me}`));
|
||||
|
@ -110,11 +162,19 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies }));
|
||||
this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies, tagged: this.props.params.tagged, withoutReblogs: this.props.withoutReblogs }));
|
||||
}
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl } = this.props;
|
||||
const { intl, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl, about, withReplies, posts, advancedMode, hideFeaturedTags, showPostsInAbout, hideRelation } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -139,28 +199,39 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
|
||||
} else if (blockedBy) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
||||
} else if (about && advancedMode && featuredStatusIds.isEmpty()) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.pinned_unavailable' defaultMessage='Pinned posts unavailable' />;
|
||||
} else if (remote && statusIds.isEmpty()) {
|
||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||
} else {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
|
||||
}
|
||||
|
||||
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
|
||||
const remoteMessage = (!about && remote) ? <RemoteHint url={remoteUrl} /> : null;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='user'
|
||||
active={false}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={false}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
<StatusList
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} tagged={this.props.params.tagged} hideProfile={withReplies || posts || !!this.props.params.tagged} hideRelation={!about && hideRelation} hideFeaturedTags={hideFeaturedTags} />}
|
||||
alwaysPrepend
|
||||
append={remoteMessage}
|
||||
scrollKey='account_timeline'
|
||||
statusIds={(suspended || blockedBy) ? emptyList : statusIds}
|
||||
featuredStatusIds={featuredStatusIds}
|
||||
isLoading={isLoading}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
hasMore={about && advancedMode && !showPostsInAbout ? null : hasMore}
|
||||
onLoadMore={about && advancedMode && !showPostsInAbout ? null : this.handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
timelineId='account'
|
||||
|
|
|
@ -10,14 +10,20 @@ import {
|
|||
fetchFollowers,
|
||||
expandFollowers,
|
||||
} from '../../actions/accounts';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ColumnSettingsContainer from '../account_timeline/containers/column_settings_container';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||
import { new_features_policy } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.account', defaultMessage: 'Account' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])),
|
||||
|
@ -27,6 +33,7 @@ const mapStateToProps = (state, props) => ({
|
|||
hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
|
||||
});
|
||||
|
||||
const RemoteHint = ({ url }) => (
|
||||
|
@ -38,12 +45,14 @@ RemoteHint.propTypes = {
|
|||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Followers extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
advancedMode: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
|
@ -71,8 +80,16 @@ class Followers extends ImmutablePureComponent {
|
|||
this.props.dispatch(expandFollowers(this.props.params.accountId));
|
||||
}, 300, { leading: true });
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
|
||||
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, intl } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -103,15 +120,24 @@ class Followers extends ImmutablePureComponent {
|
|||
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='user'
|
||||
active={false}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={false}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='followers'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} hideProfile hideFeaturedTags />}
|
||||
alwaysPrepend
|
||||
append={remoteMessage}
|
||||
emptyMessage={emptyMessage}
|
||||
|
|
|
@ -10,14 +10,20 @@ import {
|
|||
fetchFollowing,
|
||||
expandFollowing,
|
||||
} from '../../actions/accounts';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ColumnSettingsContainer from '../account_timeline/containers/column_settings_container';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||
import { new_features_policy } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.account', defaultMessage: 'Account' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])),
|
||||
|
@ -27,6 +33,7 @@ const mapStateToProps = (state, props) => ({
|
|||
hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
|
||||
});
|
||||
|
||||
const RemoteHint = ({ url }) => (
|
||||
|
@ -38,12 +45,14 @@ RemoteHint.propTypes = {
|
|||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Following extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
advancedMode: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
|
@ -71,8 +80,16 @@ class Following extends ImmutablePureComponent {
|
|||
this.props.dispatch(expandFollowing(this.props.params.accountId));
|
||||
}, 300, { leading: true });
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl } = this.props;
|
||||
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, intl } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -103,15 +120,24 @@ class Following extends ImmutablePureComponent {
|
|||
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='user'
|
||||
active={false}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={false}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='following'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} hideProfile hideFeaturedTags />}
|
||||
alwaysPrepend
|
||||
append={remoteMessage}
|
||||
emptyMessage={emptyMessage}
|
||||
|
|
|
@ -10,13 +10,19 @@ import {
|
|||
fetchSubscribing,
|
||||
expandSubscribing,
|
||||
} from '../../actions/accounts';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ColumnSettingsContainer from '../account_timeline/containers/column_settings_container';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import { new_features_policy } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.account', defaultMessage: 'Account' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||
|
@ -24,15 +30,18 @@ const mapStateToProps = (state, props) => ({
|
|||
hasMore: !!state.getIn(['user_lists', 'subscribing', props.params.accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'subscribing', props.params.accountId, 'isLoading'], true),
|
||||
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
|
||||
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Subscribing extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
advancedMode: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
|
@ -57,8 +66,16 @@ class Subscribing extends ImmutablePureComponent {
|
|||
this.props.dispatch(expandSubscribing(this.props.params.accountId));
|
||||
}, 300, { leading: true });
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading } = this.props;
|
||||
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, intl } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -79,15 +96,24 @@ class Subscribing extends ImmutablePureComponent {
|
|||
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.subscribes.empty' defaultMessage="This user doesn't subscribe anyone yet." />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='user'
|
||||
active={false}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={false}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='subscribing'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
|
||||
prepend={<HeaderContainer accountId={this.props.params.accountId} hideProfile hideFeaturedTags />}
|
||||
alwaysPrepend
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
|
|
|
@ -87,6 +87,8 @@ const mapStateToProps = state => ({
|
|||
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
|
||||
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
|
||||
firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
|
||||
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], false),
|
||||
openPostsFirst: state.getIn(['settings', 'account', 'other', 'openPostsFirst'], false),
|
||||
visibilities: getHomeVisibilities(state),
|
||||
});
|
||||
|
||||
|
@ -129,6 +131,8 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
children: PropTypes.node,
|
||||
location: PropTypes.object,
|
||||
mobile: PropTypes.bool,
|
||||
advancedMode: PropTypes.bool,
|
||||
openPostsFirst: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
|
@ -159,13 +163,15 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { children, mobile } = this.props;
|
||||
const { children, mobile, advancedMode, openPostsFirst } = this.props;
|
||||
const redirect = mobile ? <Redirect from='/' to='/timelines/home' exact /> : enableEmptyColumn ? <Redirect from='/' to='/empty' exact /> : <Redirect from='/' to='/getting-started' exact />;
|
||||
const account_redirect = advancedMode && openPostsFirst ? <Redirect from='/accounts/:accountId' to='/accounts/:accountId/posts' exact /> : <Redirect from='/accounts/:accountId' to='/accounts/:accountId/about' exact />;
|
||||
|
||||
return (
|
||||
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
|
||||
<WrappedSwitch>
|
||||
{redirect}
|
||||
{account_redirect}
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
||||
|
@ -200,7 +206,9 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<WrappedRoute path='/statuses/:statusId/mentions' component={Mentions} content={children} />
|
||||
|
||||
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/about' exact component={AccountTimeline} content={children} componentParams={{ about: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/posts/:tagged?' component={AccountTimeline} content={children} componentParams={{ posts: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
|
||||
<WrappedRoute path='/accounts/:accountId/subscribing' component={Subscribing} content={children} />
|
||||
|
@ -246,6 +254,8 @@ class UI extends React.PureComponent {
|
|||
layout: PropTypes.string.isRequired,
|
||||
firstLaunch: PropTypes.bool,
|
||||
visibilities: PropTypes.arrayOf(PropTypes.string),
|
||||
advancedMode: PropTypes.bool,
|
||||
openPostsFirst: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -534,7 +544,7 @@ class UI extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const { draggingOver } = this.state;
|
||||
const { children, isComposing, location, dropdownMenuIsOpen, layout } = this.props;
|
||||
const { children, isComposing, location, dropdownMenuIsOpen, layout, advancedMode, openPostsFirst } = this.props;
|
||||
|
||||
const handlers = {
|
||||
help: this.handleHotkeyToggleHelp,
|
||||
|
@ -561,7 +571,7 @@ class UI extends React.PureComponent {
|
|||
return (
|
||||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
||||
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
|
||||
<SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'}>
|
||||
<SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'} advancedMode={advancedMode} openPostsFirst={openPostsFirst}>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
|
||||
|
|
|
@ -11,6 +11,13 @@
|
|||
"account.blocked": "Blocked",
|
||||
"account.browse_more_on_origin_server": "Browse more on the original profile",
|
||||
"account.cancel_follow_request": "Cancel follow request",
|
||||
"account.column_settings.advanced_settings": "Advanced settings",
|
||||
"account.column_settings.advanced_mode": "Advanced mode",
|
||||
"account.column_settings.hide_featured_tags": "Hide featuread tags selection",
|
||||
"account.column_settings.hide_relation": "Hide post and follow counters",
|
||||
"account.column_settings.open_posts_first": "Open first post when selecting an account",
|
||||
"account.column_settings.show_posts_in_about": "Show posts in about",
|
||||
"account.column_settings.without_reblogs": "Without boosts",
|
||||
"account.conversations": "Show conversations with @{name}",
|
||||
"account.conversations_all": "Show all conversations",
|
||||
"account.direct": "Direct message @{name}",
|
||||
|
@ -27,6 +34,9 @@
|
|||
"account.follows": "Follows",
|
||||
"account.follows.empty": "This user doesn't follow anyone yet.",
|
||||
"account.follows_you": "Follows you",
|
||||
"account.hashtag_all": "All",
|
||||
"account.hashtag_all_description": "All posts (deselect hashtags)",
|
||||
"account.hashtag_select_description": "Select hashtag #{name}",
|
||||
"account.hide_reblogs": "Hide boosts from @{name}",
|
||||
"account.joined": "Joined",
|
||||
"account.last_status": "Last active",
|
||||
|
@ -49,6 +59,11 @@
|
|||
"account.requested": "Awaiting approval. Click to cancel follow request",
|
||||
"account.secret": "Secret",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.short.about": "About",
|
||||
"account.short.conversations": "Conversations",
|
||||
"account.short.media": "Media",
|
||||
"account.short.posts": "Posts",
|
||||
"account.short.with_replies": "Posts & Replies",
|
||||
"account.show_reblogs": "Show boosts from @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}",
|
||||
"account.subscribe": "Subscribe",
|
||||
|
@ -64,6 +79,7 @@
|
|||
"account.unmute_notifications": "Unmute notifications from @{name}",
|
||||
"account_note.placeholder": "Click to add note",
|
||||
"account_popup.more_users": "({number, plural, one {# other user} other {# other users}})",
|
||||
"account.view_about": "View about",
|
||||
"alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Rate limited",
|
||||
"alert.unexpected.message": "An unexpected error occurred.",
|
||||
|
@ -91,6 +107,7 @@
|
|||
"circle.reply": "(Reply to circle context)",
|
||||
"circle.select": "Select circle",
|
||||
"circle.unselect": "(Select circle)",
|
||||
"column.account": "Account",
|
||||
"column.blocks": "Blocked users",
|
||||
"column.bookmarks": "Bookmarks",
|
||||
"column.circles": "Circles",
|
||||
|
@ -223,6 +240,7 @@
|
|||
"empty_column.blocks": "You haven't blocked any users yet.",
|
||||
"empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.conversation_unavailable": "No conversation with this user yet",
|
||||
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||
"empty_column.domain_blocks": "There are no blocked domains yet.",
|
||||
"empty_column.emoji_reactioned_statuses": "You don't have any reaction posts yet. When you reaction one, it will show up here.",
|
||||
|
@ -240,6 +258,7 @@
|
|||
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
|
||||
"empty_column.mutes": "You haven't muted any users yet.",
|
||||
"empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
|
||||
"empty_column.pinned_unavailable": "Pinned posts unavailable",
|
||||
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
|
||||
"empty_column.referred_by_statuses": "There are no referred by posts yet. When someone refers a post, it will appear here.",
|
||||
"empty_column.suggestions": "No one has suggestions yet.",
|
||||
|
|
|
@ -11,6 +11,13 @@
|
|||
"account.blocked": "ブロック済み",
|
||||
"account.browse_more_on_origin_server": "リモートで表示",
|
||||
"account.cancel_follow_request": "フォローリクエストを取り消す",
|
||||
"account.column_settings.advanced_settings": "詳細設定",
|
||||
"account.column_settings.advanced_mode": "詳細モード",
|
||||
"account.column_settings.hide_featured_tags": "注目のタグの選択を隠す",
|
||||
"account.column_settings.hide_relation": "投稿とフォローのカウンターを隠す",
|
||||
"account.column_settings.open_posts_first": "アカウント選択時、最初に投稿を開く",
|
||||
"account.column_settings.show_posts_in_about": "概要に投稿を表示",
|
||||
"account.column_settings.without_reblogs": "ブーストを除外",
|
||||
"account.conversations": "@{name}さんとの会話を表示",
|
||||
"account.conversations_all": "すべての会話を表示",
|
||||
"account.direct": "@{name}さんにダイレクトメッセージ",
|
||||
|
@ -27,6 +34,9 @@
|
|||
"account.follows": "フォロー",
|
||||
"account.follows.empty": "まだ誰もフォローしていません。",
|
||||
"account.follows_you": "フォローされています",
|
||||
"account.hashtag_all": "すべて",
|
||||
"account.hashtag_all_description": "すべての投稿(ハッシュタグの選択解除)",
|
||||
"account.hashtag_select_description": "ハッシュタグ #{name} を選択",
|
||||
"account.hide_reblogs": "@{name}さんからのブーストを非表示",
|
||||
"account.joined": "登録日",
|
||||
"account.last_status": "最後の活動",
|
||||
|
@ -49,6 +59,11 @@
|
|||
"account.report": "@{name}さんを通報",
|
||||
"account.requested": "フォロー承認待ちです。クリックしてキャンセル",
|
||||
"account.share": "@{name}さんのプロフィールを共有する",
|
||||
"account.short.about": "概要",
|
||||
"account.short.conversations": "会話",
|
||||
"account.short.media": "メディア",
|
||||
"account.short.posts": "投稿",
|
||||
"account.short.with_replies": "投稿と返信",
|
||||
"account.show_reblogs": "@{name}さんからのブーストを表示",
|
||||
"account.statuses_counter": "{counter} 投稿",
|
||||
"account.subscribe": "購読",
|
||||
|
@ -64,6 +79,7 @@
|
|||
"account.unmute_notifications": "@{name}さんからの通知を受け取るようにする",
|
||||
"account_note.placeholder": "クリックしてメモを追加",
|
||||
"account_popup.more_users": "(他、{number}人のユーザー)",
|
||||
"account.view_about": "概要を見る",
|
||||
"alert.rate_limited.message": "{retry_time, time, medium} 以降に再度実行してください。",
|
||||
"alert.rate_limited.title": "制限に達しました",
|
||||
"alert.unexpected.message": "不明なエラーが発生しました。",
|
||||
|
@ -91,6 +107,7 @@
|
|||
"circles.new.title_placeholder": "新規サークル名",
|
||||
"circles.search": "フォローされている人の中から検索",
|
||||
"circles.subheading": "あなたのサークル",
|
||||
"column.account": "アカウント",
|
||||
"column.blocks": "ブロックしたユーザー",
|
||||
"column.bookmarks": "ブックマーク",
|
||||
"column.circles": "サークル",
|
||||
|
@ -223,6 +240,7 @@
|
|||
"empty_column.blocks": "まだ誰もブロックしていません。",
|
||||
"empty_column.bookmarked_statuses": "まだ何もブックマーク登録していません。ブックマーク登録するとここに表示されます。",
|
||||
"empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
|
||||
"empty_column.conversation_unavailable": "このユーザーとの会話はまだありません。",
|
||||
"empty_column.direct": "ダイレクトメッセージはまだありません。ダイレクトメッセージをやりとりすると、ここに表示されます。",
|
||||
"empty_column.domain_blocks": "ブロックしているドメインはありません。",
|
||||
"empty_column.emoji_reactioned_statuses": "まだ何も絵文字リアクションしていません。絵文字リアクションするとここに表示されます。",
|
||||
|
@ -240,6 +258,7 @@
|
|||
"empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。",
|
||||
"empty_column.mutes": "まだ誰もミュートしていません。",
|
||||
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
|
||||
"empty_column.pinned_unavailable": "固定された投稿はありません。",
|
||||
"empty_column.referred_by_statuses": "まだ、参照している投稿はありません。誰かが投稿を参照すると、ここに表示されます。",
|
||||
"empty_column.suggestions": "まだおすすめできるユーザーがいません。",
|
||||
"empty_column.trends": "まだ何もトレンドがありません。",
|
||||
|
|
|
@ -16,6 +16,17 @@ const initialState = ImmutableMap({
|
|||
show: true,
|
||||
}),
|
||||
|
||||
account: ImmutableMap({
|
||||
other: ImmutableMap({
|
||||
advancedMode: true,
|
||||
openPostsFirst: false,
|
||||
withoutReblogs: false,
|
||||
showPostsInAbout: true,
|
||||
hideFeaturedTags: false,
|
||||
hideRelation: false,
|
||||
}),
|
||||
}),
|
||||
|
||||
home: ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
reblog: true,
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
FOLLOW_REQUESTS_EXPAND_FAIL,
|
||||
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
|
||||
FOLLOW_REQUEST_REJECT_SUCCESS,
|
||||
} from '../actions/accounts';
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
REBLOGS_FETCH_SUCCESS,
|
||||
REBLOGS_FETCH_REQUEST,
|
||||
|
@ -87,6 +87,11 @@ import {
|
|||
DIRECTORY_EXPAND_SUCCESS,
|
||||
DIRECTORY_EXPAND_FAIL,
|
||||
} from 'mastodon/actions/directory';
|
||||
import {
|
||||
FEATURED_TAGS_FETCH_REQUEST,
|
||||
FEATURED_TAGS_FETCH_SUCCESS,
|
||||
FEATURED_TAGS_FETCH_FAIL,
|
||||
} from 'mastodon/actions/featured_tags';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
|
||||
const initialListState = ImmutableMap({
|
||||
|
@ -106,6 +111,7 @@ const initialState = ImmutableMap({
|
|||
follow_requests: initialListState,
|
||||
blocks: initialListState,
|
||||
mutes: initialListState,
|
||||
featured_tags: initialListState,
|
||||
});
|
||||
|
||||
const normalizeList = (state, path, accounts, next) => {
|
||||
|
@ -147,6 +153,18 @@ const appendToEmojiReactions = (state, path, emojiReactions, next) => {
|
|||
});
|
||||
};
|
||||
|
||||
const normalizeFeaturedTag = (featuredTags, accountId) => {
|
||||
const normalizeFeaturedTag = { ...featuredTags, accountId: accountId };
|
||||
return fromJS(normalizeFeaturedTag);
|
||||
};
|
||||
|
||||
const normalizeFeaturedTags = (state, path, featuredTags, accountId) => {
|
||||
return state.setIn(path, ImmutableMap({
|
||||
items: ImmutableList(featuredTags.map(featuredTag => normalizeFeaturedTag(featuredTag, accountId)).sort((a, b) => b.get('statuses_count') - a.get('statuses_count'))),
|
||||
isLoading: false,
|
||||
}));
|
||||
};
|
||||
|
||||
export default function userLists(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case FOLLOWERS_FETCH_SUCCESS:
|
||||
|
@ -274,6 +292,12 @@ export default function userLists(state = initialState, action) {
|
|||
case DIRECTORY_FETCH_FAIL:
|
||||
case DIRECTORY_EXPAND_FAIL:
|
||||
return state.setIn(['directory', 'isLoading'], false);
|
||||
case FEATURED_TAGS_FETCH_SUCCESS:
|
||||
return normalizeFeaturedTags(state, ['featured_tags', action.id], action.tags, action.id);
|
||||
case FEATURED_TAGS_FETCH_REQUEST:
|
||||
return state.setIn(['featured_tags', action.id, 'isLoading'], true);
|
||||
case FEATURED_TAGS_FETCH_FAIL:
|
||||
return state.setIn(['featured_tags', action.id, 'isLoading'], false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -6663,6 +6663,13 @@ a.status-card.compact:hover {
|
|||
}
|
||||
}
|
||||
|
||||
&.with-short-label {
|
||||
button,
|
||||
a {
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.directory__section-headline {
|
||||
background: darken($ui-base-color, 2%);
|
||||
border-bottom-color: transparent;
|
||||
|
@ -6680,6 +6687,13 @@ a.status-card.compact:hover {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__short-label {
|
||||
font-size: 10px;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
|
@ -7569,6 +7583,51 @@ noscript {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__hashtag-links {
|
||||
overflow: hidden;
|
||||
padding: 10px 5px;
|
||||
margin: 0 -5px;
|
||||
color: $darker-text-color;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 12%);
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
color: $darker-text-color;
|
||||
text-decoration: none;
|
||||
padding: 5px 10px;
|
||||
font-weight: 500;
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
a.active {
|
||||
color: darken($ui-base-color, 4%);
|
||||
background: $darker-text-color;
|
||||
border-radius: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.advanced {
|
||||
.account__header__bio {
|
||||
.account__header__personal--wrapper {
|
||||
border-bottom: 1px solid lighten($ui-base-color, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
.account__header__extra {
|
||||
margin-top: 0;
|
||||
padding: 0 5px;
|
||||
.account__header__extra__links {
|
||||
border-top: none;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 12%);
|
||||
margin: 0 -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__account-note {
|
||||
|
|
Loading…
Reference in a new issue