From dd512c140bee24446d1ddab901d04b51a72a2d86 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Mon, 14 Dec 2020 01:41:13 +0100 Subject: [PATCH] WIP --- app/controllers/account_follow_controller.rb | 12 --- .../account_unfollow_controller.rb | 12 --- app/controllers/accounts_controller.rb | 24 +----- .../api/v1/accounts/lookup_controller.rb | 16 ++++ .../authorize_interactions_controller.rb | 2 +- .../concerns/account_controller_concern.rb | 7 -- .../concerns/web_app_controller_concern.rb | 18 +++++ app/controllers/directories_controller.rb | 28 +------ .../follower_accounts_controller.rb | 9 ++- .../following_accounts_controller.rb | 9 ++- app/controllers/home_controller.rb | 43 +---------- .../public_timelines_controller.rb | 18 ++--- app/controllers/statuses_controller.rb | 18 +---- app/controllers/tags_controller.rb | 15 +--- app/helpers/application_helper.rb | 14 ++-- app/javascript/mastodon/actions/accounts.js | 32 ++++++++ app/javascript/mastodon/actions/compose.js | 4 +- app/javascript/mastodon/components/account.js | 2 +- app/javascript/mastodon/components/hashtag.js | 2 +- app/javascript/mastodon/components/status.js | 64 ++++++++-------- .../mastodon/components/status_action_bar.js | 2 +- .../mastodon/components/status_content.js | 6 +- .../mastodon/containers/mastodon.js | 6 +- .../features/account/components/header.js | 6 +- .../features/account_gallery/index.js | 64 +++++++++++----- .../account_timeline/components/header.js | 6 +- .../account_timeline/components/moved_note.js | 2 +- .../features/account_timeline/index.js | 55 +++++++++----- .../compose/components/navigation_bar.js | 4 +- .../compose/components/reply_indicator.js | 2 +- .../mastodon/features/compose/index.js | 6 +- .../components/conversation.js | 2 +- .../directory/components/account_card.js | 2 +- .../components/account_authorize.js | 2 +- .../mastodon/features/followers/index.js | 66 ++++++++++++----- .../mastodon/features/following/index.js | 66 ++++++++++++----- .../components/announcements.js | 6 +- .../features/getting_started/index.js | 14 ++-- .../mastodon/features/home_timeline/index.js | 2 +- .../mastodon/features/lists/index.js | 2 +- .../components/follow_request.js | 2 +- .../notifications/components/notification.js | 6 +- .../picture_in_picture/components/footer.js | 2 +- .../picture_in_picture/components/header.js | 2 +- .../status/components/detailed_status.js | 6 +- .../mastodon/features/status/index.js | 2 +- .../features/ui/components/boost_modal.js | 2 +- .../features/ui/components/columns_area.js | 2 +- .../features/ui/components/compose_panel.js | 7 +- .../features/ui/components/list_panel.js | 2 +- .../features/ui/components/media_modal.js | 7 -- .../ui/components/navigation_panel.js | 10 +-- .../features/ui/components/tabs_bar.js | 6 +- app/javascript/mastodon/features/ui/index.js | 50 ++++++------- app/javascript/mastodon/main.js | 6 +- .../mastodon/reducers/accounts_map.js | 15 ++++ app/javascript/mastodon/reducers/index.js | 2 + .../service_worker/web_push_notifications.js | 2 +- app/views/accounts/show.html.haml | 74 ++----------------- .../_post_follow_actions.html.haml | 2 +- app/views/directories/index.html.haml | 49 ++---------- app/views/follower_accounts/index.html.haml | 19 ++--- app/views/following_accounts/index.html.haml | 19 ++--- .../notification_mailer/_status.html.haml | 2 +- .../notification_mailer/_status.text.erb | 2 +- .../notification_mailer/digest.html.haml | 2 +- app/views/notification_mailer/digest.text.erb | 2 +- .../notification_mailer/favourite.html.haml | 2 +- .../notification_mailer/follow.html.haml | 2 +- app/views/notification_mailer/follow.text.erb | 2 +- .../follow_request.html.haml | 2 +- .../follow_request.text.erb | 2 +- .../notification_mailer/mention.html.haml | 2 +- .../notification_mailer/reblog.html.haml | 2 +- app/views/public_timelines/show.html.haml | 18 ++--- app/views/statuses/_detailed_status.html.haml | 2 +- app/views/statuses/show.html.haml | 16 ++-- app/views/tags/show.html.haml | 15 ++-- app/views/user_mailer/welcome.html.haml | 2 +- app/views/user_mailer/welcome.text.erb | 2 +- config/routes.rb | 30 +++++++- 81 files changed, 517 insertions(+), 554 deletions(-) delete mode 100644 app/controllers/account_follow_controller.rb delete mode 100644 app/controllers/account_unfollow_controller.rb create mode 100644 app/controllers/api/v1/accounts/lookup_controller.rb create mode 100644 app/controllers/concerns/web_app_controller_concern.rb create mode 100644 app/javascript/mastodon/reducers/accounts_map.js diff --git a/app/controllers/account_follow_controller.rb b/app/controllers/account_follow_controller.rb deleted file mode 100644 index 33394074db4..00000000000 --- 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 378ec86dc62..00000000000 --- 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 b902ada090a..4ca37b9efa7 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 00000000000..aee6be18a9e --- /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 29c0288d09d..1d519c96f42 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 11eac0eb6bf..f05fa3f91fb 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 00000000000..8a6c73af3e4 --- /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 f198ad5ba5b..cfd0fa65686 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 ff4df2adfca..ebdefcf9b5b 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 6bb95c45498..4500fb5d7e2 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 702889cd030..72573d231da 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 - - private - - def redirect_unauthenticated_to_permalinks! - return if user_signed_in? + include WebAppControllerConcern - matches = request.path.match(/\A\/web\/(statuses|accounts)\/([\d]+)\z/) + def index; end - 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 + private 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 1332ba16c2b..1fcb9fbca9e 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 87612a29662..a55b40103d6 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 6616ba107c8..aa3969464cf 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 bf5742d34f2..31bcff56827 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 58b63660260..ce7bb6d5f09 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 891403969e2..2b2f1787e26 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 0e40ee1d6a5..733192305e4 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -116,7 +116,7 @@ class Account extends ImmutablePureComponent { return ( <div className='account'> <div className='account__wrapper'> - <Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}> + <Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}> <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div> {mute_expires_at} <DisplayName account={account} /> diff --git a/app/javascript/mastodon/components/hashtag.js b/app/javascript/mastodon/components/hashtag.js index d766ca90d78..c23a4674d77 100644 --- a/app/javascript/mastodon/components/hashtag.js +++ b/app/javascript/mastodon/components/hashtag.js @@ -27,7 +27,7 @@ const Hashtag = ({ hashtag }) => ( <div className='trends__item__name'> <Permalink href={hashtag.get('url')} - to={`/timelines/tag/${hashtag.get('name')}`} + to={`/tags/${hashtag.get('name')}`} > #<span>{hashtag.get('name')}</span> </Permalink> diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 295e83f5819..52672e02e74 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; + if (e) { + e.preventDefault(); } - const { status } = this.props; - this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); + this.handleHotkeyOpen(); } - handleExpandClick = (e) => { - if (this.props.onClick) { - this.props.onClick(); + handleAccountClick = e => { + if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) { return; } - if (e.button === 0) { - if (!this.context.router) { - return; - } - - const { status } = this.props; - this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); - } - } - - handleAccountClick = (e) => { - if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - const id = e.currentTarget.getAttribute('data-id'); + if (e) { e.preventDefault(); - this.context.router.history.push(`/accounts/${id}`); } + + this.handleHotkeyOpenProfile(); } handleExpandedToggle = () => { @@ -242,11 +228,30 @@ class Status extends ImmutablePureComponent { } handleHotkeyOpen = () => { - this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`); + if (this.props.onClick) { + this.props.onClick(); + return; + } + + const { router } = this.context; + const status = this._properStatus(); + + if (!router) { + return; + } + + router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); } handleHotkeyOpenProfile = () => { - this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`); + const { router } = this.context; + const status = this._properStatus(); + + if (!router) { + return; + } + + router.history.push(`/@${status.getIn(['account', 'acct'])}`); } handleHotkeyMoveUp = e => { @@ -465,14 +470,15 @@ class Status extends ImmutablePureComponent { {prepend} <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}> - <div className='status__expand' onClick={this.handleExpandClick} role='presentation' /> + <div className='status__expand' onClick={this.handleClick} role='presentation' /> + <div className='status__info'> - <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'> + <a onClick={this.handleClick} href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'> <span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span> <RelativeTimestamp timestamp={status.get('created_at')} /> </a> - <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'> + <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'> <div className='status__avatar'> {statusAvatar} </div> diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 9981f2449bf..85c76edee4b 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 35bd505142e..b3d7942801e 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 => ( - <Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'> + <Permalink to={`/@${item.get('acct')}`} href={item.get('url')} key={item.get('id')} className='mention'> @<span>{item.get('username')}</span> </Permalink> )).reduce((aggregate, item) => [...aggregate, item, ' '], []); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 3ac58cf7c5e..3dcbe792381 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 ( - <BrowserRouter basename='/web'> + <BrowserRouter> <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}> <Route path='/' component={UI} /> </ScrollContext> diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 8e49486bdaa..cf21a8d13fe 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 && ( <div className='account__header__extra__links'> - <NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}> + <NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}> <ShortNumber value={account.get('statuses_count')} renderer={counterRenderer('statuses')} /> </NavLink> - <NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}> + <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}> <ShortNumber value={account.get('following_count')} renderer={counterRenderer('following')} /> </NavLink> - <NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}> + <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}> <ShortNumber value={account.get('followers_count')} renderer={counterRenderer('followers')} diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 015a6a6d706..47764109899 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; -import { fetchAccount } from 'mastodon/actions/accounts'; +import { lookupAccount } from 'mastodon/actions/accounts'; import { expandAccountMediaTimeline } from '../../actions/timelines'; import LoadingIndicator from 'mastodon/components/loading_indicator'; import Column from '../ui/components/column'; @@ -17,14 +17,25 @@ import MissingIndicator from 'mastodon/components/missing_indicator'; import { openModal } from 'mastodon/actions/modal'; import { FormattedMessage } from 'react-intl'; -const mapStateToProps = (state, props) => ({ - isAccount: !!state.getIn(['accounts', props.params.accountId]), - attachments: getAccountGallery(state, props.params.accountId), - isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), - hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']), - suspended: state.getIn(['accounts', props.params.accountId, 'suspended'], false), - blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), -}); +const mapStateToProps = (state, { params: { acct } }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + isAccount: !!state.getIn(['accounts', accountId]), + attachments: getAccountGallery(state, accountId), + isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']), + hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']), + suspended: state.getIn(['accounts', accountId, 'suspended'], false), + blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false), + }; +}; class LoadMoreMedia extends ImmutablePureComponent { @@ -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, }; + _load () { + const { accountId, dispatch } = this.props; + + dispatch(expandAccountMediaTimeline(accountId)); + } + componentDidMount () { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); + const { params: { acct }, accountId, dispatch } = this.props; + + if (accountId) { + this._load(); + } else { + dispatch(lookupAccount(acct)); + } } - componentWillReceiveProps (nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId)); + componentDidUpdate (prevProps) { + const { params: { acct }, accountId, dispatch } = this.props; + + if (prevProps.accountId !== accountId && accountId) { + this._load(); + } else if (prevProps.params.acct !== acct) { + dispatch(lookupAccount(acct)); } } @@ -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 { <ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable scrollable--flex' onScroll={this.handleScroll}> - <HeaderContainer accountId={this.props.params.accountId} /> + <HeaderContainer accountId={this.props.accountId} /> {(suspended || blockedBy) ? ( <div className='empty-column-indicator'> diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 6b52defe4a0..17b693600e5 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 && ( <div className='account__section-headline'> - <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink> - <NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots and replies' /></NavLink> - <NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink> + <NavLink exact to={`/@${account.get('acct')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink> + <NavLink exact to={`/@${account.get('acct')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots and replies' /></NavLink> + <NavLink exact to={`/@${account.get('acct')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink> </div> )} </div> 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 3e090bb5f2b..2e32d660f84 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 fa4239d6f5f..c5eeec0837a 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 { <ColumnBackButton multiColumn={multiColumn} /> <StatusList - prepend={<HeaderContainer accountId={this.props.params.accountId} />} + prepend={<HeaderContainer accountId={this.props.accountId} />} 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 840d0a3da3a..e6ba7d8b73d 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 ( <div className='navigation-bar'> - <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}> + <Permalink href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}> <span style={{ display: 'none' }}>{this.props.account.get('acct')}</span> <Avatar account={this.props.account} size={48} /> </Permalink> <div className='navigation-bar__profile'> - <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}> + <Permalink href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}> <strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong> </Permalink> diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js index a1d5c420cb6..863defb768f 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 e2de8b0e6a2..2ca14a2dd2f 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 { <nav className='drawer__header'> <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link> {!columns.some(column => column.get('id') === 'HOME') && ( - <Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link> + <Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link> )} {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && ( <Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link> )} {!columns.some(column => column.get('id') === 'COMMUNITY') && ( - <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link> + <Link to='/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link> )} {!columns.some(column => column.get('id') === 'PUBLIC') && ( - <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link> + <Link to='/federated' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link> )} <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a> <a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a> diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js index 43e1d77b946..c4f7098c5db 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js @@ -133,7 +133,7 @@ class Conversation extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete }); - const names = accounts.map(a => <Permalink to={`/accounts/${a.get('id')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]); + const names = accounts.map(a => <Permalink to={`/@${a.get('acct')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]); const handlers = { reply: this.handleReply, diff --git a/app/javascript/mastodon/features/directory/components/account_card.js b/app/javascript/mastodon/features/directory/components/account_card.js index 8f0e8db4b41..03e13f28e44 100644 --- a/app/javascript/mastodon/features/directory/components/account_card.js +++ b/app/javascript/mastodon/features/directory/components/account_card.js @@ -213,7 +213,7 @@ class AccountCard extends ImmutablePureComponent { <Permalink className='directory__card__bar__name' href={account.get('url')} - to={`/accounts/${account.get('id')}`} + to={`/@${account.get('acct')}`} > <Avatar account={account} size={48} /> <DisplayName account={account} /> diff --git a/app/javascript/mastodon/features/follow_requests/components/account_authorize.js b/app/javascript/mastodon/features/follow_requests/components/account_authorize.js index 8269f5ae43f..263a7ae1626 100644 --- a/app/javascript/mastodon/features/follow_requests/components/account_authorize.js +++ b/app/javascript/mastodon/features/follow_requests/components/account_authorize.js @@ -30,7 +30,7 @@ class AccountAuthorize extends ImmutablePureComponent { return ( <div className='account-authorize__wrapper'> <div className='account-authorize'> - <Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'> + <Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='detailed-status__display-name'> <div className='account-authorize__avatar'><Avatar account={account} size={48} /></div> <DisplayName account={account} /> </Permalink> diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js index ae00d13d3b2..533e838419e 100644 --- a/app/javascript/mastodon/features/followers/index.js +++ b/app/javascript/mastodon/features/followers/index.js @@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { debounce } from 'lodash'; import LoadingIndicator from '../../components/loading_indicator'; import { - fetchAccount, + lookupAccount, fetchFollowers, expandFollowers, } from '../../actions/accounts'; @@ -19,15 +19,26 @@ import ScrollableList from '../../components/scrollable_list'; import MissingIndicator from 'mastodon/components/missing_indicator'; import TimelineHint from 'mastodon/components/timeline_hint'; -const mapStateToProps = (state, props) => ({ - remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])), - remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']), - isAccount: !!state.getIn(['accounts', props.params.accountId]), - accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']), - hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']), - isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true), - blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), -}); +const mapStateToProps = (state, { params: { acct } }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])), + remoteUrl: state.getIn(['accounts', accountId, 'url']), + isAccount: !!state.getIn(['accounts', accountId]), + accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']), + hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']), + isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true), + blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false), + }; +}; const RemoteHint = ({ url }) => ( <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.followers' defaultMessage='Followers' />} /> @@ -41,7 +52,10 @@ export default @connect(mapStateToProps) class Followers extends ImmutablePureComponent { static propTypes = { - params: PropTypes.object.isRequired, + params: PropTypes.shape({ + acct: PropTypes.string.isRequired, + }).isRequired, + accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, @@ -54,22 +68,34 @@ class Followers extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { - if (!this.props.accountIds) { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(fetchFollowers(this.props.params.accountId)); + _load () { + const { accountId, dispatch } = this.props; + + dispatch(fetchFollowers(accountId)); + } + + componentDidMount () { + const { params: { acct }, accountId, dispatch } = this.props; + + if (accountId) { + this._load(); + } else { + dispatch(lookupAccount(acct)); } } - componentWillReceiveProps (nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(fetchFollowers(nextProps.params.accountId)); + componentDidUpdate (prevProps) { + const { params: { acct }, accountId, dispatch } = this.props; + + if (prevProps.accountId !== accountId && accountId) { + this._load(); + } else if (prevProps.params.acct !== acct) { + dispatch(lookupAccount(acct)); } } handleLoadMore = debounce(() => { - this.props.dispatch(expandFollowers(this.props.params.accountId)); + this.props.dispatch(expandFollowers(this.props.accountId)); }, 300, { leading: true }); render () { diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js index 666ec7a7f66..c302706d512 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.js @@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { debounce } from 'lodash'; import LoadingIndicator from '../../components/loading_indicator'; import { - fetchAccount, + lookupAccount, fetchFollowing, expandFollowing, } from '../../actions/accounts'; @@ -19,15 +19,26 @@ import ScrollableList from '../../components/scrollable_list'; import MissingIndicator from 'mastodon/components/missing_indicator'; import TimelineHint from 'mastodon/components/timeline_hint'; -const mapStateToProps = (state, props) => ({ - remote: !!(state.getIn(['accounts', props.params.accountId, 'acct']) !== state.getIn(['accounts', props.params.accountId, 'username'])), - remoteUrl: state.getIn(['accounts', props.params.accountId, 'url']), - isAccount: !!state.getIn(['accounts', props.params.accountId]), - accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']), - hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']), - isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true), - blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), -}); +const mapStateToProps = (state, { params: { acct } }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])), + remoteUrl: state.getIn(['accounts', accountId, 'url']), + isAccount: !!state.getIn(['accounts', accountId]), + accountIds: state.getIn(['user_lists', 'following', accountId, 'items']), + hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']), + isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true), + blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false), + }; +}; const RemoteHint = ({ url }) => ( <TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.follows' defaultMessage='Follows' />} /> @@ -41,7 +52,10 @@ export default @connect(mapStateToProps) class Following extends ImmutablePureComponent { static propTypes = { - params: PropTypes.object.isRequired, + params: PropTypes.shape({ + acct: PropTypes.string.isRequired, + }).isRequired, + accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, @@ -54,22 +68,34 @@ class Following extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { - if (!this.props.accountIds) { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(fetchFollowing(this.props.params.accountId)); + _load () { + const { accountId, dispatch } = this.props; + + dispatch(fetchFollowing(accountId)); + } + + componentDidMount () { + const { params: { acct }, accountId, dispatch } = this.props; + + if (accountId) { + this._load(); + } else { + dispatch(lookupAccount(acct)); } } - componentWillReceiveProps (nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { - this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(fetchFollowing(nextProps.params.accountId)); + componentDidUpdate (prevProps) { + const { params: { acct }, accountId, dispatch } = this.props; + + if (prevProps.accountId !== accountId && accountId) { + this._load(); + } else if (prevProps.params.acct !== acct) { + dispatch(lookupAccount(acct)); } } handleLoadMore = debounce(() => { - this.props.dispatch(expandFollowing(this.props.params.accountId)); + this.props.dispatch(expandFollowing(this.props.accountId)); }, 300, { leading: true }); render () { diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.js b/app/javascript/mastodon/features/getting_started/components/announcements.js index ff1566e05ca..24db8cedec6 100644 --- a/app/javascript/mastodon/features/getting_started/components/announcements.js +++ b/app/javascript/mastodon/features/getting_started/components/announcements.js @@ -87,7 +87,7 @@ class Content extends ImmutablePureComponent { onMentionClick = (mention, e) => { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/accounts/${mention.get('id')}`); + this.context.router.history.push(`/@${mention.get('acct')}`); } } @@ -96,14 +96,14 @@ class Content extends ImmutablePureComponent { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/timelines/tag/${hashtag}`); + this.context.router.history.push(`/tags/${hashtag}`); } } onStatusClick = (status, e) => { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/statuses/${status.get('id')}`); + this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); } } diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 1b999461290..88762d60764 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -82,7 +82,7 @@ class GettingStarted extends ImmutablePureComponent { const { fetchFollowRequests, multiColumn } = this.props; if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { - this.context.router.history.replace('/timelines/home'); + this.context.router.history.replace('/home'); return; } @@ -98,15 +98,15 @@ class GettingStarted extends ImmutablePureComponent { if (multiColumn) { navItems.push( <ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />, - <ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />, - <ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />, + <ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/public/local' />, + <ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />, ); height += 34 + 48*2; if (profile_directory) { navItems.push( - <ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />, + <ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/explore' />, ); height += 48; @@ -119,7 +119,7 @@ class GettingStarted extends ImmutablePureComponent { height += 34; } else if (profile_directory) { navItems.push( - <ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />, + <ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/explore' />, ); height += 48; @@ -127,13 +127,13 @@ class GettingStarted extends ImmutablePureComponent { if (multiColumn && !columns.find(item => item.get('id') === 'HOME')) { navItems.push( - <ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/timelines/home' />, + <ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />, ); height += 48; } navItems.push( - <ColumnLink key='direct' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />, + <ColumnLink key='direct' icon='envelope' text={intl.formatMessage(messages.direct)} to='/conversations' />, <ColumnLink key='bookmark' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />, <ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />, <ColumnLink key='lists' icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />, diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index 577ff33bb03..0d7914f1f68 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -153,7 +153,7 @@ class HomeTimeline extends React.PureComponent { scrollKey={`home_timeline-${columnId}`} onLoadMore={this.handleLoadMore} timelineId='home' - emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} + emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} shouldUpdateScroll={shouldUpdateScroll} bindToDocument={!multiColumn} /> diff --git a/app/javascript/mastodon/features/lists/index.js b/app/javascript/mastodon/features/lists/index.js index ca1fa1f5ef0..b52116fc283 100644 --- a/app/javascript/mastodon/features/lists/index.js +++ b/app/javascript/mastodon/features/lists/index.js @@ -74,7 +74,7 @@ class Lists extends ImmutablePureComponent { bindToDocument={!multiColumn} > {lists.map(list => - <ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />, + <ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />, )} </ScrollableList> </Column> diff --git a/app/javascript/mastodon/features/notifications/components/follow_request.js b/app/javascript/mastodon/features/notifications/components/follow_request.js index a80cfb2fa1f..9ef3fde7eb0 100644 --- a/app/javascript/mastodon/features/notifications/components/follow_request.js +++ b/app/javascript/mastodon/features/notifications/components/follow_request.js @@ -42,7 +42,7 @@ class FollowRequest extends ImmutablePureComponent { return ( <div className='account'> <div className='account__wrapper'> - <Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}> + <Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}> <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div> <DisplayName account={account} /> </Permalink> diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 94fdbd6f453..f9f8a87f2db 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -68,7 +68,7 @@ class Notification extends ImmutablePureComponent { const { notification } = this.props; if (notification.get('status')) { - this.context.router.history.push(`/statuses/${notification.get('status')}`); + this.context.router.history.push(`/@${notification.getIn(['status', 'account', 'acct'])}/${notification.get('status')}`); } else { this.handleOpenProfile(); } @@ -76,7 +76,7 @@ class Notification extends ImmutablePureComponent { handleOpenProfile = () => { const { notification } = this.props; - this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`); + this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); } handleMention = e => { @@ -315,7 +315,7 @@ class Notification extends ImmutablePureComponent { const { notification } = this.props; const account = notification.get('account'); const displayNameHtml = { __html: account.get('display_name_html') }; - const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>; + const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>; switch(notification.get('type')) { case 'follow': diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.js b/app/javascript/mastodon/features/picture_in_picture/components/footer.js index 1ecb18bf820..9f237da461d 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.js +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.js @@ -116,7 +116,7 @@ class Footer extends ImmutablePureComponent { const { status } = this.props; - router.history.push(`/statuses/${status.get('id')}`); + router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); } render () { diff --git a/app/javascript/mastodon/features/picture_in_picture/components/header.js b/app/javascript/mastodon/features/picture_in_picture/components/header.js index 7dd199b7525..e05d8c62e90 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/header.js +++ b/app/javascript/mastodon/features/picture_in_picture/components/header.js @@ -34,7 +34,7 @@ class Header extends ImmutablePureComponent { return ( <div className='picture-in-picture__header'> - <Link to={`/statuses/${statusId}`} className='picture-in-picture__header__account'> + <Link to={`/@${account.get('acct')}/${statusId}`} className='picture-in-picture__header__account'> <Avatar account={account} size={36} /> <DisplayName account={account} /> </Link> diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 043a749ede2..72ddeb2b24d 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -55,7 +55,7 @@ class DetailedStatus extends ImmutablePureComponent { handleAccountClick = (e) => { if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) { e.preventDefault(); - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } e.stopPropagation(); @@ -195,7 +195,7 @@ class DetailedStatus extends ImmutablePureComponent { reblogLink = ( <React.Fragment> <React.Fragment> · </React.Fragment> - <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'> + <Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link'> <Icon id={reblogIcon} /> <span className='detailed-status__reblogs'> <AnimatedNumber value={status.get('reblogs_count')} /> @@ -219,7 +219,7 @@ class DetailedStatus extends ImmutablePureComponent { if (this.context.router) { favouriteLink = ( - <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'> + <Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link'> <Icon id='star' /> <span className='detailed-status__favorites'> <AnimatedNumber value={status.get('favourites_count')} /> diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index df8362a1bc7..d1f63d4251c 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -396,7 +396,7 @@ class Status extends ImmutablePureComponent { } handleHotkeyOpenProfile = () => { - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } handleHotkeyToggleHidden = () => { diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js index 83229833b15..f8a344690e3 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.js +++ b/app/javascript/mastodon/features/ui/components/boost_modal.js @@ -68,7 +68,7 @@ class BoostModal extends ImmutablePureComponent { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.props.onClose(); - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } } diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 6837450eb58..46b0fda8ad3 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -191,7 +191,7 @@ class ColumnsArea extends ImmutablePureComponent { const columnIndex = getIndex(this.context.router.history.location.pathname); if (singleColumn) { - const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>; + const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>; 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}> diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.js index 3d0c48c7a96..82a21d515ae 100644 --- a/app/javascript/mastodon/features/ui/components/compose_panel.js +++ b/app/javascript/mastodon/features/ui/components/compose_panel.js @@ -6,6 +6,7 @@ import ComposeFormContainer from 'mastodon/features/compose/containers/compose_f import NavigationContainer from 'mastodon/features/compose/containers/navigation_container'; import LinkFooter from './link_footer'; import { changeComposing } from 'mastodon/actions/compose'; +import { me } from 'mastodon/initial_state'; export default @connect() class ComposePanel extends React.PureComponent { @@ -26,8 +27,10 @@ class ComposePanel extends React.PureComponent { return ( <div className='compose-panel' onFocus={this.onFocus}> <SearchContainer openInRoute /> - <NavigationContainer onClose={this.onBlur} /> - <ComposeFormContainer singleColumn /> + + {me && <NavigationContainer onClose={this.onBlur} />} + {me && <ComposeFormContainer singleColumn />} + <LinkFooter withHotkeys /> </div> ); diff --git a/app/javascript/mastodon/features/ui/components/list_panel.js b/app/javascript/mastodon/features/ui/components/list_panel.js index 1f7ec683a7f..411f62508ed 100644 --- a/app/javascript/mastodon/features/ui/components/list_panel.js +++ b/app/javascript/mastodon/features/ui/components/list_panel.js @@ -46,7 +46,7 @@ class ListPanel extends ImmutablePureComponent { <hr /> {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> ); diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index 08da1033042..dc21946645a 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -152,13 +152,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 () { const { media, statusId, intl, onClose } = this.props; const { navigationHidden } = this.state; diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js index 0c12852f5b1..26b2cfc8780 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -10,16 +10,16 @@ import TrendsContainer from 'mastodon/features/getting_started/containers/trends const NavigationPanel = () => ( <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> <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' 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' 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='/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='/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='/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='/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> - {profile_directory && <NavLink className='column-link column-link--transparent' to='/directory'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></NavLink>} + {profile_directory && <NavLink className='column-link column-link--transparent' to='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></NavLink>} <ListPanel /> diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index 1911da8ba3f..a023bcf3412 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -8,10 +8,10 @@ import Icon from 'mastodon/components/icon'; import NotificationsCounterIcon from './notifications_counter_icon'; 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='/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' 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' 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='/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' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>, ]; diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 507ac1df146..c0b7bafce66 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -147,7 +147,7 @@ class SwitchingColumnsArea extends React.PureComponent { render () { 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 ( <ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}> @@ -155,31 +155,29 @@ class SwitchingColumnsArea extends React.PureComponent { {redirect} <WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> - <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - + <WrappedRoute path='/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/conversations' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/lists/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} /> <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/search' component={Search} content={children} /> - <WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - - <WrappedRoute path='/statuses/new' component={Compose} content={children} /> - <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - - <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} /> - <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> - <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/explore' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/publish' component={Compose} content={children} /> + + <WrappedRoute path='/@:acct' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/@:acct/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} /> + <WrappedRoute path='/@:acct/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/@:acct/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/@:acct/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/@:acct/tagged/:tag' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> @@ -447,7 +445,7 @@ class UI extends React.PureComponent { } handleHotkeyGoToHome = () => { - this.context.router.history.push('/timelines/home'); + this.context.router.history.push('/home'); } handleHotkeyGoToNotifications = () => { @@ -455,15 +453,15 @@ class UI extends React.PureComponent { } handleHotkeyGoToLocal = () => { - this.context.router.history.push('/timelines/public/local'); + this.context.router.history.push('/public/local'); } handleHotkeyGoToFederated = () => { - this.context.router.history.push('/timelines/public'); + this.context.router.history.push('/public'); } handleHotkeyGoToDirect = () => { - this.context.router.history.push('/timelines/direct'); + this.context.router.history.push('/conversations'); } handleHotkeyGoToStart = () => { @@ -479,7 +477,7 @@ class UI extends React.PureComponent { } handleHotkeyGoToProfile = () => { - this.context.router.history.push(`/accounts/${me}`); + this.context.router.history.push(`/accounts/${me}`); // FIXME } handleHotkeyGoToBlocked = () => { diff --git a/app/javascript/mastodon/main.js b/app/javascript/mastodon/main.js index bda51f692b7..91d36405bc1 100644 --- a/app/javascript/mastodon/main.js +++ b/app/javascript/mastodon/main.js @@ -13,9 +13,9 @@ function main() { if (window.history && history.replaceState) { const { pathname, search, hash } = window.location; const path = pathname + search + hash; - if (!(/^\/web($|\/)/).test(path)) { - history.replaceState(null, document.title, `/web${path}`); - } + //if (!(/^\/web($|\/)/).test(path)) { + // history.replaceState(null, document.title, `/web${path}`); + //} } ready(() => { diff --git a/app/javascript/mastodon/reducers/accounts_map.js b/app/javascript/mastodon/reducers/accounts_map.js new file mode 100644 index 00000000000..e0d42e9cd44 --- /dev/null +++ b/app/javascript/mastodon/reducers/accounts_map.js @@ -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; + } +}; diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index 3b3c5ae2995..e518c8228ab 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -38,6 +38,7 @@ import missed_updates from './missed_updates'; import announcements from './announcements'; import markers from './markers'; import picture_in_picture from './picture_in_picture'; +import accounts_map from './accounts_map'; const reducers = { announcements, @@ -52,6 +53,7 @@ const reducers = { status_lists, accounts, accounts_counters, + accounts_map, statuses, relationships, settings, diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js index 958e5fc12c8..2da78006b8e 100644 --- a/app/javascript/mastodon/service_worker/web_push_notifications.js +++ b/app/javascript/mastodon/service_worker/web_push_notifications.js @@ -90,7 +90,7 @@ const handlePush = (event) => { options.tag = notification.id; options.badge = '/badge.png'; 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 ? `/@${notification.account.acct}/${notification.status.id}` : `/@${notification.account.acct}` }; if (notification.status && notification.status.spoiler_text || notification.status.sensitive) { options.data.hiddenBody = htmlToPlainText(notification.status.content); diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 1a81b96f6c9..a64cdfa2c16 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -16,71 +16,13 @@ = opengraph 'og:type', 'profile' = render 'og', account: @account, url: short_account_url(@account, only_path: false) + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} + = render_initial_state + = javascript_pack_tag 'application', crossorigin: 'anonymous' -= render 'header', account: @account, with_bio: true +.app-holder#mastodon{ data: { props: Oj.dump(default_props) } } + %noscript + = image_pack_tag 'logo.svg', alt: 'Mastodon' -.grid - .column-0 - .h-feed - %data.p-name{ value: "#{@account.username} on #{site_hostname}" }/ - - .account__section-headline - = active_link_to t('accounts.posts_tab_heading'), short_account_url(@account) - = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account) - = active_link_to t('accounts.media'), short_account_media_url(@account) - - - if user_signed_in? && @account.blocking?(current_account) - .nothing-here.nothing-here--under-tabs= t('accounts.unavailable') - - elsif @statuses.empty? - = nothing_here 'nothing-here--under-tabs' - - else - .activity-stream.activity-stream--under-tabs - - if params[:page].to_i.zero? - = render partial: 'statuses/status', collection: @pinned_statuses, as: :status, locals: { pinned: true } - - - if @newer_url - .entry= link_to_newer @newer_url - - = render partial: 'statuses/status', collection: @statuses, as: :status - - - if @older_url - .entry= link_to_older @older_url - - .column-1 - - if @account.memorial? - .memoriam-widget= t('in_memoriam_html') - - elsif @account.moved? - = render 'moved', account: @account - - = render 'bio', account: @account - - - if @endorsed_accounts.empty? && @account.id == current_account&.id - .placeholder-widget= t('accounts.endorsements_hint') - - elsif !@endorsed_accounts.empty? - .endorsements-widget - %h4= t 'accounts.choices_html', name: content_tag(:bdi, display_name(@account, custom_emojify: true)) - - - @endorsed_accounts.each do |account| - = account_link_to account - - - if @featured_hashtags.empty? && @account.id == current_account&.id - .placeholder-widget - = t('accounts.featured_tags_hint') - = link_to settings_featured_tags_path do - = t('featured_tags.add_new') - = fa_icon 'chevron-right fw' - - else - - @featured_hashtags.each do |featured_tag| - .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil } - = link_to short_account_tag_path(@account, featured_tag.tag) do - %h4 - = fa_icon 'hashtag' - = featured_tag.name - %small - - if featured_tag.last_status_at.nil? - = t('accounts.nothing_here') - - else - %time.formatted{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at - .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true - - = render 'application/sidebar' + %div + = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps') diff --git a/app/views/authorize_interactions/_post_follow_actions.html.haml b/app/views/authorize_interactions/_post_follow_actions.html.haml index dd71160e2d7..611e4a9c053 100644 --- a/app/views/authorize_interactions/_post_follow_actions.html.haml +++ b/app/views/authorize_interactions/_post_follow_actions.html.haml @@ -1,4 +1,4 @@ .post-follow-actions - %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@resource.id}"), class: 'button button--block' + %div= link_to t('authorize_follow.post_follow.web'), short_account_path(@resource.acct), class: 'button button--block' %div= link_to t('authorize_follow.post_follow.return'), ActivityPub::TagManager.instance.url_for(@resource), class: 'button button--block' %div= t('authorize_follow.post_follow.close') diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml index 7975ee9997b..ec0f00a60ed 100644 --- a/app/views/directories/index.html.haml +++ b/app/views/directories/index.html.haml @@ -10,46 +10,13 @@ = opengraph 'og:description', t('directories.explanation') = opengraph 'og:image', File.join(root_url, 'android-chrome-192x192.png') -.page-header - %h1= t('directories.explore_mastodon', title: site_title) - %p= t('directories.explanation') + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} + = render_initial_state + = javascript_pack_tag 'application', crossorigin: 'anonymous' -- if @accounts.empty? - = nothing_here -- else - .directory__list - - @accounts.each do |account| - .directory__card - .directory__card__img - = image_tag account.header.url, alt: '' - .directory__card__bar - = link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do - .avatar - = image_tag account.avatar.url, alt: '', class: 'u-photo' +.app-holder#mastodon{ data: { props: Oj.dump(default_props) } } + %noscript + = image_pack_tag 'logo.svg', alt: 'Mastodon' - .display-name - %bdi - %strong.emojify.p-name= display_name(account, custom_emojify: true) - %span= acct(account) - .directory__card__bar__relationship.account__relationship - = minimal_account_action_button(account) - - .directory__card__extra - .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) - - .directory__card__extra - .accounts-table__count - = number_to_human account.statuses_count, strip_insignificant_zeros: true - %small= t('accounts.posts', count: account.statuses_count).downcase - .accounts-table__count - = number_to_human account.followers_count, strip_insignificant_zeros: true - %small= t('accounts.followers', count: account.followers_count).downcase - .accounts-table__count - - if account.last_status_at.present? - %time.time-ago{ datetime: account.last_status_at.to_date.iso8601, title: l(account.last_status_at.to_date) }= l account.last_status_at.to_date - - else - = t('accounts.never_active') - - %small= t('accounts.last_active') - - = paginate @accounts + %div + = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps') diff --git a/app/views/follower_accounts/index.html.haml b/app/views/follower_accounts/index.html.haml index 645dd2de17c..8b0e52a98fc 100644 --- a/app/views/follower_accounts/index.html.haml +++ b/app/views/follower_accounts/index.html.haml @@ -5,16 +5,13 @@ %meta{ name: 'robots', content: 'noindex' }/ = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false) -= render 'accounts/header', account: @account + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} + = render_initial_state + = javascript_pack_tag 'application', crossorigin: 'anonymous' -- if @account.user_hides_network? - .nothing-here= t('accounts.network_hidden') -- elsif user_signed_in? && @account.blocking?(current_account) - .nothing-here= t('accounts.unavailable') -- elsif @follows.empty? - = nothing_here -- else - .card-grid - = render partial: 'application/card', collection: @follows.map(&:account), as: :account +.app-holder#mastodon{ data: { props: Oj.dump(default_props) } } + %noscript + = image_pack_tag 'logo.svg', alt: 'Mastodon' - = paginate @follows + %div + = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps') diff --git a/app/views/following_accounts/index.html.haml b/app/views/following_accounts/index.html.haml index 17fe790188c..9b10d219724 100644 --- a/app/views/following_accounts/index.html.haml +++ b/app/views/following_accounts/index.html.haml @@ -5,16 +5,13 @@ %meta{ name: 'robots', content: 'noindex' }/ = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false) -= render 'accounts/header', account: @account + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} + = render_initial_state + = javascript_pack_tag 'application', crossorigin: 'anonymous' -- if @account.user_hides_network? - .nothing-here= t('accounts.network_hidden') -- elsif user_signed_in? && @account.blocking?(current_account) - .nothing-here= t('accounts.unavailable') -- elsif @follows.empty? - = nothing_here -- else - .card-grid - = render partial: 'application/card', collection: @follows.map(&:target_account), as: :account +.app-holder#mastodon{ data: { props: Oj.dump(default_props) } } + %noscript + = image_pack_tag 'logo.svg', alt: 'Mastodon' - = paginate @follows + %div + = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps') diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml index 9b7e1b65c63..4a7c390eb3e 100644 --- a/app/views/notification_mailer/_status.html.haml +++ b/app/views/notification_mailer/_status.html.haml @@ -42,4 +42,4 @@ = link_to a.remote_url, a.remote_url %p.status-footer - = link_to l(status.created_at), web_url("statuses/#{status.id}") + = link_to l(status.created_at), short_account_status_url(status.account.acct, status.id) diff --git a/app/views/notification_mailer/_status.text.erb b/app/views/notification_mailer/_status.text.erb index 8999a1f8ea5..6d55741ecb2 100644 --- a/app/views/notification_mailer/_status.text.erb +++ b/app/views/notification_mailer/_status.text.erb @@ -5,4 +5,4 @@ <% end %> <%= raw Formatter.instance.plaintext(status) %> -<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %> +<%= raw t('application_mailer.view')%> <%= short_account_status_url(status.account.acct, status.id) %> diff --git a/app/views/notification_mailer/digest.html.haml b/app/views/notification_mailer/digest.html.haml index a94ace228d8..f46e176d7be 100644 --- a/app/views/notification_mailer/digest.html.haml +++ b/app/views/notification_mailer/digest.html.haml @@ -19,7 +19,7 @@ %tbody %tr %td.button-primary - = link_to web_url do + = link_to home_url do %span= t 'notification_mailer.digest.action' - @notifications.each_with_index do |n, i| diff --git a/app/views/notification_mailer/digest.text.erb b/app/views/notification_mailer/digest.text.erb index b2c85a9e3dc..9dd81f64f99 100644 --- a/app/views/notification_mailer/digest.text.erb +++ b/app/views/notification_mailer/digest.text.erb @@ -7,7 +7,7 @@ <%= raw Formatter.instance.plaintext(notification.target_status) %> - <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{notification.target_status.id}") %> + <%= raw t('application_mailer.view')%> <%= short_account_status_url(notification.target_status.account.acct, notification.target_status.id) %> <% end %> <% if @follows_since > 0 %> diff --git a/app/views/notification_mailer/favourite.html.haml b/app/views/notification_mailer/favourite.html.haml index a715d615ce8..e59770fe371 100644 --- a/app/views/notification_mailer/favourite.html.haml +++ b/app/views/notification_mailer/favourite.html.haml @@ -41,5 +41,5 @@ %tbody %tr %td.button-primary - = link_to web_url("statuses/#{@status.id}") do + = link_to short_account_status_url(status.account.acct, status.id) do %span= t 'application_mailer.view_status' diff --git a/app/views/notification_mailer/follow.html.haml b/app/views/notification_mailer/follow.html.haml index cd84f785847..e9b3c8f96fe 100644 --- a/app/views/notification_mailer/follow.html.haml +++ b/app/views/notification_mailer/follow.html.haml @@ -39,5 +39,5 @@ %tbody %tr %td.button-primary - = link_to web_url("accounts/#{@account.id}") do + = link_to short_account_url(@account.acct) do %span= t 'application_mailer.view_profile' diff --git a/app/views/notification_mailer/follow.text.erb b/app/views/notification_mailer/follow.text.erb index cbe46f55241..a5e6019e7e0 100644 --- a/app/views/notification_mailer/follow.text.erb +++ b/app/views/notification_mailer/follow.text.erb @@ -2,4 +2,4 @@ <%= raw t('notification_mailer.follow.body', name: @account.acct) %> -<%= raw t('application_mailer.view')%> <%= web_url("accounts/#{@account.id}") %> +<%= raw t('application_mailer.view')%> <%= short_account_url(@account.acct) %> diff --git a/app/views/notification_mailer/follow_request.html.haml b/app/views/notification_mailer/follow_request.html.haml index a63e27a909e..f2d04b9ebe6 100644 --- a/app/views/notification_mailer/follow_request.html.haml +++ b/app/views/notification_mailer/follow_request.html.haml @@ -39,5 +39,5 @@ %tbody %tr %td.button-primary - = link_to web_url("follow_requests") do + = link_to follow_requests_url do %span= t 'notification_mailer.follow_request.action' diff --git a/app/views/notification_mailer/follow_request.text.erb b/app/views/notification_mailer/follow_request.text.erb index a018394b857..e2a608e98f9 100644 --- a/app/views/notification_mailer/follow_request.text.erb +++ b/app/views/notification_mailer/follow_request.text.erb @@ -2,4 +2,4 @@ <%= raw t('notification_mailer.follow_request.body', name: @account.acct) %> -<%= raw t('application_mailer.view')%> <%= web_url("follow_requests") %> +<%= raw t('application_mailer.view')%> <%= follow_requests_url %> diff --git a/app/views/notification_mailer/mention.html.haml b/app/views/notification_mailer/mention.html.haml index 619873cfa3a..34bfcc7044c 100644 --- a/app/views/notification_mailer/mention.html.haml +++ b/app/views/notification_mailer/mention.html.haml @@ -41,5 +41,5 @@ %tbody %tr %td.button-primary - = link_to web_url("statuses/#{@status.id}") do + = link_to short_account_status_url(@status.account.acct, @status.id) do %span= t 'notification_mailer.mention.action' diff --git a/app/views/notification_mailer/reblog.html.haml b/app/views/notification_mailer/reblog.html.haml index a2811be2326..16845c27920 100644 --- a/app/views/notification_mailer/reblog.html.haml +++ b/app/views/notification_mailer/reblog.html.haml @@ -41,5 +41,5 @@ %tbody %tr %td.button-primary - = link_to web_url("statuses/#{@status.id}") do + = link_to short_account_status_url(@status.account.acct, @status.id) do %span= t 'application_mailer.view_status' diff --git a/app/views/public_timelines/show.html.haml b/app/views/public_timelines/show.html.haml index 9254bd34852..5c208f9066b 100644 --- a/app/views/public_timelines/show.html.haml +++ b/app/views/public_timelines/show.html.haml @@ -3,15 +3,13 @@ - content_for :header_tags do %meta{ name: 'robots', content: 'noindex' }/ - = javascript_pack_tag 'about', crossorigin: 'anonymous' + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} + = render_initial_state + = javascript_pack_tag 'application', crossorigin: 'anonymous' -.page-header - %h1= t('about.see_whats_happening') +.app-holder.notranslate#mastodon{ data: { props: Oj.dump(default_props) } } + %noscript + = image_pack_tag 'logo.svg', alt: 'Mastodon' - - if Setting.show_known_fediverse_at_about_page - %p= t('about.browse_public_posts') - - else - %p= t('about.browse_local_posts') - -#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(local: !Setting.show_known_fediverse_at_about_page)) }} -.notranslate#modal-container + %div + = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps') diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index 93af131e5a6..a0077701c88 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -77,4 +77,4 @@ - if user_signed_in? · - = link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'detailed-status__application', target: '_blank' + = link_to t('statuses.open_in_web'), short_account_status_url(status.account.acct, status.id), class: 'detailed-status__application', target: '_blank' diff --git a/app/views/statuses/show.html.haml b/app/views/statuses/show.html.haml index 7ef7b09a2de..0e2ef755dfc 100644 --- a/app/views/statuses/show.html.haml +++ b/app/views/statuses/show.html.haml @@ -17,9 +17,13 @@ = render 'og_description', activity: @status = render 'og_image', activity: @status, account: @account -.grid - .column-0 - .activity-stream.h-entry - = render partial: 'status', locals: { status: @status, include_threads: true } - .column-1 - = render 'application/sidebar' + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} + = render_initial_state + = javascript_pack_tag 'application', crossorigin: 'anonymous' + +.app-holder#mastodon{ data: { props: Oj.dump(default_props) } } + %noscript + = image_pack_tag 'logo.svg', alt: 'Mastodon' + + %div + = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps') diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml index 5cd513b320e..85032af98e0 100644 --- a/app/views/tags/show.html.haml +++ b/app/views/tags/show.html.haml @@ -5,12 +5,15 @@ %meta{ name: 'robots', content: 'noindex' }/ %link{ rel: 'alternate', type: 'application/rss+xml', href: tag_url(@tag, format: 'rss') }/ - = javascript_pack_tag 'about', crossorigin: 'anonymous' = render 'og' -.page-header - %h1= "##{@tag.name}" - %p= t('about.about_hashtag_html', hashtag: @tag.name) + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} + = render_initial_state + = javascript_pack_tag 'application', crossorigin: 'anonymous' -#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name, local: @local)) }} -.notranslate#modal-container +.app-holder.notranslate#mastodon{ data: { props: Oj.dump(default_props) } } + %noscript + = image_pack_tag 'logo.svg', alt: 'Mastodon' + + %div + = t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps') diff --git a/app/views/user_mailer/welcome.html.haml b/app/views/user_mailer/welcome.html.haml index 1f75ff48ae4..d9fd1cd4fb2 100644 --- a/app/views/user_mailer/welcome.html.haml +++ b/app/views/user_mailer/welcome.html.haml @@ -114,7 +114,7 @@ %tbody %tr %td.button-primary - = link_to web_url do + = link_to home_url do %span= t 'user_mailer.welcome.final_action' %table.email-table{ cellspacing: 0, cellpadding: 0 } diff --git a/app/views/user_mailer/welcome.text.erb b/app/views/user_mailer/welcome.text.erb index e310d7ca6f9..e97f80128bb 100644 --- a/app/views/user_mailer/welcome.text.erb +++ b/app/views/user_mailer/welcome.text.erb @@ -17,7 +17,7 @@ <%= t 'user_mailer.welcome.final_step' %> -=> <%= web_url %> +=> <%= home_url %> --- diff --git a/config/routes.rb b/config/routes.rb index a534b433e07..c0b385cd73c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,25 @@ require 'sidekiq-scheduler/web' Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base] +# These paths do not have special server-side controllers and must load the web app only +APP_PATHS = %w( + /home + /getting-started + /keyboard-shortcuts + /conversations + /lists/(*any) + /notifications + /favourites + /bookmarks + /pinned + /search + /publish + /follow_requests + /blocks + /domain_blocks + /mutes +).freeze + Rails.application.routes.draw do root 'home#index' @@ -76,8 +95,6 @@ Rails.application.routes.draw do resources :followers, only: [:index], controller: :follower_accounts resources :following, only: [:index], controller: :following_accounts - resource :follow, only: [:create], controller: :account_follow - resource :unfollow, only: [:create], controller: :account_unfollow resource :outbox, only: [:show], module: :activitypub resource :inbox, only: [:create], module: :activitypub @@ -92,6 +109,8 @@ Rails.application.routes.draw do get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies get '/@:username/media', to: 'accounts#show', as: :short_account_media get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag + get '/@:username/followers', to: 'follower_accounts#index', as: :short_account_followers + get '/@:username/following', to: 'following_accounts#index', as: :short_account_following get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status @@ -99,7 +118,6 @@ Rails.application.routes.draw do post '/interact/:id', to: 'remote_interaction#create' get '/explore', to: 'directories#index', as: :explore - get '/explore/:id', to: 'directories#show', as: :explore_hashtag get '/settings', to: redirect('/settings/profile') @@ -181,6 +199,7 @@ Rails.application.routes.draw do resource :relationships, only: [:show, :update] get '/public', to: 'public_timelines#show', as: :public_timeline + get '/public/local', to: 'public_timelines#show', as: :local_public_timeline get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy resource :authorize_interaction, only: [:show, :create] @@ -431,6 +450,7 @@ Rails.application.routes.draw do get :verify_credentials, to: 'credentials#show' patch :update_credentials, to: 'credentials#update' resource :search, only: :show, controller: :search + resource :lookup, only: :show, controller: :lookup resources :relationships, only: :index end @@ -515,7 +535,9 @@ Rails.application.routes.draw do end end - get '/web/(*any)', to: 'home#index', as: :web + APP_PATHS.each do |app_path| + get app_path, to: 'home#index' + end get '/about', to: 'about#show' get '/about/more', to: 'about#more' -- GitLab