diff --git a/app/controllers/account_follow_controller.rb b/app/controllers/account_follow_controller.rb deleted file mode 100644 index 33394074d..000000000 --- a/app/controllers/account_follow_controller.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class AccountFollowController < ApplicationController - include AccountControllerConcern - - before_action :authenticate_user! - - def create - FollowService.new.call(current_user.account, @account, with_rate_limit: true) - redirect_to account_path(@account) - end -end diff --git a/app/controllers/account_unfollow_controller.rb b/app/controllers/account_unfollow_controller.rb deleted file mode 100644 index 378ec86dc..000000000 --- a/app/controllers/account_unfollow_controller.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class AccountUnfollowController < ApplicationController - include AccountControllerConcern - - before_action :authenticate_user! - - def create - UnfollowService.new.call(current_user.account, @account) - redirect_to account_path(@account) - end -end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index b902ada09..4ca37b9ef 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -5,11 +5,11 @@ class AccountsController < ApplicationController PAGE_SIZE_MAX = 200 include AccountControllerConcern + include WebAppControllerConcern include SignatureAuthentication before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_cache_headers - before_action :set_body_classes skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) } skip_before_action :require_functional!, unless: :whitelist_mode? @@ -18,24 +18,6 @@ class AccountsController < ApplicationController respond_to do |format| format.html do expires_in 0, public: true unless user_signed_in? - - @pinned_statuses = [] - @endorsed_accounts = @account.endorsed_accounts.to_a.sample(4) - @featured_hashtags = @account.featured_tags.order(statuses_count: :desc) - - if current_account && @account.blocking?(current_account) - @statuses = [] - return - end - - @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? - @statuses = cached_filtered_status_page - @rss_url = rss_url - - unless @statuses.empty? - @older_url = older_url if @statuses.last.id > filtered_statuses.last.id - @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id - end end format.rss do @@ -56,10 +38,6 @@ class AccountsController < ApplicationController private - def set_body_classes - @body_classes = 'with-modals' - end - def show_pinned_statuses? [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none? end diff --git a/app/controllers/api/v1/accounts/lookup_controller.rb b/app/controllers/api/v1/accounts/lookup_controller.rb new file mode 100644 index 000000000..aee6be18a --- /dev/null +++ b/app/controllers/api/v1/accounts/lookup_controller.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::LookupController < Api::BaseController + before_action -> { authorize_if_got_token! :read, :'read:accounts' } + before_action :set_account + + def show + render json: @account, serializer: REST::AccountSerializer + end + + private + + def set_account + @account = ResolveAccountService.new.call(params[:acct], skip_webfinger: true) || raise(ActiveRecord::RecordNotFound) + end +end diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb index 29c0288d0..1d519c96f 100644 --- a/app/controllers/authorize_interactions_controller.rb +++ b/app/controllers/authorize_interactions_controller.rb @@ -13,7 +13,7 @@ class AuthorizeInteractionsController < ApplicationController if @resource.is_a?(Account) render :show elsif @resource.is_a?(Status) - redirect_to web_url("statuses/#{@resource.id}") + redirect_to short_account_status_path(@resource.account.acct, @resource.id) else render :error end diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb index 11eac0eb6..f05fa3f91 100644 --- a/app/controllers/concerns/account_controller_concern.rb +++ b/app/controllers/concerns/account_controller_concern.rb @@ -8,18 +8,11 @@ module AccountControllerConcern FOLLOW_PER_PAGE = 12 included do - layout 'public' - - before_action :set_instance_presenter before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html } end private - def set_instance_presenter - @instance_presenter = InstancePresenter.new - end - def set_link_headers response.headers['Link'] = LinkHeader.new( [ diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb new file mode 100644 index 000000000..8a6c73af3 --- /dev/null +++ b/app/controllers/concerns/web_app_controller_concern.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module WebAppControllerConcern + extend ActiveSupport::Concern + + included do + before_action :set_body_classes + before_action :set_referrer_policy_header + end + + def set_body_classes + @body_classes = 'app-body' + end + + def set_referrer_policy_header + response.headers['Referrer-Policy'] = 'origin' + end +end diff --git a/app/controllers/directories_controller.rb b/app/controllers/directories_controller.rb index f198ad5ba..cfd0fa656 100644 --- a/app/controllers/directories_controller.rb +++ b/app/controllers/directories_controller.rb @@ -1,42 +1,20 @@ # frozen_string_literal: true class DirectoriesController < ApplicationController - layout 'public' + include WebAppControllerConcern before_action :authenticate_user!, if: :whitelist_mode? before_action :require_enabled! - before_action :set_instance_presenter - before_action :set_tag, only: :show - before_action :set_accounts skip_before_action :require_functional!, unless: :whitelist_mode? def index - render :index - end - - def show - render :index + expires_in 0, public: true if current_account.nil? end private def require_enabled! - return not_found unless Setting.profile_directory - end - - def set_tag - @tag = Tag.discoverable.find_normalized!(params[:id]) - end - - def set_accounts - @accounts = Account.local.discoverable.by_recent_status.page(params[:page]).per(20).tap do |query| - query.merge!(Account.tagged_with(@tag.id)) if @tag - query.merge!(Account.not_excluded_by_account(current_account)) if current_account - end - end - - def set_instance_presenter - @instance_presenter = InstancePresenter.new + not_found unless Setting.profile_directory end end diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index ff4df2adf..ebdefcf9b 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -2,6 +2,7 @@ class FollowerAccountsController < ApplicationController include AccountControllerConcern + include WebAppControllerConcern include SignatureVerification before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } @@ -14,10 +15,6 @@ class FollowerAccountsController < ApplicationController respond_to do |format| format.html do expires_in 0, public: true unless user_signed_in? - - next if @account.user_hides_network? - - follows end format.json do @@ -36,6 +33,10 @@ class FollowerAccountsController < ApplicationController private + def username_param + params[:username] || params[:account_username] + end + def follows return @follows if defined?(@follows) diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 6bb95c454..4500fb5d7 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -2,6 +2,7 @@ class FollowingAccountsController < ApplicationController include AccountControllerConcern + include WebAppControllerConcern include SignatureVerification before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } @@ -14,10 +15,6 @@ class FollowingAccountsController < ApplicationController respond_to do |format| format.html do expires_in 0, public: true unless user_signed_in? - - next if @account.user_hides_network? - - follows end format.json do @@ -36,6 +33,10 @@ class FollowingAccountsController < ApplicationController private + def username_param + params[:username] || params[:account_username] + end + def follows return @follows if defined?(@follows) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 702889cd0..72573d231 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,47 +1,16 @@ # frozen_string_literal: true class HomeController < ApplicationController - before_action :redirect_unauthenticated_to_permalinks! before_action :authenticate_user! - before_action :set_referrer_policy_header - def index - @body_classes = 'app-body' - end + include WebAppControllerConcern + + def index; end private - def redirect_unauthenticated_to_permalinks! - return if user_signed_in? - - matches = request.path.match(/\A\/web\/(statuses|accounts)\/([\d]+)\z/) - - if matches - case matches[1] - when 'statuses' - status = Status.find_by(id: matches[2]) - - if status&.distributable? - redirect_to(ActivityPub::TagManager.instance.url_for(status)) - return - end - when 'accounts' - account = Account.find_by(id: matches[2]) - - if account - redirect_to(ActivityPub::TagManager.instance.url_for(account)) - return - end - end - end - - matches = request.path.match(%r{\A/web/timelines/tag/(?.+)\z}) - - redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path) - end - def default_redirect_path - if request.path.start_with?('/web') || whitelist_mode? + if whitelist_mode? new_user_session_path elsif single_user_mode? short_account_path(Account.local.without_suspended.where('id > 0').first) @@ -49,8 +18,4 @@ class HomeController < ApplicationController about_path end end - - def set_referrer_policy_header - response.headers['Referrer-Policy'] = 'origin' - end end diff --git a/app/controllers/public_timelines_controller.rb b/app/controllers/public_timelines_controller.rb index 1332ba16c..1fcb9fbca 100644 --- a/app/controllers/public_timelines_controller.rb +++ b/app/controllers/public_timelines_controller.rb @@ -1,26 +1,18 @@ # frozen_string_literal: true class PublicTimelinesController < ApplicationController - layout 'public' + include WebAppControllerConcern before_action :authenticate_user!, if: :whitelist_mode? before_action :require_enabled! - before_action :set_body_classes - before_action :set_instance_presenter - def show; end + def show + expires_in 0, public: true if current_account.nil? + end private def require_enabled! - not_found unless Setting.timeline_preview - end - - def set_body_classes - @body_classes = 'with-modals' - end - - def set_instance_presenter - @instance_presenter = InstancePresenter.new + not_found unless user_signed_in? || Setting.timeline_preview end end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 87612a296..a55b40103 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -1,21 +1,17 @@ # frozen_string_literal: true class StatusesController < ApplicationController - include StatusControllerConcern include SignatureAuthentication include Authorization include AccountOwnedConcern - - layout 'public' + include WebAppControllerConcern before_action :require_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_status - before_action :set_instance_presenter before_action :set_link_headers before_action :redirect_to_original, only: :show before_action :set_referrer_policy_header, only: :show before_action :set_cache_headers - before_action :set_body_classes before_action :set_autoplay, only: :embed skip_around_action :set_locale, if: -> { request.format == :json } @@ -29,8 +25,6 @@ class StatusesController < ApplicationController respond_to do |format| format.html do expires_in 10.seconds, public: true if current_account.nil? - set_ancestors - set_descendants end format.json do @@ -56,10 +50,6 @@ class StatusesController < ApplicationController private - def set_body_classes - @body_classes = 'with-modals' - end - def set_link_headers response.headers['Link'] = LinkHeader.new([[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]]]) end @@ -71,16 +61,12 @@ class StatusesController < ApplicationController not_found end - def set_instance_presenter - @instance_presenter = InstancePresenter.new - end - def redirect_to_original redirect_to ActivityPub::TagManager.instance.url_for(@status.reblog) if @status.reblog? end def set_referrer_policy_header - response.headers['Referrer-Policy'] = 'origin' unless @status.distributable? + response.headers['Referrer-Policy'] = 'origin' end def set_autoplay diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 6616ba107..aa3969464 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -2,26 +2,23 @@ class TagsController < ApplicationController include SignatureVerification + include WebAppControllerConcern PAGE_SIZE = 20 PAGE_SIZE_MAX = 200 - layout 'public' - before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :authenticate_user!, if: :whitelist_mode? before_action :set_local before_action :set_tag before_action :set_statuses - before_action :set_body_classes - before_action :set_instance_presenter skip_before_action :require_functional!, unless: :whitelist_mode? def show respond_to do |format| format.html do - expires_in 0, public: true + expires_in 0, public: true if current_account.nil? end format.rss do @@ -55,14 +52,6 @@ class TagsController < ApplicationController end end - def set_body_classes - @body_classes = 'with-modals' - end - - def set_instance_presenter - @instance_presenter = InstancePresenter.new - end - def limit_param params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bf5742d34..31bcff568 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -158,19 +158,15 @@ module ApplicationHelper def render_initial_state state_params = { - settings: { - known_fediverse: Setting.show_known_fediverse_at_about_page, - }, - text: [params[:title], params[:text], params[:url]].compact.join(' '), } - permit_visibilities = %w(public unlisted private direct) - default_privacy = current_account&.user&.setting_default_privacy - permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present? - state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility] - if user_signed_in? + permit_visibilities = %w(public unlisted private direct) + default_privacy = current_account&.user&.setting_default_privacy + permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present? + + state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility] state_params[:settings] = state_params[:settings].merge(Web::Setting.find_by(user: current_user)&.data || {}) state_params[:push_subscription] = current_account.user.web_push_subscription(current_session) state_params[:current_account] = current_account diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index 58b636602..ce7bb6d5f 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -5,6 +5,10 @@ export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL'; +export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST'; +export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS'; +export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL'; + export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST'; export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS'; export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL'; @@ -87,6 +91,34 @@ export function fetchAccount(id) { }; }; +export const lookupAccount = acct => (dispatch, getState) => { + dispatch(lookupAccountRequest(acct)); + + api(getState).get('/api/v1/accounts/lookup', { params: { acct } }).then(response => { + dispatch(fetchRelationships([response.data.id])); + dispatch(importFetchedAccount(response.data)); + dispatch(lookupAccountSuccess()); + }).catch(error => { + dispatch(lookupAccountFail(acct, error)); + }); +}; + +export const lookupAccountRequest = (acct) => ({ + type: ACCOUNT_LOOKUP_REQUEST, + acct, +}); + +export const lookupAccountSuccess = () => ({ + type: ACCOUNT_LOOKUP_SUCCESS, +}); + +export const lookupAccountFail = (acct, error) => ({ + type: ACCOUNT_LOOKUP_FAIL, + acct, + error, + skipAlert: true, +}); + export function fetchAccountRequest(id) { return { type: ACCOUNT_FETCH_REQUEST, diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 891403969..2b2f1787e 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -72,7 +72,7 @@ const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1); export const ensureComposeIsVisible = (getState, routerHistory) => { if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { - routerHistory.push('/statuses/new'); + routerHistory.push('/publish'); } }; @@ -152,7 +152,7 @@ export function submitCompose(routerHistory) { 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), }, }).then(function (response) { - if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) { + if (routerHistory && routerHistory.location.pathname === '/publish' && window.history.state) { routerHistory.goBack(); } diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 0e40ee1d6..733192305 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -116,7 +116,7 @@ class Account extends ImmutablePureComponent { return (
- +
{mute_expires_at} diff --git a/app/javascript/mastodon/components/hashtag.js b/app/javascript/mastodon/components/hashtag.js index d766ca90d..c23a4674d 100644 --- a/app/javascript/mastodon/components/hashtag.js +++ b/app/javascript/mastodon/components/hashtag.js @@ -27,7 +27,7 @@ const Hashtag = ({ hashtag }) => (
#{hashtag.get('name')} diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 295e83f58..52672e02e 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -134,42 +134,28 @@ class Status extends ImmutablePureComponent { this.setState({ showMedia: !this.state.showMedia }); } - handleClick = () => { - if (this.props.onClick) { - this.props.onClick(); + handleClick = e => { + if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) { return; } - if (!this.context.router) { - return; - } - - 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'); + if (e) { 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 = () => { @@ -242,11 +228,30 @@ class Status extends ImmutablePureComponent { } handleHotkeyOpen = () => { - this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`); + if (this.props.onClick) { + this.props.onClick(); + return; + } + + const { router } = this.context; + const status = this._properStatus(); + + if (!router) { + return; + } + + router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); } handleHotkeyOpenProfile = () => { - this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`); + const { router } = this.context; + const status = this._properStatus(); + + if (!router) { + return; + } + + router.history.push(`/@${status.getIn(['account', 'acct'])}`); } handleHotkeyMoveUp = e => { @@ -465,14 +470,15 @@ class Status extends ImmutablePureComponent { {prepend}
-
+
+
- + - +
{statusAvatar}
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 9981f2449..85c76edee 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -186,7 +186,7 @@ class StatusActionBar extends ImmutablePureComponent { } handleOpen = () => { - this.context.router.history.push(`/statuses/${this.props.status.get('id')}`); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`); } handleEmbed = () => { diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 35bd50514..b3d794280 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -112,7 +112,7 @@ export default class StatusContent extends React.PureComponent { onMentionClick = (mention, e) => { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/accounts/${mention.get('id')}`); + this.context.router.history.push(`/@${mention.get('acct')}`); } } @@ -121,7 +121,7 @@ export default class StatusContent extends React.PureComponent { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/timelines/tag/${hashtag}`); + this.context.router.history.push(`/tags/${hashtag}`); } } @@ -202,7 +202,7 @@ export default class StatusContent extends React.PureComponent { let mentionsPlaceholder = ''; const mentionLinks = status.get('mentions').map(item => ( - + @{item.get('username')} )).reduce((aggregate, item) => [...aggregate, item, ' '], []); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 3ac58cf7c..3dcbe7923 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -14,7 +14,7 @@ import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import { previewState as previewMediaState } from 'mastodon/features/ui/components/media_modal'; import { previewState as previewVideoState } from 'mastodon/features/ui/components/video_modal'; -import initialState from '../initial_state'; +import initialState, { me } from '../initial_state'; import ErrorBoundary from '../components/error_boundary'; const { localeData, messages } = getLocale(); @@ -27,7 +27,7 @@ store.dispatch(hydrateAction); store.dispatch(fetchCustomEmojis()); const mapStateToProps = state => ({ - showIntroduction: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION, + showIntroduction: me && state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION, }); @connect(mapStateToProps) @@ -49,7 +49,7 @@ class MastodonMount extends React.PureComponent { } return ( - + diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 8e49486bd..cf21a8d13 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -330,21 +330,21 @@ class Header extends ImmutablePureComponent { {!suspended && (
- + - + - + ({ - isAccount: !!state.getIn(['accounts', props.params.accountId]), - attachments: getAccountGallery(state, props.params.accountId), - isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), - hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']), - suspended: state.getIn(['accounts', props.params.accountId, 'suspended'], false), - blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), -}); +const mapStateToProps = (state, { params: { acct } }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + isAccount: !!state.getIn(['accounts', accountId]), + attachments: getAccountGallery(state, accountId), + isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']), + hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']), + suspended: state.getIn(['accounts', accountId, 'suspended'], false), + blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false), + }; +}; class LoadMoreMedia extends ImmutablePureComponent { @@ -53,7 +64,10 @@ export default @connect(mapStateToProps) class AccountGallery extends ImmutablePureComponent { static propTypes = { - params: PropTypes.object.isRequired, + params: PropTypes.shape({ + acct: PropTypes.string.isRequired, + }).isRequired, + accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, attachments: ImmutablePropTypes.list.isRequired, isLoading: PropTypes.bool, @@ -68,15 +82,29 @@ class AccountGallery extends ImmutablePureComponent { width: 323, }; - componentDidMount () { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); + _load () { + const { accountId, dispatch } = this.props; + + dispatch(expandAccountMediaTimeline(accountId)); } - componentWillReceiveProps (nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); + componentDidMount () { + const { params: { acct }, accountId, dispatch } = this.props; + + 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)); } } @@ -96,7 +124,7 @@ class AccountGallery extends ImmutablePureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId })); + this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId })); }; handleLoadOlder = e => { @@ -166,7 +194,7 @@ class AccountGallery extends ImmutablePureComponent {
- + {(suspended || blockedBy) ? (
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 6b52defe4..17b693600 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -123,9 +123,9 @@ export default class Header extends ImmutablePureComponent { {!hideTabs && (
- - - + + +
)}
diff --git a/app/javascript/mastodon/features/account_timeline/components/moved_note.js b/app/javascript/mastodon/features/account_timeline/components/moved_note.js index 3e090bb5f..2e32d660f 100644 --- a/app/javascript/mastodon/features/account_timeline/components/moved_note.js +++ b/app/javascript/mastodon/features/account_timeline/components/moved_note.js @@ -21,7 +21,7 @@ export default class MovedNote extends ImmutablePureComponent { handleAccountClick = e => { if (e.button === 0) { e.preventDefault(); - this.context.router.history.push(`/accounts/${this.props.to.get('id')}`); + this.context.router.history.push(`/@${this.props.to.get('acct')}`); } e.stopPropagation(); diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index fa4239d6f..c5eeec083 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; -import { fetchAccount } from '../../actions/accounts'; +import { lookupAccount, fetchAccount } from '../../actions/accounts'; import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'; import StatusList from '../../components/status_list'; import LoadingIndicator from '../../components/loading_indicator'; @@ -20,10 +20,19 @@ import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines' const emptyList = ImmutableList(); -const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { +const mapStateToProps = (state, { params: { acct }, withReplies = false }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + const path = withReplies ? `${accountId}:with_replies` : accountId; return { + accountId, remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])), remoteUrl: state.getIn(['accounts', accountId, 'url']), isAccount: !!state.getIn(['accounts', accountId]), @@ -48,7 +57,10 @@ export default @connect(mapStateToProps) class AccountTimeline extends ImmutablePureComponent { static propTypes = { - params: PropTypes.object.isRequired, + params: PropTypes.shape({ + acct: PropTypes.string.isRequired, + }).isRequired, + accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, statusIds: ImmutablePropTypes.list, @@ -64,8 +76,8 @@ class AccountTimeline extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { - const { params: { accountId }, withReplies, dispatch } = this.props; + _load () { + const { accountId, withReplies, dispatch } = this.props; dispatch(fetchAccount(accountId)); dispatch(fetchAccountIdentityProofs(accountId)); @@ -81,29 +93,32 @@ class AccountTimeline extends ImmutablePureComponent { } } - componentWillReceiveProps (nextProps) { - const { dispatch } = this.props; + componentDidMount () { + const { params: { acct }, accountId, dispatch } = this.props; - if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { - dispatch(fetchAccount(nextProps.params.accountId)); - dispatch(fetchAccountIdentityProofs(nextProps.params.accountId)); + if (accountId) { + this._load(); + } else { + dispatch(lookupAccount(acct)); + } + } - if (!nextProps.withReplies) { - dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId)); - } + componentDidUpdate (prevProps) { + const { params: { acct }, accountId, dispatch } = this.props; - dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies })); + if (prevProps.accountId !== accountId && accountId) { + this._load(); + } else if (prevProps.params.acct !== acct) { + dispatch(lookupAccount(acct)); } - if (nextProps.params.accountId === me && this.props.params.accountId !== me) { - dispatch(connectTimeline(`account:${me}`)); - } else if (this.props.params.accountId === me && nextProps.params.accountId !== me) { + if (prevProps.accountId === me && accountId !== me) { dispatch(disconnectTimeline(`account:${me}`)); } } componentWillUnmount () { - const { dispatch, params: { accountId } } = this.props; + const { dispatch, accountId } = this.props; if (accountId === me) { dispatch(disconnectTimeline(`account:${me}`)); @@ -111,7 +126,7 @@ class AccountTimeline extends ImmutablePureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies })); + this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies })); } render () { @@ -153,7 +168,7 @@ class AccountTimeline extends ImmutablePureComponent { } + prepend={} alwaysPrepend append={remoteMessage} scrollKey='account_timeline' diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js index 840d0a3da..e6ba7d8b7 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.js +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js @@ -19,13 +19,13 @@ export default class NavigationBar extends ImmutablePureComponent { render () { return (
- + {this.props.account.get('acct')}
- + @{this.props.account.get('acct')} diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js index a1d5c420c..863defb76 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.js +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js @@ -32,7 +32,7 @@ class ReplyIndicator extends ImmutablePureComponent { handleAccountClick = (e) => { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } } diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index e2de8b0e6..2ca14a2dd 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -99,16 +99,16 @@ class Compose extends React.PureComponent {