Change routing paths to use usernames in web UI (#16171)
This commit is contained in:
		
							parent
							
								
									9c92907681
								
							
						
					
					
						commit
						52e5c07948
					
				@ -14,30 +14,7 @@ class HomeController < ApplicationController
 | 
				
			|||||||
  def redirect_unauthenticated_to_permalinks!
 | 
					  def redirect_unauthenticated_to_permalinks!
 | 
				
			||||||
    return if user_signed_in?
 | 
					    return if user_signed_in?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    matches = request.path.match(/\A\/web\/(statuses|accounts)\/([\d]+)\z/)
 | 
					    redirect_to(PermalinkRedirector.new(request.path).redirect_path || default_redirect_path)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    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)
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def default_redirect_path
 | 
					  def default_redirect_path
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,10 @@ export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
 | 
				
			|||||||
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
 | 
					export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
 | 
				
			||||||
export const ACCOUNT_FETCH_FAIL    = 'ACCOUNT_FETCH_FAIL';
 | 
					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_REQUEST = 'ACCOUNT_FOLLOW_REQUEST';
 | 
				
			||||||
export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
 | 
					export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
 | 
				
			||||||
export const ACCOUNT_FOLLOW_FAIL    = 'ACCOUNT_FOLLOW_FAIL';
 | 
					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) {
 | 
					export function fetchAccountRequest(id) {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    type: ACCOUNT_FETCH_REQUEST,
 | 
					    type: ACCOUNT_FETCH_REQUEST,
 | 
				
			||||||
 | 
				
			|||||||
@ -78,7 +78,7 @@ const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const ensureComposeIsVisible = (getState, routerHistory) => {
 | 
					export const ensureComposeIsVisible = (getState, routerHistory) => {
 | 
				
			||||||
  if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) {
 | 
					  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']),
 | 
					        'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    }).then(function (response) {
 | 
					    }).then(function (response) {
 | 
				
			||||||
      if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
 | 
					      if (routerHistory && routerHistory.location.pathname === '/publish' && window.history.state) {
 | 
				
			||||||
        routerHistory.goBack();
 | 
					        routerHistory.goBack();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -12,21 +12,35 @@ export const getLinks = response => {
 | 
				
			|||||||
  return LinkHeader.parse(value);
 | 
					  return LinkHeader.parse(value);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let csrfHeader = {};
 | 
					const csrfHeader = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setCSRFHeader() {
 | 
					const setCSRFHeader = () => {
 | 
				
			||||||
  const csrfToken = document.querySelector('meta[name=csrf-token]');
 | 
					  const csrfToken = document.querySelector('meta[name=csrf-token]');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (csrfToken) {
 | 
					  if (csrfToken) {
 | 
				
			||||||
    csrfHeader['X-CSRF-Token'] = csrfToken.content;
 | 
					    csrfHeader['X-CSRF-Token'] = csrfToken.content;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ready(setCSRFHeader);
 | 
					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({
 | 
					export default getState => axios.create({
 | 
				
			||||||
  headers: Object.assign(csrfHeader, getState ? {
 | 
					  headers: {
 | 
				
			||||||
    'Authorization': `Bearer ${getState().getIn(['meta', 'access_token'], '')}`,
 | 
					    ...csrfHeader,
 | 
				
			||||||
  } : {}),
 | 
					    ...authorizationHeaderFromState(getState),
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  transformResponse: [function (data) {
 | 
					  transformResponse: [function (data) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
				
			|||||||
@ -118,7 +118,7 @@ class Account extends ImmutablePureComponent {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className='account'>
 | 
					      <div className='account'>
 | 
				
			||||||
        <div className='account__wrapper'>
 | 
					        <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>
 | 
					            <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
 | 
				
			||||||
            {mute_expires_at}
 | 
					            {mute_expires_at}
 | 
				
			||||||
            <DisplayName account={account} />
 | 
					            <DisplayName account={account} />
 | 
				
			||||||
 | 
				
			|||||||
@ -52,7 +52,7 @@ const Hashtag = ({ hashtag }) => (
 | 
				
			|||||||
    <div className='trends__item__name'>
 | 
					    <div className='trends__item__name'>
 | 
				
			||||||
      <Permalink
 | 
					      <Permalink
 | 
				
			||||||
        href={hashtag.get('url')}
 | 
					        href={hashtag.get('url')}
 | 
				
			||||||
        to={`/timelines/tag/${hashtag.get('name')}`}
 | 
					        to={`/tags/${hashtag.get('name')}`}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        #<span>{hashtag.get('name')}</span>
 | 
					        #<span>{hashtag.get('name')}</span>
 | 
				
			||||||
      </Permalink>
 | 
					      </Permalink>
 | 
				
			||||||
 | 
				
			|||||||
@ -134,42 +134,28 @@ class Status extends ImmutablePureComponent {
 | 
				
			|||||||
    this.setState({ showMedia: !this.state.showMedia });
 | 
					    this.setState({ showMedia: !this.state.showMedia });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleClick = () => {
 | 
					  handleClick = e => {
 | 
				
			||||||
    if (this.props.onClick) {
 | 
					    if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
 | 
				
			||||||
      this.props.onClick();
 | 
					 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!this.context.router) {
 | 
					    if (e) {
 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const { status } = this.props;
 | 
					 | 
				
			||||||
    this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handleExpandClick = (e) => {
 | 
					 | 
				
			||||||
    if (this.props.onClick) {
 | 
					 | 
				
			||||||
      this.props.onClick();
 | 
					 | 
				
			||||||
      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');
 | 
					 | 
				
			||||||
      e.preventDefault();
 | 
					      e.preventDefault();
 | 
				
			||||||
      this.context.router.history.push(`/accounts/${id}`);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.handleHotkeyOpen();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleAccountClick = e => {
 | 
				
			||||||
 | 
					    if (e && (e.button !== 0 || e.ctrlKey || e.metaKey))  {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (e) {
 | 
				
			||||||
 | 
					      e.preventDefault();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.handleHotkeyOpenProfile();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleExpandedToggle = () => {
 | 
					  handleExpandedToggle = () => {
 | 
				
			||||||
@ -242,11 +228,30 @@ class Status extends ImmutablePureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyOpen = () => {
 | 
					  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 = () => {
 | 
					  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 => {
 | 
					  handleHotkeyMoveUp = e => {
 | 
				
			||||||
@ -465,14 +470,15 @@ class Status extends ImmutablePureComponent {
 | 
				
			|||||||
          {prepend}
 | 
					          {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={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'>
 | 
					            <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>
 | 
					                <span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
 | 
				
			||||||
                <RelativeTimestamp timestamp={status.get('created_at')} />
 | 
					                <RelativeTimestamp timestamp={status.get('created_at')} />
 | 
				
			||||||
              </a>
 | 
					              </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'>
 | 
					                <div className='status__avatar'>
 | 
				
			||||||
                  {statusAvatar}
 | 
					                  {statusAvatar}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -186,7 +186,7 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleOpen = () => {
 | 
					  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 = () => {
 | 
					  handleEmbed = () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -112,7 +112,7 @@ export default class StatusContent extends React.PureComponent {
 | 
				
			|||||||
  onMentionClick = (mention, e) => {
 | 
					  onMentionClick = (mention, e) => {
 | 
				
			||||||
    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
					    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      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)) {
 | 
					    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      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 = '';
 | 
					      let mentionsPlaceholder = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const mentionLinks = status.get('mentions').map(item => (
 | 
					      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>
 | 
					          @<span>{item.get('username')}</span>
 | 
				
			||||||
        </Permalink>
 | 
					        </Permalink>
 | 
				
			||||||
      )).reduce((aggregate, item) => [...aggregate, item, ' '], []);
 | 
					      )).reduce((aggregate, item) => [...aggregate, item, ' '], []);
 | 
				
			||||||
 | 
				
			|||||||
@ -22,14 +22,38 @@ const hydrateAction = hydrateStore(initialState);
 | 
				
			|||||||
store.dispatch(hydrateAction);
 | 
					store.dispatch(hydrateAction);
 | 
				
			||||||
store.dispatch(fetchCustomEmojis());
 | 
					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 {
 | 
					export default class Mastodon extends React.PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    locale: PropTypes.string.isRequired,
 | 
					    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() {
 | 
					  componentDidMount() {
 | 
				
			||||||
    this.disconnect = store.dispatch(connectUserStream());
 | 
					    if (this.identity.signedIn) {
 | 
				
			||||||
 | 
					      this.disconnect = store.dispatch(connectUserStream());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillUnmount () {
 | 
					  componentWillUnmount () {
 | 
				
			||||||
 | 
				
			|||||||
@ -332,21 +332,21 @@ class Header extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            {!suspended && (
 | 
					            {!suspended && (
 | 
				
			||||||
              <div className='account__header__extra__links'>
 | 
					              <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
 | 
					                  <ShortNumber
 | 
				
			||||||
                    value={account.get('statuses_count')}
 | 
					                    value={account.get('statuses_count')}
 | 
				
			||||||
                    renderer={counterRenderer('statuses')}
 | 
					                    renderer={counterRenderer('statuses')}
 | 
				
			||||||
                  />
 | 
					                  />
 | 
				
			||||||
                </NavLink>
 | 
					                </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
 | 
					                  <ShortNumber
 | 
				
			||||||
                    value={account.get('following_count')}
 | 
					                    value={account.get('following_count')}
 | 
				
			||||||
                    renderer={counterRenderer('following')}
 | 
					                    renderer={counterRenderer('following')}
 | 
				
			||||||
                  />
 | 
					                  />
 | 
				
			||||||
                </NavLink>
 | 
					                </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
 | 
					                  <ShortNumber
 | 
				
			||||||
                    value={account.get('followers_count')}
 | 
					                    value={account.get('followers_count')}
 | 
				
			||||||
                    renderer={counterRenderer('followers')}
 | 
					                    renderer={counterRenderer('followers')}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import React from 'react';
 | 
				
			|||||||
import { connect } from 'react-redux';
 | 
					import { connect } from 'react-redux';
 | 
				
			||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
					import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { fetchAccount } from 'mastodon/actions/accounts';
 | 
					import { lookupAccount } from 'mastodon/actions/accounts';
 | 
				
			||||||
import { expandAccountMediaTimeline } from '../../actions/timelines';
 | 
					import { expandAccountMediaTimeline } from '../../actions/timelines';
 | 
				
			||||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
 | 
					import LoadingIndicator from 'mastodon/components/loading_indicator';
 | 
				
			||||||
import Column from '../ui/components/column';
 | 
					import Column from '../ui/components/column';
 | 
				
			||||||
@ -17,14 +17,25 @@ import MissingIndicator from 'mastodon/components/missing_indicator';
 | 
				
			|||||||
import { openModal } from 'mastodon/actions/modal';
 | 
					import { openModal } from 'mastodon/actions/modal';
 | 
				
			||||||
import { FormattedMessage } from 'react-intl';
 | 
					import { FormattedMessage } from 'react-intl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapStateToProps = (state, props) => ({
 | 
					const mapStateToProps = (state, { params: { acct } }) => {
 | 
				
			||||||
  isAccount: !!state.getIn(['accounts', props.params.accountId]),
 | 
					  const accountId = state.getIn(['accounts_map', acct]);
 | 
				
			||||||
  attachments: getAccountGallery(state, props.params.accountId),
 | 
					
 | 
				
			||||||
  isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
 | 
					  if (!accountId) {
 | 
				
			||||||
  hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
 | 
					    return {
 | 
				
			||||||
  suspended: state.getIn(['accounts', props.params.accountId, 'suspended'], false),
 | 
					      isLoading: true,
 | 
				
			||||||
  blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
 | 
					    };
 | 
				
			||||||
});
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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 {
 | 
					class LoadMoreMedia extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -52,7 +63,10 @@ export default @connect(mapStateToProps)
 | 
				
			|||||||
class AccountGallery extends ImmutablePureComponent {
 | 
					class AccountGallery extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    params: PropTypes.object.isRequired,
 | 
					    params: PropTypes.shape({
 | 
				
			||||||
 | 
					      acct: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    }).isRequired,
 | 
				
			||||||
 | 
					    accountId: PropTypes.string,
 | 
				
			||||||
    dispatch: PropTypes.func.isRequired,
 | 
					    dispatch: PropTypes.func.isRequired,
 | 
				
			||||||
    attachments: ImmutablePropTypes.list.isRequired,
 | 
					    attachments: ImmutablePropTypes.list.isRequired,
 | 
				
			||||||
    isLoading: PropTypes.bool,
 | 
					    isLoading: PropTypes.bool,
 | 
				
			||||||
@ -67,15 +81,29 @@ class AccountGallery extends ImmutablePureComponent {
 | 
				
			|||||||
    width: 323,
 | 
					    width: 323,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentDidMount () {
 | 
					  _load () {
 | 
				
			||||||
    this.props.dispatch(fetchAccount(this.props.params.accountId));
 | 
					    const { accountId, dispatch } = this.props;
 | 
				
			||||||
    this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
 | 
					
 | 
				
			||||||
 | 
					    dispatch(expandAccountMediaTimeline(accountId));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillReceiveProps (nextProps) {
 | 
					  componentDidMount () {
 | 
				
			||||||
    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
 | 
					    const { params: { acct }, accountId, dispatch } = this.props;
 | 
				
			||||||
      this.props.dispatch(fetchAccount(nextProps.params.accountId));
 | 
					
 | 
				
			||||||
      this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
 | 
					    if (accountId) {
 | 
				
			||||||
 | 
					      this._load();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      dispatch(lookupAccount(acct));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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 => {
 | 
					  handleLoadMore = maxId => {
 | 
				
			||||||
    this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId }));
 | 
					    this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleLoadOlder = e => {
 | 
					  handleLoadOlder = e => {
 | 
				
			||||||
@ -165,7 +193,7 @@ class AccountGallery extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <ScrollContainer scrollKey='account_gallery'>
 | 
					        <ScrollContainer scrollKey='account_gallery'>
 | 
				
			||||||
          <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
 | 
					          <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
 | 
				
			||||||
            <HeaderContainer accountId={this.props.params.accountId} />
 | 
					            <HeaderContainer accountId={this.props.accountId} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {(suspended || blockedBy) ? (
 | 
					            {(suspended || blockedBy) ? (
 | 
				
			||||||
              <div className='empty-column-indicator'>
 | 
					              <div className='empty-column-indicator'>
 | 
				
			||||||
 | 
				
			|||||||
@ -123,9 +123,9 @@ export default class Header extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        {!hideTabs && (
 | 
					        {!hideTabs && (
 | 
				
			||||||
          <div className='account__section-headline'>
 | 
					          <div className='account__section-headline'>
 | 
				
			||||||
            <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
 | 
					            <NavLink exact to={`/@${account.get('acct')}`}><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={`/@${account.get('acct')}/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')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ export default class MovedNote extends ImmutablePureComponent {
 | 
				
			|||||||
  handleAccountClick = e => {
 | 
					  handleAccountClick = e => {
 | 
				
			||||||
    if (e.button === 0) {
 | 
					    if (e.button === 0) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      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();
 | 
					    e.stopPropagation();
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import React from 'react';
 | 
				
			|||||||
import { connect } from 'react-redux';
 | 
					import { connect } from 'react-redux';
 | 
				
			||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
					import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { fetchAccount } from '../../actions/accounts';
 | 
					import { lookupAccount, fetchAccount } from '../../actions/accounts';
 | 
				
			||||||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
 | 
					import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
 | 
				
			||||||
import StatusList from '../../components/status_list';
 | 
					import StatusList from '../../components/status_list';
 | 
				
			||||||
import LoadingIndicator from '../../components/loading_indicator';
 | 
					import LoadingIndicator from '../../components/loading_indicator';
 | 
				
			||||||
@ -20,10 +20,19 @@ import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines'
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const emptyList = ImmutableList();
 | 
					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;
 | 
					  const path = withReplies ? `${accountId}:with_replies` : accountId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
 | 
					    accountId,
 | 
				
			||||||
    remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
 | 
					    remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
 | 
				
			||||||
    remoteUrl: state.getIn(['accounts', accountId, 'url']),
 | 
					    remoteUrl: state.getIn(['accounts', accountId, 'url']),
 | 
				
			||||||
    isAccount: !!state.getIn(['accounts', accountId]),
 | 
					    isAccount: !!state.getIn(['accounts', accountId]),
 | 
				
			||||||
@ -48,7 +57,10 @@ export default @connect(mapStateToProps)
 | 
				
			|||||||
class AccountTimeline extends ImmutablePureComponent {
 | 
					class AccountTimeline extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    params: PropTypes.object.isRequired,
 | 
					    params: PropTypes.shape({
 | 
				
			||||||
 | 
					      acct: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    }).isRequired,
 | 
				
			||||||
 | 
					    accountId: PropTypes.string,
 | 
				
			||||||
    dispatch: PropTypes.func.isRequired,
 | 
					    dispatch: PropTypes.func.isRequired,
 | 
				
			||||||
    statusIds: ImmutablePropTypes.list,
 | 
					    statusIds: ImmutablePropTypes.list,
 | 
				
			||||||
    featuredStatusIds: ImmutablePropTypes.list,
 | 
					    featuredStatusIds: ImmutablePropTypes.list,
 | 
				
			||||||
@ -63,8 +75,8 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
				
			|||||||
    multiColumn: PropTypes.bool,
 | 
					    multiColumn: PropTypes.bool,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillMount () {
 | 
					  _load () {
 | 
				
			||||||
    const { params: { accountId }, withReplies, dispatch } = this.props;
 | 
					    const { accountId, withReplies, dispatch } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dispatch(fetchAccount(accountId));
 | 
					    dispatch(fetchAccount(accountId));
 | 
				
			||||||
    dispatch(fetchAccountIdentityProofs(accountId));
 | 
					    dispatch(fetchAccountIdentityProofs(accountId));
 | 
				
			||||||
@ -80,29 +92,32 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillReceiveProps (nextProps) {
 | 
					  componentDidMount () {
 | 
				
			||||||
    const { dispatch } = this.props;
 | 
					    const { params: { acct }, accountId, dispatch } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
 | 
					    if (accountId) {
 | 
				
			||||||
      dispatch(fetchAccount(nextProps.params.accountId));
 | 
					      this._load();
 | 
				
			||||||
      dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
 | 
					    } else {
 | 
				
			||||||
 | 
					      dispatch(lookupAccount(acct));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!nextProps.withReplies) {
 | 
					  componentDidUpdate (prevProps) {
 | 
				
			||||||
        dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
 | 
					    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) {
 | 
					    if (prevProps.accountId === me && accountId !== me) {
 | 
				
			||||||
      dispatch(connectTimeline(`account:${me}`));
 | 
					 | 
				
			||||||
    } else if (this.props.params.accountId === me && nextProps.params.accountId !== me) {
 | 
					 | 
				
			||||||
      dispatch(disconnectTimeline(`account:${me}`));
 | 
					      dispatch(disconnectTimeline(`account:${me}`));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillUnmount () {
 | 
					  componentWillUnmount () {
 | 
				
			||||||
    const { dispatch, params: { accountId } } = this.props;
 | 
					    const { dispatch, accountId } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (accountId === me) {
 | 
					    if (accountId === me) {
 | 
				
			||||||
      dispatch(disconnectTimeline(`account:${me}`));
 | 
					      dispatch(disconnectTimeline(`account:${me}`));
 | 
				
			||||||
@ -110,7 +125,7 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleLoadMore = maxId => {
 | 
					  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 () {
 | 
					  render () {
 | 
				
			||||||
@ -152,7 +167,7 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
				
			|||||||
        <ColumnBackButton multiColumn={multiColumn} />
 | 
					        <ColumnBackButton multiColumn={multiColumn} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <StatusList
 | 
					        <StatusList
 | 
				
			||||||
          prepend={<HeaderContainer accountId={this.props.params.accountId} />}
 | 
					          prepend={<HeaderContainer accountId={this.props.accountId} />}
 | 
				
			||||||
          alwaysPrepend
 | 
					          alwaysPrepend
 | 
				
			||||||
          append={remoteMessage}
 | 
					          append={remoteMessage}
 | 
				
			||||||
          scrollKey='account_timeline'
 | 
					          scrollKey='account_timeline'
 | 
				
			||||||
 | 
				
			|||||||
@ -19,13 +19,13 @@ export default class NavigationBar extends ImmutablePureComponent {
 | 
				
			|||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className='navigation-bar'>
 | 
					      <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>
 | 
					          <span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
 | 
				
			||||||
          <Avatar account={this.props.account} size={48} />
 | 
					          <Avatar account={this.props.account} size={48} />
 | 
				
			||||||
        </Permalink>
 | 
					        </Permalink>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className='navigation-bar__profile'>
 | 
					        <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>
 | 
					            <strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
 | 
				
			||||||
          </Permalink>
 | 
					          </Permalink>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ class ReplyIndicator extends ImmutablePureComponent {
 | 
				
			|||||||
  handleAccountClick = (e) => {
 | 
					  handleAccountClick = (e) => {
 | 
				
			||||||
    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
					    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      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'])}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -100,16 +100,16 @@ class Compose extends React.PureComponent {
 | 
				
			|||||||
        <nav className='drawer__header'>
 | 
					        <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>
 | 
					          <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') && (
 | 
					          {!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') && (
 | 
					          {!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>
 | 
					            <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') && (
 | 
					          {!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') && (
 | 
					          {!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='/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>
 | 
					          <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>
 | 
				
			||||||
 | 
				
			|||||||
@ -133,7 +133,7 @@ class Conversation extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete });
 | 
					    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 = {
 | 
					    const handlers = {
 | 
				
			||||||
      reply: this.handleReply,
 | 
					      reply: this.handleReply,
 | 
				
			||||||
 | 
				
			|||||||
@ -213,7 +213,7 @@ class AccountCard extends ImmutablePureComponent {
 | 
				
			|||||||
          <Permalink
 | 
					          <Permalink
 | 
				
			||||||
            className='directory__card__bar__name'
 | 
					            className='directory__card__bar__name'
 | 
				
			||||||
            href={account.get('url')}
 | 
					            href={account.get('url')}
 | 
				
			||||||
            to={`/accounts/${account.get('id')}`}
 | 
					            to={`/@${account.get('acct')}`}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <Avatar account={account} size={48} />
 | 
					            <Avatar account={account} size={48} />
 | 
				
			||||||
            <DisplayName account={account} />
 | 
					            <DisplayName account={account} />
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,7 @@ class AccountAuthorize extends ImmutablePureComponent {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className='account-authorize__wrapper'>
 | 
					      <div className='account-authorize__wrapper'>
 | 
				
			||||||
        <div className='account-authorize'>
 | 
					        <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>
 | 
					            <div className='account-authorize__avatar'><Avatar account={account} size={48} /></div>
 | 
				
			||||||
            <DisplayName account={account} />
 | 
					            <DisplayName account={account} />
 | 
				
			||||||
          </Permalink>
 | 
					          </Permalink>
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			|||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
import LoadingIndicator from '../../components/loading_indicator';
 | 
					import LoadingIndicator from '../../components/loading_indicator';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  fetchAccount,
 | 
					  lookupAccount,
 | 
				
			||||||
  fetchFollowers,
 | 
					  fetchFollowers,
 | 
				
			||||||
  expandFollowers,
 | 
					  expandFollowers,
 | 
				
			||||||
} from '../../actions/accounts';
 | 
					} from '../../actions/accounts';
 | 
				
			||||||
@ -19,15 +19,26 @@ import ScrollableList from '../../components/scrollable_list';
 | 
				
			|||||||
import MissingIndicator from 'mastodon/components/missing_indicator';
 | 
					import MissingIndicator from 'mastodon/components/missing_indicator';
 | 
				
			||||||
import TimelineHint from 'mastodon/components/timeline_hint';
 | 
					import TimelineHint from 'mastodon/components/timeline_hint';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapStateToProps = (state, props) => ({
 | 
					const mapStateToProps = (state, { params: { acct } }) => {
 | 
				
			||||||
  remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])),
 | 
					  const accountId = state.getIn(['accounts_map', acct]);
 | 
				
			||||||
  remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']),
 | 
					
 | 
				
			||||||
  isAccount: !!state.getIn(['accounts', props.params.accountId]),
 | 
					  if (!accountId) {
 | 
				
			||||||
  accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
 | 
					    return {
 | 
				
			||||||
  hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
 | 
					      isLoading: true,
 | 
				
			||||||
  isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true),
 | 
					    };
 | 
				
			||||||
  blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
 | 
					  }
 | 
				
			||||||
});
 | 
					
 | 
				
			||||||
 | 
					  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 }) => (
 | 
					const RemoteHint = ({ url }) => (
 | 
				
			||||||
  <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.followers' defaultMessage='Followers' />} />
 | 
					  <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.followers' defaultMessage='Followers' />} />
 | 
				
			||||||
@ -41,7 +52,10 @@ export default @connect(mapStateToProps)
 | 
				
			|||||||
class Followers extends ImmutablePureComponent {
 | 
					class Followers extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    params: PropTypes.object.isRequired,
 | 
					    params: PropTypes.shape({
 | 
				
			||||||
 | 
					      acct: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    }).isRequired,
 | 
				
			||||||
 | 
					    accountId: PropTypes.string,
 | 
				
			||||||
    dispatch: PropTypes.func.isRequired,
 | 
					    dispatch: PropTypes.func.isRequired,
 | 
				
			||||||
    accountIds: ImmutablePropTypes.list,
 | 
					    accountIds: ImmutablePropTypes.list,
 | 
				
			||||||
    hasMore: PropTypes.bool,
 | 
					    hasMore: PropTypes.bool,
 | 
				
			||||||
@ -53,22 +67,34 @@ class Followers extends ImmutablePureComponent {
 | 
				
			|||||||
    multiColumn: PropTypes.bool,
 | 
					    multiColumn: PropTypes.bool,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillMount () {
 | 
					  _load () {
 | 
				
			||||||
    if (!this.props.accountIds) {
 | 
					    const { accountId, dispatch } = this.props;
 | 
				
			||||||
      this.props.dispatch(fetchAccount(this.props.params.accountId));
 | 
					
 | 
				
			||||||
      this.props.dispatch(fetchFollowers(this.props.params.accountId));
 | 
					    dispatch(fetchFollowers(accountId));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentDidMount () {
 | 
				
			||||||
 | 
					    const { params: { acct }, accountId, dispatch } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (accountId) {
 | 
				
			||||||
 | 
					      this._load();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      dispatch(lookupAccount(acct));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillReceiveProps (nextProps) {
 | 
					  componentDidUpdate (prevProps) {
 | 
				
			||||||
    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
 | 
					    const { params: { acct }, accountId, dispatch } = this.props;
 | 
				
			||||||
      this.props.dispatch(fetchAccount(nextProps.params.accountId));
 | 
					
 | 
				
			||||||
      this.props.dispatch(fetchFollowers(nextProps.params.accountId));
 | 
					    if (prevProps.accountId !== accountId && accountId) {
 | 
				
			||||||
 | 
					      this._load();
 | 
				
			||||||
 | 
					    } else if (prevProps.params.acct !== acct) {
 | 
				
			||||||
 | 
					      dispatch(lookupAccount(acct));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleLoadMore = debounce(() => {
 | 
					  handleLoadMore = debounce(() => {
 | 
				
			||||||
    this.props.dispatch(expandFollowers(this.props.params.accountId));
 | 
					    this.props.dispatch(expandFollowers(this.props.accountId));
 | 
				
			||||||
  }, 300, { leading: true });
 | 
					  }, 300, { leading: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
@ -111,7 +137,7 @@ class Followers extends ImmutablePureComponent {
 | 
				
			|||||||
          hasMore={hasMore}
 | 
					          hasMore={hasMore}
 | 
				
			||||||
          isLoading={isLoading}
 | 
					          isLoading={isLoading}
 | 
				
			||||||
          onLoadMore={this.handleLoadMore}
 | 
					          onLoadMore={this.handleLoadMore}
 | 
				
			||||||
          prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
 | 
					          prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
 | 
				
			||||||
          alwaysPrepend
 | 
					          alwaysPrepend
 | 
				
			||||||
          append={remoteMessage}
 | 
					          append={remoteMessage}
 | 
				
			||||||
          emptyMessage={emptyMessage}
 | 
					          emptyMessage={emptyMessage}
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			|||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
import LoadingIndicator from '../../components/loading_indicator';
 | 
					import LoadingIndicator from '../../components/loading_indicator';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  fetchAccount,
 | 
					  lookupAccount,
 | 
				
			||||||
  fetchFollowing,
 | 
					  fetchFollowing,
 | 
				
			||||||
  expandFollowing,
 | 
					  expandFollowing,
 | 
				
			||||||
} from '../../actions/accounts';
 | 
					} from '../../actions/accounts';
 | 
				
			||||||
@ -19,15 +19,26 @@ import ScrollableList from '../../components/scrollable_list';
 | 
				
			|||||||
import MissingIndicator from 'mastodon/components/missing_indicator';
 | 
					import MissingIndicator from 'mastodon/components/missing_indicator';
 | 
				
			||||||
import TimelineHint from 'mastodon/components/timeline_hint';
 | 
					import TimelineHint from 'mastodon/components/timeline_hint';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapStateToProps = (state, props) => ({
 | 
					const mapStateToProps = (state, { params: { acct } }) => {
 | 
				
			||||||
  remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])),
 | 
					  const accountId = state.getIn(['accounts_map', acct]);
 | 
				
			||||||
  remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']),
 | 
					
 | 
				
			||||||
  isAccount: !!state.getIn(['accounts', props.params.accountId]),
 | 
					  if (!accountId) {
 | 
				
			||||||
  accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
 | 
					    return {
 | 
				
			||||||
  hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
 | 
					      isLoading: true,
 | 
				
			||||||
  isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true),
 | 
					    };
 | 
				
			||||||
  blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
 | 
					  }
 | 
				
			||||||
});
 | 
					
 | 
				
			||||||
 | 
					  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 }) => (
 | 
					const RemoteHint = ({ url }) => (
 | 
				
			||||||
  <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.follows' defaultMessage='Follows' />} />
 | 
					  <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.follows' defaultMessage='Follows' />} />
 | 
				
			||||||
@ -41,7 +52,10 @@ export default @connect(mapStateToProps)
 | 
				
			|||||||
class Following extends ImmutablePureComponent {
 | 
					class Following extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    params: PropTypes.object.isRequired,
 | 
					    params: PropTypes.shape({
 | 
				
			||||||
 | 
					      acct: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    }).isRequired,
 | 
				
			||||||
 | 
					    accountId: PropTypes.string,
 | 
				
			||||||
    dispatch: PropTypes.func.isRequired,
 | 
					    dispatch: PropTypes.func.isRequired,
 | 
				
			||||||
    accountIds: ImmutablePropTypes.list,
 | 
					    accountIds: ImmutablePropTypes.list,
 | 
				
			||||||
    hasMore: PropTypes.bool,
 | 
					    hasMore: PropTypes.bool,
 | 
				
			||||||
@ -53,22 +67,34 @@ class Following extends ImmutablePureComponent {
 | 
				
			|||||||
    multiColumn: PropTypes.bool,
 | 
					    multiColumn: PropTypes.bool,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillMount () {
 | 
					  _load () {
 | 
				
			||||||
    if (!this.props.accountIds) {
 | 
					    const { accountId, dispatch } = this.props;
 | 
				
			||||||
      this.props.dispatch(fetchAccount(this.props.params.accountId));
 | 
					
 | 
				
			||||||
      this.props.dispatch(fetchFollowing(this.props.params.accountId));
 | 
					    dispatch(fetchFollowing(accountId));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentDidMount () {
 | 
				
			||||||
 | 
					    const { params: { acct }, accountId, dispatch } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (accountId) {
 | 
				
			||||||
 | 
					      this._load();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      dispatch(lookupAccount(acct));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillReceiveProps (nextProps) {
 | 
					  componentDidUpdate (prevProps) {
 | 
				
			||||||
    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
 | 
					    const { params: { acct }, accountId, dispatch } = this.props;
 | 
				
			||||||
      this.props.dispatch(fetchAccount(nextProps.params.accountId));
 | 
					
 | 
				
			||||||
      this.props.dispatch(fetchFollowing(nextProps.params.accountId));
 | 
					    if (prevProps.accountId !== accountId && accountId) {
 | 
				
			||||||
 | 
					      this._load();
 | 
				
			||||||
 | 
					    } else if (prevProps.params.acct !== acct) {
 | 
				
			||||||
 | 
					      dispatch(lookupAccount(acct));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleLoadMore = debounce(() => {
 | 
					  handleLoadMore = debounce(() => {
 | 
				
			||||||
    this.props.dispatch(expandFollowing(this.props.params.accountId));
 | 
					    this.props.dispatch(expandFollowing(this.props.accountId));
 | 
				
			||||||
  }, 300, { leading: true });
 | 
					  }, 300, { leading: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
@ -111,7 +137,7 @@ class Following extends ImmutablePureComponent {
 | 
				
			|||||||
          hasMore={hasMore}
 | 
					          hasMore={hasMore}
 | 
				
			||||||
          isLoading={isLoading}
 | 
					          isLoading={isLoading}
 | 
				
			||||||
          onLoadMore={this.handleLoadMore}
 | 
					          onLoadMore={this.handleLoadMore}
 | 
				
			||||||
          prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
 | 
					          prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
 | 
				
			||||||
          alwaysPrepend
 | 
					          alwaysPrepend
 | 
				
			||||||
          append={remoteMessage}
 | 
					          append={remoteMessage}
 | 
				
			||||||
          emptyMessage={emptyMessage}
 | 
					          emptyMessage={emptyMessage}
 | 
				
			||||||
 | 
				
			|||||||
@ -87,7 +87,7 @@ class Content extends ImmutablePureComponent {
 | 
				
			|||||||
  onMentionClick = (mention, e) => {
 | 
					  onMentionClick = (mention, e) => {
 | 
				
			||||||
    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
					    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      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)) {
 | 
					    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      e.preventDefault();
 | 
				
			||||||
      this.context.router.history.push(`/timelines/tag/${hashtag}`);
 | 
					      this.context.router.history.push(`/tags/${hashtag}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onStatusClick = (status, e) => {
 | 
					  onStatusClick = (status, e) => {
 | 
				
			||||||
    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
					    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      e.preventDefault();
 | 
				
			||||||
      this.context.router.history.push(`/statuses/${status.get('id')}`);
 | 
					      this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -82,7 +82,7 @@ class GettingStarted extends ImmutablePureComponent {
 | 
				
			|||||||
    const { fetchFollowRequests, multiColumn } = this.props;
 | 
					    const { fetchFollowRequests, multiColumn } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
 | 
					    if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
 | 
				
			||||||
      this.context.router.history.replace('/timelines/home');
 | 
					      this.context.router.history.replace('/home');
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -98,8 +98,8 @@ class GettingStarted extends ImmutablePureComponent {
 | 
				
			|||||||
    if (multiColumn) {
 | 
					    if (multiColumn) {
 | 
				
			||||||
      navItems.push(
 | 
					      navItems.push(
 | 
				
			||||||
        <ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />,
 | 
					        <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='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='/timelines/public' />,
 | 
					        <ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      height += 34 + 48*2;
 | 
					      height += 34 + 48*2;
 | 
				
			||||||
@ -127,13 +127,13 @@ class GettingStarted extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (multiColumn && !columns.find(item => item.get('id') === 'HOME')) {
 | 
					    if (multiColumn && !columns.find(item => item.get('id') === 'HOME')) {
 | 
				
			||||||
      navItems.push(
 | 
					      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;
 | 
					      height += 48;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    navItems.push(
 | 
					    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='bookmark' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
 | 
				
			||||||
      <ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
 | 
					      <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' />,
 | 
					      <ColumnLink key='lists' icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />,
 | 
				
			||||||
 | 
				
			|||||||
@ -73,7 +73,7 @@ class Lists extends ImmutablePureComponent {
 | 
				
			|||||||
          bindToDocument={!multiColumn}
 | 
					          bindToDocument={!multiColumn}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {lists.map(list =>
 | 
					          {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>
 | 
					        </ScrollableList>
 | 
				
			||||||
      </Column>
 | 
					      </Column>
 | 
				
			||||||
 | 
				
			|||||||
@ -42,7 +42,7 @@ class FollowRequest extends ImmutablePureComponent {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className='account'>
 | 
					      <div className='account'>
 | 
				
			||||||
        <div className='account__wrapper'>
 | 
					        <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>
 | 
					            <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
 | 
				
			||||||
            <DisplayName account={account} />
 | 
					            <DisplayName account={account} />
 | 
				
			||||||
          </Permalink>
 | 
					          </Permalink>
 | 
				
			||||||
 | 
				
			|||||||
@ -68,7 +68,7 @@ class Notification extends ImmutablePureComponent {
 | 
				
			|||||||
    const { notification } = this.props;
 | 
					    const { notification } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (notification.get('status')) {
 | 
					    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 {
 | 
					    } else {
 | 
				
			||||||
      this.handleOpenProfile();
 | 
					      this.handleOpenProfile();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -76,7 +76,7 @@ class Notification extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  handleOpenProfile = () => {
 | 
					  handleOpenProfile = () => {
 | 
				
			||||||
    const { notification } = this.props;
 | 
					    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 => {
 | 
					  handleMention = e => {
 | 
				
			||||||
@ -315,7 +315,7 @@ class Notification extends ImmutablePureComponent {
 | 
				
			|||||||
    const { notification } = this.props;
 | 
					    const { notification } = this.props;
 | 
				
			||||||
    const account          = notification.get('account');
 | 
					    const account          = notification.get('account');
 | 
				
			||||||
    const displayNameHtml  = { __html: account.get('display_name_html') };
 | 
					    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')) {
 | 
					    switch(notification.get('type')) {
 | 
				
			||||||
    case 'follow':
 | 
					    case 'follow':
 | 
				
			||||||
 | 
				
			|||||||
@ -120,7 +120,7 @@ class Footer extends ImmutablePureComponent {
 | 
				
			|||||||
      onClose();
 | 
					      onClose();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    router.history.push(`/statuses/${status.get('id')}`);
 | 
					    router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,7 @@ class Header extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className='picture-in-picture__header'>
 | 
					      <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} />
 | 
					          <Avatar account={account} size={36} />
 | 
				
			||||||
          <DisplayName account={account} />
 | 
					          <DisplayName account={account} />
 | 
				
			||||||
        </Link>
 | 
					        </Link>
 | 
				
			||||||
 | 
				
			|||||||
@ -55,7 +55,7 @@ class DetailedStatus extends ImmutablePureComponent {
 | 
				
			|||||||
  handleAccountClick = (e) => {
 | 
					  handleAccountClick = (e) => {
 | 
				
			||||||
    if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) {
 | 
					    if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      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();
 | 
					    e.stopPropagation();
 | 
				
			||||||
@ -195,7 +195,7 @@ class DetailedStatus extends ImmutablePureComponent {
 | 
				
			|||||||
      reblogLink = (
 | 
					      reblogLink = (
 | 
				
			||||||
        <React.Fragment>
 | 
					        <React.Fragment>
 | 
				
			||||||
          <React.Fragment> · </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} />
 | 
					            <Icon id={reblogIcon} />
 | 
				
			||||||
            <span className='detailed-status__reblogs'>
 | 
					            <span className='detailed-status__reblogs'>
 | 
				
			||||||
              <AnimatedNumber value={status.get('reblogs_count')} />
 | 
					              <AnimatedNumber value={status.get('reblogs_count')} />
 | 
				
			||||||
@ -219,7 +219,7 @@ class DetailedStatus extends ImmutablePureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (this.context.router) {
 | 
					    if (this.context.router) {
 | 
				
			||||||
      favouriteLink = (
 | 
					      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' />
 | 
					          <Icon id='star' />
 | 
				
			||||||
          <span className='detailed-status__favorites'>
 | 
					          <span className='detailed-status__favorites'>
 | 
				
			||||||
            <AnimatedNumber value={status.get('favourites_count')} />
 | 
					            <AnimatedNumber value={status.get('favourites_count')} />
 | 
				
			||||||
 | 
				
			|||||||
@ -396,7 +396,7 @@ class Status extends ImmutablePureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyOpenProfile = () => {
 | 
					  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 = () => {
 | 
					  handleHotkeyToggleHidden = () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -68,7 +68,7 @@ class BoostModal extends ImmutablePureComponent {
 | 
				
			|||||||
    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
					    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
				
			||||||
      e.preventDefault();
 | 
					      e.preventDefault();
 | 
				
			||||||
      this.props.onClose();
 | 
					      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'])}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -216,7 +216,7 @@ class ColumnsArea extends ImmutablePureComponent {
 | 
				
			|||||||
    const columnIndex = getIndex(this.context.router.history.location.pathname);
 | 
					    const columnIndex = getIndex(this.context.router.history.location.pathname);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (singleColumn) {
 | 
					    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>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const content = columnIndex !== -1 ? (
 | 
					      const content = columnIndex !== -1 ? (
 | 
				
			||||||
        <ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>
 | 
					        <ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>
 | 
				
			||||||
 | 
				
			|||||||
@ -46,7 +46,7 @@ class ListPanel extends ImmutablePureComponent {
 | 
				
			|||||||
        <hr />
 | 
					        <hr />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {lists.map(list => (
 | 
					        {lists.map(list => (
 | 
				
			||||||
          <NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/timelines/list/${list.get('id')}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.get('title')}</NavLink>
 | 
					          <NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/lists/${list.get('id')}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.get('title')}</NavLink>
 | 
				
			||||||
        ))}
 | 
					        ))}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
				
			|||||||
@ -128,13 +128,6 @@ class MediaModal extends ImmutablePureComponent {
 | 
				
			|||||||
    }));
 | 
					    }));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleStatusClick = e => {
 | 
					 | 
				
			||||||
    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | 
					 | 
				
			||||||
      e.preventDefault();
 | 
					 | 
				
			||||||
      this.context.router.history.push(`/statuses/${this.props.statusId}`);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { media, statusId, intl, onClose } = this.props;
 | 
					    const { media, statusId, intl, onClose } = this.props;
 | 
				
			||||||
    const { navigationHidden } = this.state;
 | 
					    const { navigationHidden } = this.state;
 | 
				
			||||||
 | 
				
			|||||||
@ -10,12 +10,12 @@ import TrendsContainer from 'mastodon/features/getting_started/containers/trends
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const NavigationPanel = () => (
 | 
					const NavigationPanel = () => (
 | 
				
			||||||
  <div className='navigation-panel'>
 | 
					  <div className='navigation-panel'>
 | 
				
			||||||
    <NavLink className='column-link column-link--transparent' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
 | 
					    <NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
 | 
				
			||||||
    <NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>
 | 
					    <NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>
 | 
				
			||||||
    <FollowRequestsNavLink />
 | 
					    <FollowRequestsNavLink />
 | 
				
			||||||
    <NavLink className='column-link column-link--transparent' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
 | 
					    <NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
 | 
				
			||||||
    <NavLink className='column-link column-link--transparent' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
 | 
					    <NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
 | 
				
			||||||
    <NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
 | 
					    <NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
 | 
				
			||||||
    <NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink>
 | 
					    <NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink>
 | 
				
			||||||
    <NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink>
 | 
					    <NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink>
 | 
				
			||||||
    <NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
 | 
					    <NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
 | 
				
			||||||
 | 
				
			|||||||
@ -8,10 +8,10 @@ import Icon from 'mastodon/components/icon';
 | 
				
			|||||||
import NotificationsCounterIcon from './notifications_counter_icon';
 | 
					import NotificationsCounterIcon from './notifications_counter_icon';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const links = [
 | 
					export const links = [
 | 
				
			||||||
  <NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
 | 
					  <NavLink className='tabs-bar__link' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
 | 
				
			||||||
  <NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
 | 
					  <NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
 | 
				
			||||||
  <NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
 | 
					  <NavLink className='tabs-bar__link' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
 | 
				
			||||||
  <NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
 | 
					  <NavLink className='tabs-bar__link' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
 | 
				
			||||||
  <NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
 | 
					  <NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
 | 
				
			||||||
  <NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
 | 
					  <NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
				
			|||||||
@ -72,6 +72,7 @@ const mapStateToProps = state => ({
 | 
				
			|||||||
  canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
 | 
					  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,
 | 
					  dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
 | 
				
			||||||
  firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
 | 
					  firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
 | 
				
			||||||
 | 
					  username: state.getIn(['accounts', me, 'username']),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const keyMap = {
 | 
					const keyMap = {
 | 
				
			||||||
@ -144,7 +145,7 @@ class SwitchingColumnsArea extends React.PureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { children, mobile } = this.props;
 | 
					    const { children, mobile } = this.props;
 | 
				
			||||||
    const redirect = mobile ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
 | 
					    const redirect = mobile ? <Redirect from='/' to='/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
 | 
					      <ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
 | 
				
			||||||
@ -152,32 +153,32 @@ class SwitchingColumnsArea extends React.PureComponent {
 | 
				
			|||||||
          {redirect}
 | 
					          {redirect}
 | 
				
			||||||
          <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
 | 
					          <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
 | 
					          <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
 | 
					 | 
				
			||||||
          <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
 | 
					 | 
				
			||||||
          <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} />
 | 
					 | 
				
			||||||
          <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
 | 
					 | 
				
			||||||
          <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
 | 
					 | 
				
			||||||
          <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <WrappedRoute path='/home' component={HomeTimeline} content={children} />
 | 
				
			||||||
 | 
					          <WrappedRoute path='/public' exact component={PublicTimeline} content={children} />
 | 
				
			||||||
 | 
					          <WrappedRoute path='/public/local' exact component={CommunityTimeline} content={children} />
 | 
				
			||||||
 | 
					          <WrappedRoute path='/conversations' component={DirectTimeline} content={children} />
 | 
				
			||||||
 | 
					          <WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
 | 
				
			||||||
 | 
					          <WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/notifications' component={Notifications} content={children} />
 | 
					          <WrappedRoute path='/notifications' component={Notifications} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
 | 
					          <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
 | 
					          <WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
 | 
					          <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <WrappedRoute path='/start' component={FollowRecommendations} content={children} />
 | 
					          <WrappedRoute path='/start' component={FollowRecommendations} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/search' component={Search} content={children} />
 | 
					          <WrappedRoute path='/search' component={Search} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/directory' component={Directory} content={children} />
 | 
					          <WrappedRoute path='/directory' component={Directory} content={children} />
 | 
				
			||||||
 | 
					          <WrappedRoute path='/publish' component={Compose} content={children} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <WrappedRoute path='/statuses/new' component={Compose} content={children} />
 | 
					          <WrappedRoute path='/@:acct' exact component={AccountTimeline} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
 | 
					          <WrappedRoute path='/@:acct/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
 | 
				
			||||||
          <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
 | 
					          <WrappedRoute path='/@:acct/followers' component={Followers} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
 | 
					          <WrappedRoute path='/@:acct/following' component={Following} content={children} />
 | 
				
			||||||
 | 
					          <WrappedRoute path='/@:acct/media' component={AccountGallery} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
 | 
					          <WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
 | 
					          <WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
 | 
					          <WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
 | 
					 | 
				
			||||||
          <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
 | 
					          <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
 | 
				
			||||||
          <WrappedRoute path='/blocks' component={Blocks} content={children} />
 | 
					          <WrappedRoute path='/blocks' component={Blocks} content={children} />
 | 
				
			||||||
@ -214,6 +215,7 @@ class UI extends React.PureComponent {
 | 
				
			|||||||
    dropdownMenuIsOpen: PropTypes.bool,
 | 
					    dropdownMenuIsOpen: PropTypes.bool,
 | 
				
			||||||
    layout: PropTypes.string.isRequired,
 | 
					    layout: PropTypes.string.isRequired,
 | 
				
			||||||
    firstLaunch: PropTypes.bool,
 | 
					    firstLaunch: PropTypes.bool,
 | 
				
			||||||
 | 
					    username: PropTypes.string,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  state = {
 | 
					  state = {
 | 
				
			||||||
@ -451,7 +453,7 @@ class UI extends React.PureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyGoToHome = () => {
 | 
					  handleHotkeyGoToHome = () => {
 | 
				
			||||||
    this.context.router.history.push('/timelines/home');
 | 
					    this.context.router.history.push('/home');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyGoToNotifications = () => {
 | 
					  handleHotkeyGoToNotifications = () => {
 | 
				
			||||||
@ -459,15 +461,15 @@ class UI extends React.PureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyGoToLocal = () => {
 | 
					  handleHotkeyGoToLocal = () => {
 | 
				
			||||||
    this.context.router.history.push('/timelines/public/local');
 | 
					    this.context.router.history.push('/public/local');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyGoToFederated = () => {
 | 
					  handleHotkeyGoToFederated = () => {
 | 
				
			||||||
    this.context.router.history.push('/timelines/public');
 | 
					    this.context.router.history.push('/public');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyGoToDirect = () => {
 | 
					  handleHotkeyGoToDirect = () => {
 | 
				
			||||||
    this.context.router.history.push('/timelines/direct');
 | 
					    this.context.router.history.push('/conversations');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyGoToStart = () => {
 | 
					  handleHotkeyGoToStart = () => {
 | 
				
			||||||
@ -483,7 +485,7 @@ class UI extends React.PureComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyGoToProfile = () => {
 | 
					  handleHotkeyGoToProfile = () => {
 | 
				
			||||||
    this.context.router.history.push(`/accounts/${me}`);
 | 
					    this.context.router.history.push(`/@${this.props.username}`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyGoToBlocked = () => {
 | 
					  handleHotkeyGoToBlocked = () => {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								app/javascript/mastodon/reducers/accounts_map.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/javascript/mastodon/reducers/accounts_map.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
 | 
				
			||||||
 | 
					import { Map as ImmutableMap } from 'immutable';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initialState = ImmutableMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function accountsMap(state = initialState, action) {
 | 
				
			||||||
 | 
					  switch(action.type) {
 | 
				
			||||||
 | 
					  case ACCOUNT_IMPORT:
 | 
				
			||||||
 | 
					    return state.set(action.account.acct, action.account.id);
 | 
				
			||||||
 | 
					  case ACCOUNTS_IMPORT:
 | 
				
			||||||
 | 
					    return state.withMutations(map => action.accounts.forEach(account => map.set(account.acct, account.id)));
 | 
				
			||||||
 | 
					  default:
 | 
				
			||||||
 | 
					    return state;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -38,6 +38,7 @@ import missed_updates from './missed_updates';
 | 
				
			|||||||
import announcements from './announcements';
 | 
					import announcements from './announcements';
 | 
				
			||||||
import markers from './markers';
 | 
					import markers from './markers';
 | 
				
			||||||
import picture_in_picture from './picture_in_picture';
 | 
					import picture_in_picture from './picture_in_picture';
 | 
				
			||||||
 | 
					import accounts_map from './accounts_map';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const reducers = {
 | 
					const reducers = {
 | 
				
			||||||
  announcements,
 | 
					  announcements,
 | 
				
			||||||
@ -52,6 +53,7 @@ const reducers = {
 | 
				
			|||||||
  status_lists,
 | 
					  status_lists,
 | 
				
			||||||
  accounts,
 | 
					  accounts,
 | 
				
			||||||
  accounts_counters,
 | 
					  accounts_counters,
 | 
				
			||||||
 | 
					  accounts_map,
 | 
				
			||||||
  statuses,
 | 
					  statuses,
 | 
				
			||||||
  relationships,
 | 
					  relationships,
 | 
				
			||||||
  settings,
 | 
					  settings,
 | 
				
			||||||
 | 
				
			|||||||
@ -90,7 +90,7 @@ const handlePush = (event) => {
 | 
				
			|||||||
      options.tag       = notification.id;
 | 
					      options.tag       = notification.id;
 | 
				
			||||||
      options.badge     = '/badge.png';
 | 
					      options.badge     = '/badge.png';
 | 
				
			||||||
      options.image     = notification.status && notification.status.media_attachments.length > 0 && notification.status.media_attachments[0].preview_url || undefined;
 | 
					      options.image     = notification.status && notification.status.media_attachments.length > 0 && notification.status.media_attachments[0].preview_url || undefined;
 | 
				
			||||||
      options.data      = { access_token, preferred_locale, id: notification.status ? notification.status.id : notification.account.id, url: notification.status ? `/web/statuses/${notification.status.id}` : `/web/accounts/${notification.account.id}` };
 | 
					      options.data      = { access_token, preferred_locale, id: notification.status ? notification.status.id : notification.account.id, url: notification.status ? `/web/@${notification.account.acct}/${notification.status.id}` : `/web/@${notification.account.acct}` };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (notification.status && notification.status.spoiler_text || notification.status.sensitive) {
 | 
					      if (notification.status && notification.status.spoiler_text || notification.status.sensitive) {
 | 
				
			||||||
        options.data.hiddenBody  = htmlToPlainText(notification.status.content);
 | 
					        options.data.hiddenBody  = htmlToPlainText(notification.status.content);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										63
									
								
								app/lib/permalink_redirector.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								app/lib/permalink_redirector.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PermalinkRedirector
 | 
				
			||||||
 | 
					  include RoutingHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def initialize(path)
 | 
				
			||||||
 | 
					    @path = path
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def redirect_path
 | 
				
			||||||
 | 
					    if path_segments[0] == 'web'
 | 
				
			||||||
 | 
					      if path_segments[1].present? && path_segments[1].start_with?('@') && path_segments[2] =~ /\d/
 | 
				
			||||||
 | 
					        find_status_url_by_id(path_segments[2])
 | 
				
			||||||
 | 
					      elsif path_segments[1].present? && path_segments[1].start_with?('@')
 | 
				
			||||||
 | 
					        find_account_url_by_name(path_segments[1])
 | 
				
			||||||
 | 
					      elsif path_segments[1] == 'statuses' && path_segments[2] =~ /\d/
 | 
				
			||||||
 | 
					        find_status_url_by_id(path_segments[2])
 | 
				
			||||||
 | 
					      elsif path_segments[1] == 'accounts' && path_segments[2] =~ /\d/
 | 
				
			||||||
 | 
					        find_account_url_by_id(path_segments[2])
 | 
				
			||||||
 | 
					      elsif path_segments[1] == 'timelines' && path_segments[2] == 'tag' && path_segments[3].present?
 | 
				
			||||||
 | 
					        find_tag_url_by_name(path_segments[3])
 | 
				
			||||||
 | 
					      elsif path_segments[1] == 'tags' && path_segments[2].present?
 | 
				
			||||||
 | 
					        find_tag_url_by_name(path_segments[2])
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def path_segments
 | 
				
			||||||
 | 
					    @path_segments ||= @path.gsub(/\A\//, '').split('/')
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def find_status_url_by_id(id)
 | 
				
			||||||
 | 
					    status = Status.find_by(id: id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return unless status&.distributable?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ActivityPub::TagManager.instance.url_for(status)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def find_account_url_by_id(id)
 | 
				
			||||||
 | 
					    account = Account.find_by(id: id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return unless account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ActivityPub::TagManager.instance.url_for(account)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def find_account_url_by_name(name)
 | 
				
			||||||
 | 
					    username, domain = name.gsub(/\A@/, '').split('@')
 | 
				
			||||||
 | 
					    domain           = nil if TagManager.instance.local_domain?(domain)
 | 
				
			||||||
 | 
					    account          = Account.find_remote(username, domain)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return unless account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ActivityPub::TagManager.instance.url_for(account)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def find_tag_url_by_name(name)
 | 
				
			||||||
 | 
					    tag_path(CGI.unescape(name))
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@ -8,8 +8,10 @@ RSpec.describe HomeController, type: :controller do
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    context 'when not signed in' do
 | 
					    context 'when not signed in' do
 | 
				
			||||||
      context 'when requested path is tag timeline' do
 | 
					      context 'when requested path is tag timeline' do
 | 
				
			||||||
        before { @request.path = '/web/timelines/tag/name' }
 | 
					        it 'redirects to the tag\'s permalink' do
 | 
				
			||||||
        it { is_expected.to redirect_to '/tags/name' }
 | 
					          @request.path = '/web/timelines/tag/name'
 | 
				
			||||||
 | 
					          is_expected.to redirect_to '/tags/name'
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'redirects to about page' do
 | 
					      it 'redirects to about page' do
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										42
									
								
								spec/lib/permalink_redirector_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								spec/lib/permalink_redirector_spec.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe PermalinkRedirector do
 | 
				
			||||||
 | 
					  describe '#redirect_url' do
 | 
				
			||||||
 | 
					    before do
 | 
				
			||||||
 | 
					      account = Fabricate(:account, username: 'alice', id: 1)
 | 
				
			||||||
 | 
					      Fabricate(:status, account: account, id: 123)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'returns path for legacy account links' do
 | 
				
			||||||
 | 
					      redirector = described_class.new('web/accounts/1')
 | 
				
			||||||
 | 
					      expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'returns path for legacy status links' do
 | 
				
			||||||
 | 
					      redirector = described_class.new('web/statuses/123')
 | 
				
			||||||
 | 
					      expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice/123'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'returns path for legacy tag links' do
 | 
				
			||||||
 | 
					      redirector = described_class.new('web/timelines/tag/hoge')
 | 
				
			||||||
 | 
					      expect(redirector.redirect_path).to eq '/tags/hoge'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'returns path for pretty account links' do
 | 
				
			||||||
 | 
					      redirector = described_class.new('web/@alice')
 | 
				
			||||||
 | 
					      expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'returns path for pretty status links' do
 | 
				
			||||||
 | 
					      redirector = described_class.new('web/@alice/123')
 | 
				
			||||||
 | 
					      expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice/123'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'returns path for pretty tag links' do
 | 
				
			||||||
 | 
					      redirector = described_class.new('web/tags/hoge')
 | 
				
			||||||
 | 
					      expect(redirector.redirect_path).to eq '/tags/hoge'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user