Browse Source

Merge branch 'main' into glitch-soc/merge-upstream

Conflicts:
- `streaming/index.js`:
  Filtering code for streaming notifications has been refactored upstream, but
  glitch-soc had similar code for local-only toots in the same places.
  Ported upstream changes, but did not refactor local-only filtering.
main
Claire 3 weeks ago
parent
commit
3622110778
  1. 25
      app/controllers/home_controller.rb
  2. 32
      app/javascript/mastodon/actions/accounts.js
  3. 4
      app/javascript/mastodon/actions/compose.js
  4. 26
      app/javascript/mastodon/api.js
  5. 2
      app/javascript/mastodon/components/account.js
  6. 2
      app/javascript/mastodon/components/hashtag.js
  7. 64
      app/javascript/mastodon/components/status.js
  8. 2
      app/javascript/mastodon/components/status_action_bar.js
  9. 6
      app/javascript/mastodon/components/status_content.js
  10. 26
      app/javascript/mastodon/containers/mastodon.js
  11. 6
      app/javascript/mastodon/features/account/components/header.js
  12. 64
      app/javascript/mastodon/features/account_gallery/index.js
  13. 6
      app/javascript/mastodon/features/account_timeline/components/header.js
  14. 2
      app/javascript/mastodon/features/account_timeline/components/moved_note.js
  15. 55
      app/javascript/mastodon/features/account_timeline/index.js
  16. 4
      app/javascript/mastodon/features/compose/components/navigation_bar.js
  17. 2
      app/javascript/mastodon/features/compose/components/reply_indicator.js
  18. 6
      app/javascript/mastodon/features/compose/index.js
  19. 2
      app/javascript/mastodon/features/direct_timeline/components/conversation.js
  20. 2
      app/javascript/mastodon/features/directory/components/account_card.js
  21. 2
      app/javascript/mastodon/features/follow_requests/components/account_authorize.js
  22. 68
      app/javascript/mastodon/features/followers/index.js
  23. 68
      app/javascript/mastodon/features/following/index.js
  24. 6
      app/javascript/mastodon/features/getting_started/components/announcements.js
  25. 10
      app/javascript/mastodon/features/getting_started/index.js
  26. 2
      app/javascript/mastodon/features/lists/index.js
  27. 2
      app/javascript/mastodon/features/notifications/components/follow_request.js
  28. 6
      app/javascript/mastodon/features/notifications/components/notification.js
  29. 2
      app/javascript/mastodon/features/picture_in_picture/components/footer.js
  30. 2
      app/javascript/mastodon/features/picture_in_picture/components/header.js
  31. 6
      app/javascript/mastodon/features/status/components/detailed_status.js
  32. 2
      app/javascript/mastodon/features/status/index.js
  33. 2
      app/javascript/mastodon/features/ui/components/boost_modal.js
  34. 2
      app/javascript/mastodon/features/ui/components/columns_area.js
  35. 2
      app/javascript/mastodon/features/ui/components/list_panel.js
  36. 7
      app/javascript/mastodon/features/ui/components/media_modal.js
  37. 8
      app/javascript/mastodon/features/ui/components/navigation_panel.js
  38. 6
      app/javascript/mastodon/features/ui/components/tabs_bar.js
  39. 46
      app/javascript/mastodon/features/ui/index.js
  40. 15
      app/javascript/mastodon/reducers/accounts_map.js
  41. 2
      app/javascript/mastodon/reducers/index.js
  42. 2
      app/javascript/mastodon/service_worker/web_push_notifications.js
  43. 63
      app/lib/permalink_redirector.rb
  44. 2
      app/services/notify_service.rb
  45. 6
      spec/controllers/home_controller_spec.rb
  46. 42
      spec/lib/permalink_redirector_spec.rb
  47. 80
      streaming/index.js

25
app/controllers/home_controller.rb

@ -16,30 +16,7 @@ class HomeController < ApplicationController
def redirect_unauthenticated_to_permalinks!
return if user_signed_in?
matches = request.path.match(/\A\/web\/(statuses|accounts)\/([\d]+)\z/)
if matches
case matches[1]
when 'statuses'
status = Status.find_by(id: matches[2])
if status&.distributable?
redirect_to(ActivityPub::TagManager.instance.url_for(status))
return
end
when 'accounts'
account = Account.find_by(id: matches[2])
if account
redirect_to(ActivityPub::TagManager.instance.url_for(account))
return
end
end
end
matches = request.path.match(%r{\A/web/timelines/tag/(?<tag>.+)\z})
redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
redirect_to(PermalinkRedirector.new(request.path).redirect_path || default_redirect_path)
end
def set_pack

32
app/javascript/mastodon/actions/accounts.js

@ -5,6 +5,10 @@ export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL';
export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST';
export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS';
export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL';
export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST';
export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL';
@ -87,6 +91,34 @@ export function fetchAccount(id) {
};
};
export const lookupAccount = acct => (dispatch, getState) => {
dispatch(lookupAccountRequest(acct));
api(getState).get('/api/v1/accounts/lookup', { params: { acct } }).then(response => {
dispatch(fetchRelationships([response.data.id]));
dispatch(importFetchedAccount(response.data));
dispatch(lookupAccountSuccess());
}).catch(error => {
dispatch(lookupAccountFail(acct, error));
});
};
export const lookupAccountRequest = (acct) => ({
type: ACCOUNT_LOOKUP_REQUEST,
acct,
});
export const lookupAccountSuccess = () => ({
type: ACCOUNT_LOOKUP_SUCCESS,
});
export const lookupAccountFail = (acct, error) => ({
type: ACCOUNT_LOOKUP_FAIL,
acct,
error,
skipAlert: true,
});
export function fetchAccountRequest(id) {
return {
type: ACCOUNT_FETCH_REQUEST,

4
app/javascript/mastodon/actions/compose.js

@ -78,7 +78,7 @@ const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1);
export const ensureComposeIsVisible = (getState, routerHistory) => {
if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) {
routerHistory.push('/statuses/new');
routerHistory.push('/publish');
}
};
@ -158,7 +158,7 @@ export function submitCompose(routerHistory) {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
},
}).then(function (response) {
if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
if (routerHistory && routerHistory.location.pathname === '/publish' && window.history.state) {
routerHistory.goBack();
}

26
app/javascript/mastodon/api.js

@ -12,21 +12,35 @@ export const getLinks = response => {
return LinkHeader.parse(value);
};
let csrfHeader = {};
const csrfHeader = {};
function setCSRFHeader() {
const setCSRFHeader = () => {
const csrfToken = document.querySelector('meta[name=csrf-token]');
if (csrfToken) {
csrfHeader['X-CSRF-Token'] = csrfToken.content;
}
}
};
ready(setCSRFHeader);
const authorizationHeaderFromState = getState => {
const accessToken = getState && getState().getIn(['meta', 'access_token'], '');
if (!accessToken) {
return {};
}
return {
'Authorization': `Bearer ${accessToken}`,
};
};
export default getState => axios.create({
headers: Object.assign(csrfHeader, getState ? {
'Authorization': `Bearer ${getState().getIn(['meta', 'access_token'], '')}`,
} : {}),
headers: {
...csrfHeader,
...authorizationHeaderFromState(getState),
},
transformResponse: [function (data) {
try {

2
app/javascript/mastodon/components/account.js

@ -118,7 +118,7 @@ class Account extends ImmutablePureComponent {
return (
<div className='account'>
<div className='account__wrapper'>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
{mute_expires_at}
<DisplayName account={account} />

2
app/javascript/mastodon/components/hashtag.js

@ -52,7 +52,7 @@ const Hashtag = ({ hashtag }) => (
<div className='trends__item__name'>
<Permalink
href={hashtag.get('url')}
to={`/timelines/tag/${hashtag.get('name')}`}
to={`/tags/${hashtag.get('name')}`}
>
#<span>{hashtag.get('name')}</span>
</Permalink>

64
app/javascript/mastodon/components/status.js

@ -134,42 +134,28 @@ class Status extends ImmutablePureComponent {
this.setState({ showMedia: !this.state.showMedia });
}
handleClick = () => {
if (this.props.onClick) {
this.props.onClick();
handleClick = e => {
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
return;
}
if (!this.context.router) {
return;
if (e) {
e.preventDefault();
}
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
this.handleHotkeyOpen();
}
handleExpandClick = (e) => {
if (this.props.onClick) {
this.props.onClick();
handleAccountClick = e => {
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
return;
}
if (e.button === 0) {
if (!this.context.router) {
return;
}
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
}
}
handleAccountClick = (e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
const id = e.currentTarget.getAttribute('data-id');
if (e) {
e.preventDefault();
this.context.router.history.push(`/accounts/${id}`);
}
this.handleHotkeyOpenProfile();
}
handleExpandedToggle = () => {
@ -242,11 +228,30 @@ class Status extends ImmutablePureComponent {
}
handleHotkeyOpen = () => {
this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`);
if (this.props.onClick) {
this.props.onClick();
return;
}
const { router } = this.context;
const status = this._properStatus();
if (!router) {
return;
}
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
}
handleHotkeyOpenProfile = () => {
this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`);
const { router } = this.context;
const status = this._properStatus();
if (!router) {
return;
}
router.history.push(`/@${status.getIn(['account', 'acct'])}`);
}
handleHotkeyMoveUp = e => {
@ -465,14 +470,15 @@ class Status extends ImmutablePureComponent {
{prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' />
<div className='status__expand' onClick={this.handleClick} role='presentation' />
<div className='status__info'>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
<a onClick={this.handleClick} href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
<RelativeTimestamp timestamp={status.get('created_at')} />
</a>
<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
<div className='status__avatar'>
{statusAvatar}
</div>

2
app/javascript/mastodon/components/status_action_bar.js

@ -186,7 +186,7 @@ class StatusActionBar extends ImmutablePureComponent {
}
handleOpen = () => {
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
}
handleEmbed = () => {

6
app/javascript/mastodon/components/status_content.js

@ -112,7 +112,7 @@ export default class StatusContent extends React.PureComponent {
onMentionClick = (mention, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/accounts/${mention.get('id')}`);
this.context.router.history.push(`/@${mention.get('acct')}`);
}
}
@ -121,7 +121,7 @@ export default class StatusContent extends React.PureComponent {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/timelines/tag/${hashtag}`);
this.context.router.history.push(`/tags/${hashtag}`);
}
}
@ -198,7 +198,7 @@ export default class StatusContent extends React.PureComponent {
let mentionsPlaceholder = '';
const mentionLinks = status.get('mentions').map(item => (
<Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'>
<Permalink to={`/@${item.get('acct')}`} href={item.get('url')} key={item.get('id')} className='mention'>
@<span>{item.get('username')}</span>
</Permalink>
)).reduce((aggregate, item) => [...aggregate, item, ' '], []);

26
app/javascript/mastodon/containers/mastodon.js

@ -22,14 +22,38 @@ const hydrateAction = hydrateStore(initialState);
store.dispatch(hydrateAction);
store.dispatch(fetchCustomEmojis());
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
accessToken: state.meta.access_token,
});
export default class Mastodon extends React.PureComponent {
static propTypes = {
locale: PropTypes.string.isRequired,
};
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() {
this.disconnect = store.dispatch(connectUserStream());
if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream());
}
}
componentWillUnmount () {

6
app/javascript/mastodon/features/account/components/header.js

@ -332,21 +332,21 @@ class Header extends ImmutablePureComponent {
{!suspended && (
<div className='account__header__extra__links'>
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}>
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
<ShortNumber
value={account.get('statuses_count')}
renderer={counterRenderer('statuses')}
/>
</NavLink>
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}>
<NavLink exact activeClassName='active' to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
<ShortNumber
value={account.get('following_count')}
renderer={counterRenderer('following')}
/>
</NavLink>
<NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
<NavLink exact activeClassName='active' to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
<ShortNumber
value={account.get('followers_count')}
renderer={counterRenderer('followers')}

64
app/javascript/mastodon/features/account_gallery/index.js

@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { fetchAccount } from 'mastodon/actions/accounts';
import { lookupAccount } from 'mastodon/actions/accounts';
import { expandAccountMediaTimeline } from '../../actions/timelines';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import Column from '../ui/components/column';
@ -17,14 +17,25 @@ import MissingIndicator from 'mastodon/components/missing_indicator';
import { openModal } from 'mastodon/actions/modal';
import { FormattedMessage } from 'react-intl';
const mapStateToProps = (state, props) => ({
isAccount: !!state.getIn(['accounts', props.params.accountId]),
attachments: getAccountGallery(state, props.params.accountId),
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
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),
});
const mapStateToProps = (state, { params: { acct } }) => {
const accountId = state.getIn(['accounts_map', acct]);
if (!accountId) {
return {
isLoading: true,
};
}
return {
accountId,
isAccount: !!state.getIn(['accounts', accountId]),
attachments: getAccountGallery(state, accountId),
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
};
};
class LoadMoreMedia extends ImmutablePureComponent {
@ -52,7 +63,10 @@ export default @connect(mapStateToProps)
class AccountGallery extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
params: PropTypes.shape({
acct: PropTypes.string.isRequired,
}).isRequired,
accountId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
attachments: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool,
@ -67,15 +81,29 @@ class AccountGallery extends ImmutablePureComponent {
width: 323,
};
_load () {
const { accountId, dispatch } = this.props;
dispatch(expandAccountMediaTimeline(accountId));
}
componentDidMount () {
this.props.dispatch(fetchAccount(this.props.params.accountId));
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
const { params: { acct }, accountId, dispatch } = this.props;
if (accountId) {
this._load();
} else {
dispatch(lookupAccount(acct));
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
componentDidUpdate (prevProps) {
const { params: { acct }, accountId, dispatch } = this.props;
if (prevProps.accountId !== accountId && accountId) {
this._load();
} else if (prevProps.params.acct !== acct) {
dispatch(lookupAccount(acct));
}
}
@ -95,7 +123,7 @@ class AccountGallery extends ImmutablePureComponent {
}
handleLoadMore = maxId => {
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId }));
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }));
};
handleLoadOlder = e => {
@ -165,7 +193,7 @@ class AccountGallery extends ImmutablePureComponent {
<ScrollContainer scrollKey='account_gallery'>
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
<HeaderContainer accountId={this.props.params.accountId} />
<HeaderContainer accountId={this.props.accountId} />
{(suspended || blockedBy) ? (
<div className='empty-column-indicator'>

6
app/javascript/mastodon/features/account_timeline/components/header.js

@ -123,9 +123,9 @@ export default class Header extends ImmutablePureComponent {
{!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>
<NavLink exact to={`/@${account.get('acct')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
<NavLink exact to={`/@${account.get('acct')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots and replies' /></NavLink>
<NavLink exact to={`/@${account.get('acct')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
</div>
)}
</div>

2
app/javascript/mastodon/features/account_timeline/components/moved_note.js

@ -21,7 +21,7 @@ export default class MovedNote extends ImmutablePureComponent {
handleAccountClick = e => {
if (e.button === 0) {
e.preventDefault();
this.context.router.history.push(`/accounts/${this.props.to.get('id')}`);
this.context.router.history.push(`/@${this.props.to.get('acct')}`);
}
e.stopPropagation();

55
app/javascript/mastodon/features/account_timeline/index.js

@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { fetchAccount } from '../../actions/accounts';
import { lookupAccount, fetchAccount } from '../../actions/accounts';
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
import StatusList from '../../components/status_list';
import LoadingIndicator from '../../components/loading_indicator';
@ -20,10 +20,19 @@ import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines'
const emptyList = ImmutableList();
const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
const mapStateToProps = (state, { params: { acct }, withReplies = false }) => {
const accountId = state.getIn(['accounts_map', acct]);
if (!accountId) {
return {
isLoading: true,
};
}
const path = withReplies ? `${accountId}:with_replies` : accountId;
return {
accountId,
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
remoteUrl: state.getIn(['accounts', accountId, 'url']),
isAccount: !!state.getIn(['accounts', accountId]),
@ -48,7 +57,10 @@ export default @connect(mapStateToProps)
class AccountTimeline extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
params: PropTypes.shape({
acct: PropTypes.string.isRequired,
}).isRequired,
accountId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list,
featuredStatusIds: ImmutablePropTypes.list,
@ -63,8 +75,8 @@ class AccountTimeline extends ImmutablePureComponent {
multiColumn: PropTypes.bool,
};
componentWillMount () {
const { params: { accountId }, withReplies, dispatch } = this.props;
_load () {
const { accountId, withReplies, dispatch } = this.props;
dispatch(fetchAccount(accountId));
dispatch(fetchAccountIdentityProofs(accountId));
@ -80,29 +92,32 @@ class AccountTimeline extends ImmutablePureComponent {
}
}
componentWillReceiveProps (nextProps) {
const { dispatch } = this.props;
componentDidMount () {
const { params: { acct }, accountId, dispatch } = this.props;
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
dispatch(fetchAccount(nextProps.params.accountId));
dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
if (accountId) {
this._load();
} else {
dispatch(lookupAccount(acct));
}
}
if (!nextProps.withReplies) {
dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
}
componentDidUpdate (prevProps) {
const { params: { acct }, accountId, dispatch } = this.props;
dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies }));
if (prevProps.accountId !== accountId && accountId) {
this._load();
} else if (prevProps.params.acct !== acct) {
dispatch(lookupAccount(acct));
}
if (nextProps.params.accountId === me && this.props.params.accountId !== me) {
dispatch(connectTimeline(`account:${me}`));
} else if (this.props.params.accountId === me && nextProps.params.accountId !== me) {
if (prevProps.accountId === me && accountId !== me) {
dispatch(disconnectTimeline(`account:${me}`));
}
}
componentWillUnmount () {
const { dispatch, params: { accountId } } = this.props;
const { dispatch, accountId } = this.props;
if (accountId === me) {
dispatch(disconnectTimeline(`account:${me}`));
@ -110,7 +125,7 @@ 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.accountId, { maxId, withReplies: this.props.withReplies }));
}
render () {
@ -152,7 +167,7 @@ class AccountTimeline extends ImmutablePureComponent {
<ColumnBackButton multiColumn={multiColumn} />
<StatusList
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
prepend={<HeaderContainer accountId={this.props.accountId} />}
alwaysPrepend
append={remoteMessage}
scrollKey='account_timeline'

4
app/javascript/mastodon/features/compose/components/navigation_bar.js

@ -19,13 +19,13 @@ export default class NavigationBar extends ImmutablePureComponent {
render () {
return (
<div className='navigation-bar'>
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
<Permalink href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
<Avatar account={this.props.account} size={48} />
</Permalink>
<div className='navigation-bar__profile'>
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
<Permalink href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
</Permalink>

2
app/javascript/mastodon/features/compose/components/reply_indicator.js

@ -32,7 +32,7 @@ class ReplyIndicator extends ImmutablePureComponent {
handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
}
}

6
app/javascript/mastodon/features/compose/index.js

@ -100,16 +100,16 @@ class Compose extends React.PureComponent {
<nav className='drawer__header'>
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link>
{!columns.some(column => column.get('id') === 'HOME') && (
<Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
<Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
<Link to='/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'PUBLIC') && (
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
<Link to='/federated' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
)}
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a>

2
app/javascript/mastodon/features/direct_timeline/components/conversation.js

@ -133,7 +133,7 @@ class Conversation extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete });
const names = accounts.map(a => <Permalink to={`/accounts/${a.get('id')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]);
const names = accounts.map(a => <Permalink to={`/@${a.get('acct')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]);
const handlers = {
reply: this.handleReply,

2
app/javascript/mastodon/features/directory/components/account_card.js

@ -213,7 +213,7 @@ class AccountCard extends ImmutablePureComponent {
<Permalink
className='directory__card__bar__name'
href={account.get('url')}
to={`/accounts/${account.get('id')}`}
to={`/@${account.get('acct')}`}
>
<Avatar account={account} size={48} />
<DisplayName account={account} />

2
app/javascript/mastodon/features/follow_requests/components/account_authorize.js

@ -30,7 +30,7 @@ class AccountAuthorize extends ImmutablePureComponent {
return (
<div className='account-authorize__wrapper'>
<div className='account-authorize'>
<Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'>
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='detailed-status__display-name'>
<div className='account-authorize__avatar'><Avatar account={account} size={48} /></div>
<DisplayName account={account} />
</Permalink>

68
app/javascript/mastodon/features/followers/index.js

@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import LoadingIndicator from '../../components/loading_indicator';
import {
fetchAccount,
lookupAccount,
fetchFollowers,
expandFollowers,
} from '../../actions/accounts';
@ -19,15 +19,26 @@ import ScrollableList from '../../components/scrollable_list';
import MissingIndicator from 'mastodon/components/missing_indicator';
import TimelineHint from 'mastodon/components/timeline_hint';
const mapStateToProps = (state, props) => ({
remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])),
remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']),
isAccount: !!state.getIn(['accounts', props.params.accountId]),
accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
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),
});
const mapStateToProps = (state, { params: { acct } }) => {
const accountId = state.getIn(['accounts_map', acct]);
if (!accountId) {
return {
isLoading: true,
};
}
return {
accountId,
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
remoteUrl: state.getIn(['accounts', accountId, 'url']),
isAccount: !!state.getIn(['accounts', accountId]),
accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
};
};
const RemoteHint = ({ url }) => (
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.followers' defaultMessage='Followers' />} />
@ -41,7 +52,10 @@ export default @connect(mapStateToProps)
class Followers extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
params: PropTypes.shape({
acct: PropTypes.string.isRequired,
}).isRequired,
accountId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
@ -53,22 +67,34 @@ class Followers extends ImmutablePureComponent {
multiColumn: PropTypes.bool,
};
componentWillMount () {
if (!this.props.accountIds) {
this.props.dispatch(fetchAccount(this.props.params.accountId));
this.props.dispatch(fetchFollowers(this.props.params.accountId));
_load () {
const { accountId, dispatch } = this.props;
dispatch(fetchFollowers(accountId));
}
componentDidMount () {
const { params: { acct }, accountId, dispatch } = this.props;
if (accountId) {
this._load();
} else {
dispatch(lookupAccount(acct));
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(fetchFollowers(nextProps.params.accountId));
componentDidUpdate (prevProps) {
const { params: { acct }, accountId, dispatch } = this.props;
if (prevProps.accountId !== accountId && accountId) {
this._load();
} else if (prevProps.params.acct !== acct) {
dispatch(lookupAccount(acct));
}
}
handleLoadMore = debounce(() => {
this.props.dispatch(expandFollowers(this.props.params.accountId));
this.props.dispatch(expandFollowers(this.props.accountId));
}, 300, { leading: true });
render () {
@ -111,7 +137,7 @@ class Followers extends ImmutablePureComponent {
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
alwaysPrepend
append={remoteMessage}
emptyMessage={emptyMessage}

68
app/javascript/mastodon/features/following/index.js

@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import LoadingIndicator from '../../components/loading_indicator';
import {
fetchAccount,
lookupAccount,
fetchFollowing,
expandFollowing,
} from '../../actions/accounts';
@ -19,15 +19,26 @@ import ScrollableList from '../../components/scrollable_list';
import MissingIndicator from 'mastodon/components/missing_indicator';
import TimelineHint from 'mastodon/components/timeline_hint';
const mapStateToProps = (state, props) => ({
remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])),
remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']),
isAccount: !!state.getIn(['accounts', props.params.accountId]),
accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
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),
});
const mapStateToProps = (state, { params: { acct } }) => {
const accountId = state.getIn(['accounts_map', acct]);
if (!accountId) {
return {
isLoading: true,
};
}
return {
accountId,
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
remoteUrl: state.getIn(['accounts', accountId, 'url']),
isAccount: !!state.getIn(['accounts', accountId]),
accountIds: state.getIn(['user_lists', 'following', accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
};
};
const RemoteHint = ({ url }) => (
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.follows' defaultMessage='Follows' />} />
@ -41,7 +52,10 @@ export default @connect(mapStateToProps)
class Following extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
params: PropTypes.shape({
acct: PropTypes.string.isRequired,
}).isRequired,
accountId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
@ -53,22 +67,34 @@ class Following extends ImmutablePureComponent {
multiColumn: PropTypes.bool,
};
componentWillMount () {
if (!this.props.accountIds) {
this.props.dispatch(fetchAccount(this.props.params.accountId));
this.props.dispatch(fetchFollowing(this.props.params.accountId));
_load () {
const { accountId, dispatch } = this.props;
dispatch(fetchFollowing(accountId));
}
componentDidMount () {
const { params: { acct }, accountId, dispatch } = this.props;
if (accountId) {
this._load();
} else {
dispatch(lookupAccount(acct));
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(fetchFollowing(nextProps.params.accountId));
componentDidUpdate (prevProps) {
const { params: { acct }, accountId, dispatch } = this.props;
if (prevProps.accountId !== accountId && accountId) {
this._load();
} else if (prevProps.params.acct !== acct) {
dispatch(lookupAccount(acct));
}
}
handleLoadMore = debounce(() => {
this.props.dispatch(expandFollowing(this.props.params.accountId));
this.props.dispatch(expandFollowing(this.props.accountId));
}, 300, { leading: true });
render () {
@ -111,7 +137,7 @@ class Following extends ImmutablePureComponent {
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
alwaysPrepend
append={remoteMessage}
emptyMessage={emptyMessage}

6
app/javascript/mastodon/features/getting_started/components/announcements.js

@ -87,7 +87,7 @@ class Content extends ImmutablePureComponent {
onMentionClick = (mention, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/accounts/${mention.get('id')}`);
this.context.router.history.push(`/@${mention.get('acct')}`);
}
}
@ -96,14 +96,14 @@ class Content extends ImmutablePureComponent {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/timelines/tag/${hashtag}`);
this.context.router.history.push(`/tags/${hashtag}`);
}
}
onStatusClick = (status, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/statuses/${status.get('id')}`);
this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
}
}

10
app/javascript/mastodon/features/getting_started/index.js

@ -82,7 +82,7 @@ class GettingStarted extends ImmutablePureComponent {
const { fetchFollowRequests, multiColumn } = this.props;
if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
this.context.router.history.replace('/timelines/home');
this.context.router.history.replace('/home');
return;
}
@ -98,8 +98,8 @@ class GettingStarted extends ImmutablePureComponent {
if (multiColumn) {
navItems.push(
<ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />,
<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />,
<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />,
<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/public/local' />,
<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />,
);
height += 34 + 48*2;
@ -127,13 +127,13 @@ class GettingStarted extends ImmutablePureComponent {
if (multiColumn && !columns.find(item => item.get('id') === 'HOME')) {
navItems.push(
<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/timelines/home' />,
<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />,
);
height += 48;
}
navItems.push(
<ColumnLink key='direct' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />,
<ColumnLink key='direct' icon='envelope' text={intl.formatMessage(messages.direct)} to='/conversations' />,
<ColumnLink key='bookmark' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
<ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
<ColumnLink key='lists' icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />,

2
app/javascript/mastodon/features/lists/index.js

@ -73,7 +73,7 @@ class Lists extends ImmutablePureComponent {
bindToDocument={!multiColumn}
>
{lists.map(list =>
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
<ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
)}
</ScrollableList>
</Column>

2
app/javascript/mastodon/features/notifications/components/follow_request.js

@ -42,7 +42,7 @@ class FollowRequest extends ImmutablePureComponent {
return (
<div className='account'>
<div className='account__wrapper'>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
<DisplayName account={account} />
</Permalink>

6
app/javascript/mastodon/features/notifications/components/notification.js

@ -68,7 +68,7 @@ class Notification extends ImmutablePureComponent {
const { notification } = this.props;
if (notification.get('status')) {
this.context.router.history.push(`/statuses/${notification.get('status')}`);
this.context.router.history.push(`/@${notification.getIn(['status', 'account', 'acct'])}/${notification.get('status')}`);
} else {
this.handleOpenProfile();
}
@ -76,7 +76,7 @@ class Notification extends ImmutablePureComponent {
handleOpenProfile = () => {
const { notification } = this.props;
this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`);
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`);
}
handleMention = e => {
@ -315,7 +315,7 @@ class Notification extends ImmutablePureComponent {
const { notification } = this.props;
const account = notification.get('account');
const displayNameHtml = { __html: account.get('display_name_html') };
const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>;
const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>;
switch(notification.get('type')) {
case 'follow':

2
app/javascript/mastodon/features/picture_in_picture/components/footer.js

@ -120,7 +120,7 @@ class Footer extends ImmutablePureComponent {
onClose();
}
router.history.push(`/statuses/${status.get('id')}`);
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
}
render () {

2
app/javascript/mastodon/features/picture_in_picture/components/header.js

@ -34,7 +34,7 @@ class Header extends ImmutablePureComponent {
return (
<div className='picture-in-picture__header'>
<Link to={`/statuses/${statusId}`} className='picture-in-picture__header__account'>
<Link to={`/@${account.get('acct')}/${statusId}`} className='picture-in-picture__header__account'>
<Avatar account={account} size={36} />
<DisplayName account={account} />
</Link>

6
app/javascript/mastodon/features/status/components/detailed_status.js

@ -55,7 +55,7 @@ class DetailedStatus extends ImmutablePureComponent {
handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) {
e.preventDefault();
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
}
e.stopPropagation();
@ -195,7 +195,7 @@ class DetailedStatus extends ImmutablePureComponent {
reblogLink = (
<React.Fragment>
<React.Fragment> · </React.Fragment>
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link'>
<Icon id={reblogIcon} />
<span className='detailed-status__reblogs'>
<AnimatedNumber value={status.get('reblogs_count')} />
@ -219,7 +219,7 @@ class DetailedStatus extends ImmutablePureComponent {
if (this.context.router) {
favouriteLink = (
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link'>
<Icon id='star' />
<span className='detailed-status__favorites'>
<AnimatedNumber value={status.get('favourites_count')} />

2
app/javascript/mastodon/features/status/index.js

@ -396,7 +396,7 @@ class Status extends ImmutablePureComponent {
}
handleHotkeyOpenProfile = () => {
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
}
handleHotkeyToggleHidden = () => {

2
app/javascript/mastodon/features/ui/components/boost_modal.js

@ -68,7 +68,7 @@ class BoostModal extends ImmutablePureComponent {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.props.onClose();
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
}
}

2
app/javascript/mastodon/features/ui/components/columns_area.js

@ -216,7 +216,7 @@ class ColumnsArea extends ImmutablePureComponent {
const columnIndex = getIndex(this.context.router.history.location.pathname);
if (singleColumn) {
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
</