From 02ba9cfa35c7b2285950955619ae3431391e9625 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 4 Oct 2022 20:13:46 +0200 Subject: Remove code for rendering public and hashtag timelines outside the web UI (#19257) --- app/helpers/application_helper.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'app/helpers') diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index db33292c1..14d27b148 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -198,10 +198,7 @@ module ApplicationHelper def render_initial_state state_params = { - settings: { - known_fediverse: Setting.show_known_fediverse_at_about_page, - }, - + settings: {}, text: [params[:title], params[:text], params[:url]].compact.join(' '), } -- cgit From 93f340a4bf35082968118319448905b489b101a3 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 6 Oct 2022 10:16:47 +0200 Subject: Remove setting that disables account deletes (#17683) --- app/controllers/settings/deletes_controller.rb | 5 ----- app/helpers/application_helper.rb | 4 ---- app/models/form/admin_settings.rb | 2 -- app/views/admin/settings/edit.html.haml | 3 --- app/views/auth/registrations/edit.html.haml | 7 +++---- app/views/settings/profiles/show.html.haml | 7 +++---- config/locales/en.yml | 3 --- spec/controllers/settings/deletes_controller_spec.rb | 14 -------------- 8 files changed, 6 insertions(+), 39 deletions(-) (limited to 'app/helpers') diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb index e0dd5edcb..bb096567a 100644 --- a/app/controllers/settings/deletes_controller.rb +++ b/app/controllers/settings/deletes_controller.rb @@ -4,7 +4,6 @@ class Settings::DeletesController < Settings::BaseController skip_before_action :require_functional! before_action :require_not_suspended! - before_action :check_enabled_deletion def show @confirmation = Form::DeleteConfirmation.new @@ -21,10 +20,6 @@ class Settings::DeletesController < Settings::BaseController private - def check_enabled_deletion - redirect_to root_path unless Setting.open_deletion - end - def resource_params params.require(:form_delete_confirmation).permit(:password, :username) end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 14d27b148..23884fbd4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -87,10 +87,6 @@ module ApplicationHelper link_to label, omniauth_authorize_path(:user, provider), class: "button button-#{provider}", method: :post end - def open_deletion? - Setting.open_deletion - end - def locale_direction if RTL_LOCALES.include?(I18n.locale) 'rtl' diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index e744344c5..7bd9e3743 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -13,7 +13,6 @@ class Form::AdminSettings site_terms registrations_mode closed_registrations_message - open_deletion timeline_preview bootstrap_timeline_accounts theme @@ -37,7 +36,6 @@ class Form::AdminSettings ).freeze BOOLEAN_KEYS = %i( - open_deletion timeline_preview activity_api_enabled peers_api_enabled diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml index a00cd0222..79f73a60f 100644 --- a/app/views/admin/settings/edit.html.haml +++ b/app/views/admin/settings/edit.html.haml @@ -57,9 +57,6 @@ .fields-group = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html') - .fields-group - = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html') - - unless whitelist_mode? .fields-group = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html'), recommended: true diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index a3445b421..df929e3e8 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -41,8 +41,7 @@ %h3= t('migrations.incoming_migrations') %p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path) - - if open_deletion? - %hr.spacer/ + %hr.spacer/ - %h3= t('auth.delete_account') - %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) + %h3= t('auth.delete_account') + %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index fe9666d84..3067b3737 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -70,8 +70,7 @@ %h6= t 'migrations.incoming_migrations' %p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path) -- if open_deletion? - %hr.spacer/ +%hr.spacer/ - %h6= t('auth.delete_account') - %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) +%h6= t('auth.delete_account') +%p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) diff --git a/config/locales/en.yml b/config/locales/en.yml index b41e4f47b..505a2f9fc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -754,9 +754,6 @@ en: closed_message: desc_html: Displayed on frontpage when registrations are closed. You can use HTML tags title: Closed registration message - deletion: - desc_html: Allow anyone to delete their account - title: Open account deletion require_invite_text: desc_html: When registrations require manual approval, make the “Why do you want to join?” text input mandatory rather than optional title: Require new users to enter a reason to join diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb index cd36ecc35..a94dc042a 100644 --- a/spec/controllers/settings/deletes_controller_spec.rb +++ b/spec/controllers/settings/deletes_controller_spec.rb @@ -81,20 +81,6 @@ describe Settings::DeletesController do expect(response).to redirect_to settings_delete_path end end - - context 'when account deletions are disabled' do - around do |example| - open_deletion = Setting.open_deletion - example.run - Setting.open_deletion = open_deletion - end - - it 'redirects' do - Setting.open_deletion = false - delete :destroy - expect(response).to redirect_to root_path - end - end end context 'when not signed in' do -- cgit From 7afc6a630c76fb071bd189af3ac1366efc82f819 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Thu, 13 Oct 2022 04:07:30 +0900 Subject: Redirect non-logged-in user to owner statuses on single user mode (#19333) --- app/helpers/application_helper.rb | 4 ++ app/javascript/mastodon/features/ui/index.js | 4 +- app/javascript/mastodon/initial_state.js | 80 +++++++++++++++++++++------- app/presenters/initial_state_presenter.rb | 2 +- app/serializers/initial_state_serializer.rb | 6 +++ spec/views/statuses/show.html.haml_spec.rb | 1 + 6 files changed, 77 insertions(+), 20 deletions(-) (limited to 'app/helpers') diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 23884fbd4..9cc34cab6 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -211,6 +211,10 @@ module ApplicationHelper state_params[:admin] = Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')) end + if single_user_mode? + state_params[:owner] = Account.local.without_suspended.where('id > 0').first + end + json = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(state_params), serializer: InitialStateSerializer).to_json # rubocop:disable Rails/OutputSafety content_tag(:script, json_escape(json).html_safe, id: 'initial-state', type: 'application/json') diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 2edd3b9fe..8333ea282 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -54,7 +54,7 @@ import { About, PrivacyPolicy, } from './util/async-components'; -import { me } from '../../initial_state'; +import initialState, { me, owner, singleUserMode } from '../../initial_state'; import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; import Header from './components/header'; @@ -161,6 +161,8 @@ class SwitchingColumnsArea extends React.PureComponent { } else { redirect = ; } + } else if (singleUserMode && owner && initialState?.accounts[owner]) { + redirect = ; } else { redirect = ; } diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 031c748cf..f9843f7f8 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -1,5 +1,44 @@ // @ts-check +/** + * @typedef Emoji + * @property {string} shortcode + * @property {string} static_url + * @property {string} url + */ + +/** + * @typedef AccountField + * @property {string} name + * @property {string} value + * @property {string} verified_at + */ + +/** + * @typedef Account + * @property {string} acct + * @property {string} avatar + * @property {string} avatar_static + * @property {boolean} bot + * @property {string} created_at + * @property {boolean=} discoverable + * @property {string} display_name + * @property {Emoji[]} emojis + * @property {AccountField[]} fields + * @property {number} followers_count + * @property {number} following_count + * @property {boolean} group + * @property {string} header + * @property {string} header_static + * @property {string} id + * @property {string=} last_status_at + * @property {boolean} locked + * @property {string} note + * @property {number} statuses_count + * @property {string} url + * @property {string} username + */ + /** * @typedef {[code: string, name: string, localName: string]} InitialStateLanguage */ @@ -22,11 +61,13 @@ * @property {string} locale * @property {string | null} mascot * @property {string=} me + * @property {string=} owner * @property {boolean} profile_directory * @property {boolean} registrations_open * @property {boolean} reduce_motion * @property {string} repository * @property {boolean} search_enabled + * @property {boolean} single_user_mode * @property {string} source_url * @property {string} streaming_api_base_url * @property {boolean} timeline_preview @@ -40,13 +81,14 @@ /** * @typedef InitialState + * @property {Record} accounts * @property {InitialStateLanguage[]} languages * @property {InitialStateMeta} meta */ const element = document.getElementById('initial-state'); /** @type {InitialState | undefined} */ -const initialState = element && JSON.parse(element.textContent); +const initialState = element?.textContent && JSON.parse(element.textContent); /** * @template {keyof InitialStateMeta} K @@ -55,32 +97,34 @@ const initialState = element && JSON.parse(element.textContent); */ const getMeta = (prop) => initialState?.meta && initialState.meta[prop]; -export const domain = getMeta('domain'); -export const reduceMotion = getMeta('reduce_motion'); +export const activityApiEnabled = getMeta('activity_api_enabled'); export const autoPlayGif = getMeta('auto_play_gif'); -export const displayMedia = getMeta('display_media'); -export const expandSpoilers = getMeta('expand_spoilers'); -export const unfollowModal = getMeta('unfollow_modal'); export const boostModal = getMeta('boost_modal'); +export const cropImages = getMeta('crop_images'); export const deleteModal = getMeta('delete_modal'); -export const me = getMeta('me'); -export const searchEnabled = getMeta('search_enabled'); +export const disableSwiping = getMeta('disable_swiping'); +export const displayMedia = getMeta('display_media'); +export const domain = getMeta('domain'); +export const expandSpoilers = getMeta('expand_spoilers'); +export const forceSingleColumn = !getMeta('advanced_layout'); export const limitedFederationMode = getMeta('limited_federation_mode'); +export const mascot = getMeta('mascot'); +export const me = getMeta('me'); +export const owner = getMeta('owner'); +export const profile_directory = getMeta('profile_directory'); +export const reduceMotion = getMeta('reduce_motion'); export const registrationsOpen = getMeta('registrations_open'); export const repository = getMeta('repository'); +export const searchEnabled = getMeta('search_enabled'); +export const showTrends = getMeta('trends'); +export const singleUserMode = getMeta('single_user_mode'); export const source_url = getMeta('source_url'); -export const version = getMeta('version'); -export const mascot = getMeta('mascot'); -export const profile_directory = getMeta('profile_directory'); -export const forceSingleColumn = !getMeta('advanced_layout'); +export const timelinePreview = getMeta('timeline_preview'); +export const title = getMeta('title'); +export const unfollowModal = getMeta('unfollow_modal'); export const useBlurhash = getMeta('use_blurhash'); export const usePendingItems = getMeta('use_pending_items'); -export const showTrends = getMeta('trends'); -export const title = getMeta('title'); -export const cropImages = getMeta('crop_images'); -export const disableSwiping = getMeta('disable_swiping'); -export const timelinePreview = getMeta('timeline_preview'); -export const activityApiEnabled = getMeta('activity_api_enabled'); +export const version = getMeta('version'); export const languages = initialState?.languages; export default initialState; diff --git a/app/presenters/initial_state_presenter.rb b/app/presenters/initial_state_presenter.rb index 129ea2a46..ed0479211 100644 --- a/app/presenters/initial_state_presenter.rb +++ b/app/presenters/initial_state_presenter.rb @@ -2,7 +2,7 @@ class InitialStatePresenter < ActiveModelSerializers::Model attributes :settings, :push_subscription, :token, - :current_account, :admin, :text, :visibility + :current_account, :admin, :owner, :text, :visibility def role current_account&.user_role diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index bec725e1b..ba446854c 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -30,6 +30,7 @@ class InitialStateSerializer < ActiveModel::Serializer registrations_open: Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode, timeline_preview: Setting.timeline_preview, activity_api_enabled: Setting.activity_api_enabled, + single_user_mode: Rails.configuration.x.single_user_mode, } if object.current_account @@ -55,6 +56,10 @@ class InitialStateSerializer < ActiveModel::Serializer store[:crop_images] = Setting.crop_images end + if Rails.configuration.x.single_user_mode + store[:owner] = object.owner&.id&.to_s + end + store end # rubocop:enable Metrics/AbcSize @@ -78,6 +83,7 @@ class InitialStateSerializer < ActiveModel::Serializer store = {} store[object.current_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account store[object.admin.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin + store[object.owner.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.owner, serializer: REST::AccountSerializer) if object.owner store end diff --git a/spec/views/statuses/show.html.haml_spec.rb b/spec/views/statuses/show.html.haml_spec.rb index a69843216..eeea2f698 100644 --- a/spec/views/statuses/show.html.haml_spec.rb +++ b/spec/views/statuses/show.html.haml_spec.rb @@ -12,6 +12,7 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do allow(view).to receive(:local_time) allow(view).to receive(:local_time_ago) allow(view).to receive(:current_account).and_return(nil) + allow(view).to receive(:single_user_mode?).and_return(false) assign(:instance_presenter, InstancePresenter.new) end -- cgit From 839f893168ab221b08fa439012189e6c29a2721a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 20 Oct 2022 14:35:29 +0200 Subject: Change public accounts pages to mount the web UI (#19319) * Change public accounts pages to mount the web UI * Fix handling of remote usernames in routes - When logged in, serve web app - When logged out, redirect to permalink - Fix `app-body` class not being set sometimes due to name conflict * Fix missing `multiColumn` prop * Fix failing test * Use `discoverable` attribute to control indexing directives * Fix `` not using `multiColumn` * Add `noindex` to accounts in REST API * Change noindex directive to not be rendered by default before a route is mounted * Add loading indicator for detailed status in web UI * Fix missing indicator appearing while account is loading in web UI --- app/controllers/about_controller.rb | 8 + app/controllers/account_follow_controller.rb | 12 - app/controllers/account_unfollow_controller.rb | 12 - app/controllers/accounts_controller.rb | 58 -- .../concerns/account_controller_concern.rb | 3 +- .../concerns/web_app_controller_concern.rb | 13 +- app/controllers/follower_accounts_controller.rb | 5 +- app/controllers/following_accounts_controller.rb | 5 +- app/controllers/home_controller.rb | 13 +- app/controllers/privacy_controller.rb | 8 + app/controllers/remote_follow_controller.rb | 41 -- app/controllers/remote_interaction_controller.rb | 55 -- app/controllers/statuses_controller.rb | 2 +- app/controllers/tags_controller.rb | 10 +- app/helpers/accounts_helper.rb | 50 +- .../mastodon/components/error_boundary.js | 7 + .../mastodon/components/missing_indicator.js | 5 + app/javascript/mastodon/containers/mastodon.js | 2 +- app/javascript/mastodon/features/about/index.js | 6 +- .../mastodon/features/account/components/header.js | 5 +- .../mastodon/features/account_timeline/index.js | 12 +- .../mastodon/features/bookmarked_statuses/index.js | 1 + .../mastodon/features/community_timeline/index.js | 1 + app/javascript/mastodon/features/compose/index.js | 5 + .../mastodon/features/direct_timeline/index.js | 1 + .../mastodon/features/directory/index.js | 1 + .../mastodon/features/domain_blocks/index.js | 6 + app/javascript/mastodon/features/explore/index.js | 1 + .../mastodon/features/favourited_statuses/index.js | 1 + .../mastodon/features/favourites/index.js | 5 + .../features/follow_recommendations/index.js | 5 + .../mastodon/features/follow_requests/index.js | 5 + .../mastodon/features/getting_started/index.js | 1 + .../mastodon/features/hashtag_timeline/index.js | 1 + .../mastodon/features/home_timeline/index.js | 3 +- .../mastodon/features/keyboard_shortcuts/index.js | 5 + .../mastodon/features/list_timeline/index.js | 1 + app/javascript/mastodon/features/lists/index.js | 1 + app/javascript/mastodon/features/mutes/index.js | 5 + .../mastodon/features/notifications/index.js | 1 + .../mastodon/features/pinned_statuses/index.js | 4 + .../mastodon/features/privacy_policy/index.js | 6 +- .../mastodon/features/public_timeline/index.js | 1 + app/javascript/mastodon/features/reblogs/index.js | 5 + app/javascript/mastodon/features/status/index.js | 17 +- .../features/ui/components/bundle_column_error.js | 27 +- .../features/ui/components/column_loading.js | 6 +- .../features/ui/components/columns_area.js | 4 +- .../mastodon/features/ui/components/modal_root.js | 21 +- app/javascript/mastodon/features/ui/index.js | 4 +- .../mastodon/features/ui/util/async-components.js | 8 + .../features/ui/util/react_router_helpers.js | 4 +- app/javascript/mastodon/main.js | 8 - app/javascript/mastodon/reducers/statuses.js | 6 + app/javascript/mastodon/selectors/index.js | 2 +- .../service_worker/web_push_notifications.js | 26 +- app/javascript/packs/public.js | 29 - app/javascript/styles/application.scss | 1 - app/javascript/styles/contrast/diff.scss | 4 - app/javascript/styles/mastodon-light/diff.scss | 89 --- app/javascript/styles/mastodon/containers.scss | 782 --------------------- app/javascript/styles/mastodon/footer.scss | 152 ---- app/javascript/styles/mastodon/rtl.scss | 74 -- app/javascript/styles/mastodon/statuses.scss | 3 +- app/lib/permalink_redirector.rb | 36 +- app/models/account.rb | 1 + app/models/user.rb | 4 + app/serializers/rest/account_serializer.rb | 7 +- app/views/about/show.html.haml | 3 + app/views/accounts/_bio.html.haml | 21 - app/views/accounts/_header.html.haml | 43 -- app/views/accounts/_moved.html.haml | 20 - app/views/accounts/show.html.haml | 76 +- app/views/follower_accounts/index.html.haml | 18 +- app/views/following_accounts/index.html.haml | 18 +- app/views/home/index.html.haml | 3 + app/views/layouts/public.html.haml | 60 -- app/views/privacy/show.html.haml | 3 + app/views/remote_follow/new.html.haml | 20 - app/views/remote_interaction/new.html.haml | 24 - app/views/statuses/_detailed_status.html.haml | 6 +- app/views/statuses/_simple_status.html.haml | 6 +- app/views/statuses/show.html.haml | 2 +- app/views/tags/show.html.haml | 5 + config/locales/en.yml | 40 -- config/routes.rb | 57 +- package.json | 1 - spec/controllers/account_follow_controller_spec.rb | 64 -- .../account_unfollow_controller_spec.rb | 64 -- spec/controllers/accounts_controller_spec.rb | 194 ----- .../authorize_interactions_controller_spec.rb | 4 +- .../follower_accounts_controller_spec.rb | 21 - .../following_accounts_controller_spec.rb | 21 - spec/controllers/remote_follow_controller_spec.rb | 135 ---- .../remote_interaction_controller_spec.rb | 39 - spec/controllers/tags_controller_spec.rb | 7 +- spec/features/profile_spec.rb | 26 +- spec/lib/permalink_redirector_spec.rb | 31 +- spec/requests/account_show_page_spec.rb | 15 - spec/routing/accounts_routing_spec.rb | 88 ++- yarn.lock | 5 - 101 files changed, 389 insertions(+), 2464 deletions(-) delete mode 100644 app/controllers/account_follow_controller.rb delete mode 100644 app/controllers/account_unfollow_controller.rb delete mode 100644 app/controllers/remote_follow_controller.rb delete mode 100644 app/controllers/remote_interaction_controller.rb delete mode 100644 app/javascript/styles/mastodon/footer.scss delete mode 100644 app/views/accounts/_bio.html.haml delete mode 100644 app/views/accounts/_header.html.haml delete mode 100644 app/views/accounts/_moved.html.haml delete mode 100644 app/views/layouts/public.html.haml delete mode 100644 app/views/remote_follow/new.html.haml delete mode 100644 app/views/remote_interaction/new.html.haml create mode 100644 app/views/tags/show.html.haml delete mode 100644 spec/controllers/account_follow_controller_spec.rb delete mode 100644 spec/controllers/account_unfollow_controller_spec.rb delete mode 100644 spec/controllers/remote_follow_controller_spec.rb delete mode 100644 spec/controllers/remote_interaction_controller_spec.rb (limited to 'app/helpers') diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 0fbc6a800..104348614 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -5,7 +5,15 @@ class AboutController < ApplicationController skip_before_action :require_functional! + before_action :set_instance_presenter + def show expires_in 0, public: true unless user_signed_in? end + + private + + def set_instance_presenter + @instance_presenter = InstancePresenter.new + end end diff --git a/app/controllers/account_follow_controller.rb b/app/controllers/account_follow_controller.rb deleted file mode 100644 index 33394074d..000000000 --- a/app/controllers/account_follow_controller.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class AccountFollowController < ApplicationController - include AccountControllerConcern - - before_action :authenticate_user! - - def create - FollowService.new.call(current_user.account, @account, with_rate_limit: true) - redirect_to account_path(@account) - end -end diff --git a/app/controllers/account_unfollow_controller.rb b/app/controllers/account_unfollow_controller.rb deleted file mode 100644 index 378ec86dc..000000000 --- a/app/controllers/account_unfollow_controller.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class AccountUnfollowController < ApplicationController - include AccountControllerConcern - - before_action :authenticate_user! - - def create - UnfollowService.new.call(current_user.account, @account) - redirect_to account_path(@account) - end -end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index d92f91b30..5ceea5d3c 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -9,7 +9,6 @@ class AccountsController < ApplicationController before_action :require_account_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 +17,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 = cached_filtered_status_pins 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 @@ -55,18 +36,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 - - def filtered_pinned_statuses - @account.pinned_statuses.where(visibility: [:public, :unlisted]) - end - def filtered_statuses default_statuses.tap do |statuses| statuses.merge!(hashtag_scope) if tag_requested? @@ -113,26 +82,6 @@ class AccountsController < ApplicationController end end - def older_url - pagination_url(max_id: @statuses.last.id) - end - - def newer_url - pagination_url(min_id: @statuses.first.id) - end - - def pagination_url(max_id: nil, min_id: nil) - if tag_requested? - short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id) - elsif media_requested? - short_account_media_url(@account, max_id: max_id, min_id: min_id) - elsif replies_requested? - short_account_with_replies_url(@account, max_id: max_id, min_id: min_id) - else - short_account_url(@account, max_id: max_id, min_id: min_id) - end - end - def media_requested? request.path.split('.').first.end_with?('/media') && !tag_requested? end @@ -145,13 +94,6 @@ class AccountsController < ApplicationController request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) end - def cached_filtered_status_pins - cache_collection( - filtered_pinned_statuses, - Status - ) - end - def cached_filtered_status_page cache_collection_paginated_by_id( filtered_statuses, diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb index 11eac0eb6..2f7d84df0 100644 --- a/app/controllers/concerns/account_controller_concern.rb +++ b/app/controllers/concerns/account_controller_concern.rb @@ -3,13 +3,12 @@ module AccountControllerConcern extend ActiveSupport::Concern + include WebAppControllerConcern include AccountOwnedConcern 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 diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb index 8a6c73af3..c671ce785 100644 --- a/app/controllers/concerns/web_app_controller_concern.rb +++ b/app/controllers/concerns/web_app_controller_concern.rb @@ -4,15 +4,24 @@ module WebAppControllerConcern extend ActiveSupport::Concern included do - before_action :set_body_classes + before_action :redirect_unauthenticated_to_permalinks! + before_action :set_app_body_class before_action :set_referrer_policy_header end - def set_body_classes + def set_app_body_class @body_classes = 'app-body' end def set_referrer_policy_header response.headers['Referrer-Policy'] = 'origin' end + + def redirect_unauthenticated_to_permalinks! + return if user_signed_in? + + redirect_path = PermalinkRedirector.new(request.path).redirect_path + + redirect_to(redirect_path) if redirect_path.present? + end end diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index da7bb4ed2..e4d8cc495 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -3,6 +3,7 @@ class FollowerAccountsController < ApplicationController include AccountControllerConcern include SignatureVerification + include WebAppControllerConcern before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_cache_headers @@ -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.hide_collections? - - follows end format.json do diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index c37e3b68c..f84dca1e5 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -3,6 +3,7 @@ class FollowingAccountsController < ApplicationController include AccountControllerConcern include SignatureVerification + include WebAppControllerConcern before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_cache_headers @@ -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.hide_collections? - - follows end format.json do diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index b4d6578b9..d8ee82a7a 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -3,21 +3,14 @@ class HomeController < ApplicationController include WebAppControllerConcern - before_action :redirect_unauthenticated_to_permalinks! before_action :set_instance_presenter - def index; end + def index + expires_in 0, public: true unless user_signed_in? + end private - def redirect_unauthenticated_to_permalinks! - return if user_signed_in? - - redirect_path = PermalinkRedirector.new(request.path).redirect_path - - redirect_to(redirect_path) if redirect_path.present? - end - def set_instance_presenter @instance_presenter = InstancePresenter.new end diff --git a/app/controllers/privacy_controller.rb b/app/controllers/privacy_controller.rb index bc98bca51..2c98bf3bf 100644 --- a/app/controllers/privacy_controller.rb +++ b/app/controllers/privacy_controller.rb @@ -5,7 +5,15 @@ class PrivacyController < ApplicationController skip_before_action :require_functional! + before_action :set_instance_presenter + def show expires_in 0, public: true if current_account.nil? end + + private + + def set_instance_presenter + @instance_presenter = InstancePresenter.new + end end diff --git a/app/controllers/remote_follow_controller.rb b/app/controllers/remote_follow_controller.rb deleted file mode 100644 index db1604644..000000000 --- a/app/controllers/remote_follow_controller.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -class RemoteFollowController < ApplicationController - include AccountOwnedConcern - - layout 'modal' - - before_action :set_body_classes - - skip_before_action :require_functional! - - def new - @remote_follow = RemoteFollow.new(session_params) - end - - def create - @remote_follow = RemoteFollow.new(resource_params) - - if @remote_follow.valid? - session[:remote_follow] = @remote_follow.acct - redirect_to @remote_follow.subscribe_address_for(@account) - else - render :new - end - end - - private - - def resource_params - params.require(:remote_follow).permit(:acct) - end - - def session_params - { acct: session[:remote_follow] || current_account&.username } - end - - def set_body_classes - @body_classes = 'modal-layout' - @hide_header = true - end -end diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb deleted file mode 100644 index 6c29a2b9f..000000000 --- a/app/controllers/remote_interaction_controller.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -class RemoteInteractionController < ApplicationController - include Authorization - - layout 'modal' - - before_action :authenticate_user!, if: :whitelist_mode? - before_action :set_interaction_type - before_action :set_status - before_action :set_body_classes - - skip_before_action :require_functional!, unless: :whitelist_mode? - - def new - @remote_follow = RemoteFollow.new(session_params) - end - - def create - @remote_follow = RemoteFollow.new(resource_params) - - if @remote_follow.valid? - session[:remote_follow] = @remote_follow.acct - redirect_to @remote_follow.interact_address_for(@status) - else - render :new - end - end - - private - - def resource_params - params.require(:remote_follow).permit(:acct) - end - - def session_params - { acct: session[:remote_follow] || current_account&.username } - end - - def set_status - @status = Status.find(params[:id]) - authorize @status, :show? - rescue Mastodon::NotPermittedError - not_found - end - - def set_body_classes - @body_classes = 'modal-layout' - @hide_header = true - end - - def set_interaction_type - @interaction_type = %w(reply reblog favourite).include?(params[:type]) ? params[:type] : 'reply' - end -end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 181c76c9a..bb4e5b01f 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class StatusesController < ApplicationController + include WebAppControllerConcern include StatusControllerConcern include SignatureAuthentication include Authorization include AccountOwnedConcern - include WebAppControllerConcern before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_status diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 2890c179d..f0a099350 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -2,18 +2,16 @@ class TagsController < ApplicationController include SignatureVerification + include WebAppControllerConcern PAGE_SIZE = 20 PAGE_SIZE_MAX = 200 - layout 'public' - before_action :require_account_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? @@ -21,7 +19,7 @@ class TagsController < ApplicationController def show respond_to do |format| format.html do - redirect_to web_path("tags/#{@tag.name}") + expires_in 0, public: true unless user_signed_in? end format.rss do @@ -54,10 +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 diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index 59664373d..6301919a9 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -20,54 +20,10 @@ module AccountsHelper end def account_action_button(account) - if user_signed_in? - if account.id == current_user.account_id - link_to settings_profile_url, class: 'button logo-button' do - safe_join([logo_as_symbol, t('settings.edit_profile')]) - end - elsif current_account.following?(account) || current_account.requested?(account) - link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do - safe_join([logo_as_symbol, t('accounts.unfollow')]) - end - elsif !(account.memorial? || account.moved?) - link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do - safe_join([logo_as_symbol, t('accounts.follow')]) - end - end - elsif !(account.memorial? || account.moved?) - link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do - safe_join([logo_as_symbol, t('accounts.follow')]) - end - end - end - - def minimal_account_action_button(account) - if user_signed_in? - return if account.id == current_user.account_id - - if current_account.following?(account) || current_account.requested?(account) - link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do - fa_icon('user-times fw') - end - elsif !(account.memorial? || account.moved?) - link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do - fa_icon('user-plus fw') - end - end - elsif !(account.memorial? || account.moved?) - link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do - fa_icon('user-plus fw') - end - end - end + return if account.memorial? || account.moved? - def account_badge(account) - if account.bot? - content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles') - elsif account.group? - content_tag(:div, content_tag(:div, t('accounts.roles.group'), class: 'account-role group'), class: 'roles') - elsif account.user_role&.highlighted? - content_tag(:div, content_tag(:div, account.user_role.name, class: "account-role user-role-#{account.user_role.id}"), class: 'roles') + link_to ActivityPub::TagManager.instance.url_for(account), class: 'button logo-button', target: '_new' do + safe_join([logo_as_symbol, t('accounts.follow')]) end end diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js index ca4a2cfe1..02d5616d6 100644 --- a/app/javascript/mastodon/components/error_boundary.js +++ b/app/javascript/mastodon/components/error_boundary.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { version, source_url } from 'mastodon/initial_state'; import StackTrace from 'stacktrace-js'; +import { Helmet } from 'react-helmet'; export default class ErrorBoundary extends React.PureComponent { @@ -84,6 +85,7 @@ export default class ErrorBoundary extends React.PureComponent { )}

+

{ likelyBrowserAddonIssue ? ( @@ -91,8 +93,13 @@ export default class ErrorBoundary extends React.PureComponent { )}

+

Mastodon v{version} · ·

+ + + + ); } diff --git a/app/javascript/mastodon/components/missing_indicator.js b/app/javascript/mastodon/components/missing_indicator.js index 7b0101bab..05e0d653d 100644 --- a/app/javascript/mastodon/components/missing_indicator.js +++ b/app/javascript/mastodon/components/missing_indicator.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import illustration from 'mastodon/../images/elephant_ui_disappointed.svg'; import classNames from 'classnames'; +import { Helmet } from 'react-helmet'; const MissingIndicator = ({ fullPage }) => (
@@ -14,6 +15,10 @@ const MissingIndicator = ({ fullPage }) => (
+ + + + ); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 8e5a1fa3a..730695c49 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -78,7 +78,7 @@ export default class Mastodon extends React.PureComponent { - + diff --git a/app/javascript/mastodon/features/about/index.js b/app/javascript/mastodon/features/about/index.js index e9212565a..75fed9b95 100644 --- a/app/javascript/mastodon/features/about/index.js +++ b/app/javascript/mastodon/features/about/index.js @@ -94,6 +94,7 @@ class About extends React.PureComponent { }), dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, }; componentDidMount () { @@ -108,11 +109,11 @@ class About extends React.PureComponent { } render () { - const { intl, server, extendedDescription, domainBlocks } = this.props; + const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props; const isLoading = server.get('isLoading'); return ( - +
`${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' /> @@ -212,6 +213,7 @@ class About extends React.PureComponent { {intl.formatMessage(messages.title)} + ); diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 44c53f9ce..954cb0ee7 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -270,7 +270,9 @@ class Header extends ImmutablePureComponent { const content = { __html: account.get('note_emojified') }; const displayNameHtml = { __html: account.get('display_name_html') }; const fields = account.get('fields'); - const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + const isLocal = account.get('acct').indexOf('@') === -1; + const acct = isLocal && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + const isIndexable = !account.get('noindex'); let badge; @@ -373,6 +375,7 @@ class Header extends ImmutablePureComponent { {titleFromAccount(account)} +
); diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index 51fb76f1f..437cee95c 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -142,19 +142,17 @@ class AccountTimeline extends ImmutablePureComponent { render () { const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props; - if (!isAccount) { + if (isLoading && statusIds.isEmpty()) { return ( - - + ); - } - - if (!statusIds && isLoading) { + } else if (!isLoading && !isAccount) { return ( - + + ); } diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.js b/app/javascript/mastodon/features/bookmarked_statuses/index.js index 0e466e5ed..097be17c9 100644 --- a/app/javascript/mastodon/features/bookmarked_statuses/index.js +++ b/app/javascript/mastodon/features/bookmarked_statuses/index.js @@ -99,6 +99,7 @@ class Bookmarks extends ImmutablePureComponent { {intl.formatMessage(messages.heading)} + ); diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 757521802..7b3f8845f 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -151,6 +151,7 @@ class CommunityTimeline extends React.PureComponent { {intl.formatMessage(messages.title)} + ); diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index c27556a0e..763c715de 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -18,6 +18,7 @@ import { mascot } from '../../initial_state'; import Icon from 'mastodon/components/icon'; import { logOut } from 'mastodon/utils/log_out'; import Column from 'mastodon/components/column'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -145,6 +146,10 @@ class Compose extends React.PureComponent { + + + + ); } diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js index cfaa9c4c5..8dcc43e28 100644 --- a/app/javascript/mastodon/features/direct_timeline/index.js +++ b/app/javascript/mastodon/features/direct_timeline/index.js @@ -98,6 +98,7 @@ class DirectTimeline extends React.PureComponent { {intl.formatMessage(messages.title)} + ); diff --git a/app/javascript/mastodon/features/directory/index.js b/app/javascript/mastodon/features/directory/index.js index 0ce7919b6..b45faa049 100644 --- a/app/javascript/mastodon/features/directory/index.js +++ b/app/javascript/mastodon/features/directory/index.js @@ -169,6 +169,7 @@ class Directory extends React.PureComponent { {intl.formatMessage(messages.title)} + ); diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js index edb80aef4..43b275c2d 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.js +++ b/app/javascript/mastodon/features/domain_blocks/index.js @@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import DomainContainer from '../../containers/domain_container'; import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; import ScrollableList from '../../components/scrollable_list'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' }, @@ -59,6 +60,7 @@ class Blocks extends ImmutablePureComponent { return ( + , )} + + + + ); } diff --git a/app/javascript/mastodon/features/explore/index.js b/app/javascript/mastodon/features/explore/index.js index 566be631e..1c7049e97 100644 --- a/app/javascript/mastodon/features/explore/index.js +++ b/app/javascript/mastodon/features/explore/index.js @@ -84,6 +84,7 @@ class Explore extends React.PureComponent { {intl.formatMessage(messages.title)} + )} diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index f1d32eff1..3741f68f6 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -99,6 +99,7 @@ class Favourites extends ImmutablePureComponent { {intl.formatMessage(messages.heading)} + ); diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js index 673317f04..ad10744da 100644 --- a/app/javascript/mastodon/features/favourites/index.js +++ b/app/javascript/mastodon/features/favourites/index.js @@ -11,6 +11,7 @@ import LoadingIndicator from 'mastodon/components/loading_indicator'; import ScrollableList from 'mastodon/components/scrollable_list'; import AccountContainer from 'mastodon/containers/account_container'; import Column from 'mastodon/features/ui/components/column'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ refresh: { id: 'refresh', defaultMessage: 'Refresh' }, @@ -80,6 +81,10 @@ class Favourites extends ImmutablePureComponent { , )} + + + + ); } diff --git a/app/javascript/mastodon/features/follow_recommendations/index.js b/app/javascript/mastodon/features/follow_recommendations/index.js index 32b55eeb3..5f7baa64c 100644 --- a/app/javascript/mastodon/features/follow_recommendations/index.js +++ b/app/javascript/mastodon/features/follow_recommendations/index.js @@ -12,6 +12,7 @@ import Column from 'mastodon/features/ui/components/column'; import Account from './components/account'; import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg'; import Button from 'mastodon/components/button'; +import { Helmet } from 'react-helmet'; const mapStateToProps = state => ({ suggestions: state.getIn(['suggestions', 'items']), @@ -104,6 +105,10 @@ class FollowRecommendations extends ImmutablePureComponent { )}
+ + + +
); } diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index 1f9b635bb..d16aa7737 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -12,6 +12,7 @@ import AccountAuthorizeContainer from './containers/account_authorize_container' import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import ScrollableList from '../../components/scrollable_list'; import { me } from '../../initial_state'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }, @@ -87,6 +88,10 @@ class FollowRequests extends ImmutablePureComponent { , )} + + + +
); } diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 42a5b581f..f002ffc77 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -138,6 +138,7 @@ class GettingStarted extends ImmutablePureComponent { {intl.formatMessage(messages.menu)} + ); diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index 0f7df5036..ec524be8f 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -228,6 +228,7 @@ class HashtagTimeline extends React.PureComponent { #{id} + ); diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index 68770b739..838ed7dd8 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -20,7 +20,7 @@ const messages = defineMessages({ title: { id: 'column.home', defaultMessage: 'Home' }, show_announcements: { id: 'home.show_announcements', defaultMessage: 'Show announcements' }, hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' }, -}); +}); const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, @@ -167,6 +167,7 @@ class HomeTimeline extends React.PureComponent { {intl.formatMessage(messages.title)} + ); diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js index 2a32577ba..9a870478d 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js @@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ColumnHeader from 'mastodon/components/column_header'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, @@ -164,6 +165,10 @@ class KeyboardShortcuts extends ImmutablePureComponent { + + + + ); } diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js index f0a7a0c7f..fd9d33df7 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -212,6 +212,7 @@ class ListTimeline extends React.PureComponent { {title} + ); diff --git a/app/javascript/mastodon/features/lists/index.js b/app/javascript/mastodon/features/lists/index.js index 389a0c5c8..017595ba0 100644 --- a/app/javascript/mastodon/features/lists/index.js +++ b/app/javascript/mastodon/features/lists/index.js @@ -80,6 +80,7 @@ class Lists extends ImmutablePureComponent { {intl.formatMessage(messages.heading)} + ); diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js index c21433cc4..65df6149f 100644 --- a/app/javascript/mastodon/features/mutes/index.js +++ b/app/javascript/mastodon/features/mutes/index.js @@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountContainer from '../../containers/account_container'; import { fetchMutes, expandMutes } from '../../actions/mutes'; import ScrollableList from '../../components/scrollable_list'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.mutes', defaultMessage: 'Muted users' }, @@ -72,6 +73,10 @@ class Mutes extends ImmutablePureComponent { , )} + + + + ); } diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index 4577bcb2d..f1bc5f160 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -281,6 +281,7 @@ class Notifications extends React.PureComponent { {intl.formatMessage(messages.title)} + ); diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.js index 67b13f10a..c6790ea06 100644 --- a/app/javascript/mastodon/features/pinned_statuses/index.js +++ b/app/javascript/mastodon/features/pinned_statuses/index.js @@ -8,6 +8,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import StatusList from '../../components/status_list'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.pins', defaultMessage: 'Pinned post' }, @@ -54,6 +55,9 @@ class PinnedStatuses extends ImmutablePureComponent { hasMore={hasMore} bindToDocument={!multiColumn} /> + + + ); } diff --git a/app/javascript/mastodon/features/privacy_policy/index.js b/app/javascript/mastodon/features/privacy_policy/index.js index eee4255f4..3df487e8f 100644 --- a/app/javascript/mastodon/features/privacy_policy/index.js +++ b/app/javascript/mastodon/features/privacy_policy/index.js @@ -15,6 +15,7 @@ class PrivacyPolicy extends React.PureComponent { static propTypes = { intl: PropTypes.object, + multiColumn: PropTypes.bool, }; state = { @@ -32,11 +33,11 @@ class PrivacyPolicy extends React.PureComponent { } render () { - const { intl } = this.props; + const { intl, multiColumn } = this.props; const { isLoading, content, lastUpdated } = this.state; return ( - +

@@ -51,6 +52,7 @@ class PrivacyPolicy extends React.PureComponent { {intl.formatMessage(messages.title)} + ); diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 8dbef98c0..a41be07e1 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -153,6 +153,7 @@ class PublicTimeline extends React.PureComponent { {intl.formatMessage(messages.title)} + ); diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js index 7704a049f..70d338ef1 100644 --- a/app/javascript/mastodon/features/reblogs/index.js +++ b/app/javascript/mastodon/features/reblogs/index.js @@ -11,6 +11,7 @@ import Column from '../ui/components/column'; import ScrollableList from '../../components/scrollable_list'; import Icon from 'mastodon/components/icon'; import ColumnHeader from '../../components/column_header'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ refresh: { id: 'refresh', defaultMessage: 'Refresh' }, @@ -80,6 +81,10 @@ class Reblogs extends ImmutablePureComponent { , )} + + + + ); } diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index f9a97c9b5..02f390c6a 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -7,6 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { createSelector } from 'reselect'; import { fetchStatus } from '../../actions/statuses'; import MissingIndicator from '../../components/missing_indicator'; +import LoadingIndicator from 'mastodon/components/loading_indicator'; import DetailedStatus from './components/detailed_status'; import ActionBar from './components/action_bar'; import Column from '../ui/components/column'; @@ -145,6 +146,7 @@ const makeMapStateToProps = () => { } return { + isLoading: state.getIn(['statuses', props.params.statusId, 'isLoading']), status, ancestorsIds, descendantsIds, @@ -187,6 +189,7 @@ class Status extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, status: ImmutablePropTypes.map, + isLoading: PropTypes.bool, ancestorsIds: ImmutablePropTypes.list, descendantsIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, @@ -566,9 +569,17 @@ class Status extends ImmutablePureComponent { render () { let ancestors, descendants; - const { status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props; + const { isLoading, status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props; const { fullscreen } = this.state; + if (isLoading) { + return ( + + + + ); + } + if (status === null) { return ( @@ -586,6 +597,9 @@ class Status extends ImmutablePureComponent { descendants =
{this.renderChildren(descendantsIds)}
; } + const isLocal = status.getIn(['account', 'acct'], '').indexOf('@') === -1; + const isIndexable = !status.getIn(['account', 'noindex']); + const handlers = { moveUp: this.handleHotkeyMoveUp, moveDown: this.handleHotkeyMoveDown, @@ -659,6 +673,7 @@ class Status extends ImmutablePureComponent { {titleFromStatus(status)} +
); diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.js b/app/javascript/mastodon/features/ui/components/bundle_column_error.js index f39ebd900..ab6d4aa44 100644 --- a/app/javascript/mastodon/features/ui/components/bundle_column_error.js +++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.js @@ -1,11 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; - -import Column from './column'; -import ColumnHeader from './column_header'; -import ColumnBackButtonSlim from '../../../components/column_back_button_slim'; -import IconButton from '../../../components/icon_button'; +import Column from 'mastodon/components/column'; +import ColumnHeader from 'mastodon/components/column_header'; +import IconButton from 'mastodon/components/icon_button'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' }, @@ -18,6 +17,7 @@ class BundleColumnError extends React.PureComponent { static propTypes = { onRetry: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, } handleRetry = () => { @@ -25,16 +25,25 @@ class BundleColumnError extends React.PureComponent { } render () { - const { intl: { formatMessage } } = this.props; + const { multiColumn, intl: { formatMessage } } = this.props; return ( - - - + + +
{formatMessage(messages.body)}
+ + + +
); } diff --git a/app/javascript/mastodon/features/ui/components/column_loading.js b/app/javascript/mastodon/features/ui/components/column_loading.js index 0cdfd05d8..e5ed22584 100644 --- a/app/javascript/mastodon/features/ui/components/column_loading.js +++ b/app/javascript/mastodon/features/ui/components/column_loading.js @@ -10,6 +10,7 @@ export default class ColumnLoading extends ImmutablePureComponent { static propTypes = { title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), icon: PropTypes.string, + multiColumn: PropTypes.bool, }; static defaultProps = { @@ -18,10 +19,11 @@ export default class ColumnLoading extends ImmutablePureComponent { }; render() { - let { title, icon } = this.props; + let { title, icon, multiColumn } = this.props; + return ( - +
); diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index cc1bc83e0..9ee6fca43 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -139,11 +139,11 @@ class ColumnsArea extends ImmutablePureComponent { } renderLoading = columnId => () => { - return columnId === 'COMPOSE' ? : ; + return columnId === 'COMPOSE' ? : ; } renderError = (props) => { - return ; + return ; } render () { diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index 5c273ffa4..2224a8207 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -11,9 +11,7 @@ import VideoModal from './video_modal'; import BoostModal from './boost_modal'; import AudioModal from './audio_modal'; import ConfirmationModal from './confirmation_modal'; -import SubscribedLanguagesModal from 'mastodon/features/subscribed_languages_modal'; import FocalPointModal from './focal_point_modal'; -import InteractionModal from 'mastodon/features/interaction_modal'; import { MuteModal, BlockModal, @@ -23,7 +21,10 @@ import { ListAdder, CompareHistoryModal, FilterModal, + InteractionModal, + SubscribedLanguagesModal, } from 'mastodon/features/ui/util/async-components'; +import { Helmet } from 'react-helmet'; const MODAL_COMPONENTS = { 'MEDIA': () => Promise.resolve({ default: MediaModal }), @@ -41,8 +42,8 @@ const MODAL_COMPONENTS = { 'LIST_ADDER': ListAdder, 'COMPARE_HISTORY': CompareHistoryModal, 'FILTER': FilterModal, - 'SUBSCRIBED_LANGUAGES': () => Promise.resolve({ default: SubscribedLanguagesModal }), - 'INTERACTION': () => Promise.resolve({ default: InteractionModal }), + 'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal, + 'INTERACTION': InteractionModal, }; export default class ModalRoot extends React.PureComponent { @@ -111,9 +112,15 @@ export default class ModalRoot extends React.PureComponent { return ( {visible && ( - - {(SpecificComponent) => } - + <> + + {(SpecificComponent) => } + + + + + + )} ); diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 8f9f38036..003991857 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -197,8 +197,8 @@ class SwitchingColumnsArea extends React.PureComponent { - - + + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index c79dc014c..7686a69ea 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -166,6 +166,14 @@ export function FilterModal () { return import(/*webpackChunkName: "modals/filter_modal" */'../components/filter_modal'); } +export function InteractionModal () { + return import(/*webpackChunkName: "modals/interaction_modal" */'../../interaction_modal'); +} + +export function SubscribedLanguagesModal () { + return import(/*webpackChunkName: "modals/subscribed_languages_modal" */'../../subscribed_languages_modal'); +} + export function About () { return import(/*webpackChunkName: "features/about" */'../../about'); } diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js index d452b871f..a65d79def 100644 --- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js +++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js @@ -53,7 +53,9 @@ export class WrappedRoute extends React.Component { } renderLoading = () => { - return ; + const { multiColumn } = this.props; + + return ; } renderError = (props) => { diff --git a/app/javascript/mastodon/main.js b/app/javascript/mastodon/main.js index f33375b50..d0337ce0c 100644 --- a/app/javascript/mastodon/main.js +++ b/app/javascript/mastodon/main.js @@ -12,14 +12,6 @@ const perf = require('mastodon/performance'); function main() { perf.start('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}`); - } - } - return ready(async () => { const mountNode = document.getElementById('mastodon'); const props = JSON.parse(mountNode.getAttribute('data-props')); diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 7efb49d85..c30c1e2cc 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -15,6 +15,8 @@ import { STATUS_COLLAPSE, STATUS_TRANSLATE_SUCCESS, STATUS_TRANSLATE_UNDO, + STATUS_FETCH_REQUEST, + STATUS_FETCH_FAIL, } from '../actions/statuses'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; @@ -37,6 +39,10 @@ const initialState = ImmutableMap(); export default function statuses(state = initialState, action) { switch(action.type) { + case STATUS_FETCH_REQUEST: + return state.setIn([action.id, 'isLoading'], true); + case STATUS_FETCH_FAIL: + return state.delete(action.id); case STATUS_IMPORT: return importStatus(state, action.status); case STATUSES_IMPORT: diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 3dd7f4897..bf46c810e 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -41,7 +41,7 @@ export const makeGetStatus = () => { ], (statusBase, statusReblog, accountBase, accountReblog, filters) => { - if (!statusBase) { + if (!statusBase || statusBase.get('isLoading')) { return null; } diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js index 9b75e9b9d..f12595777 100644 --- a/app/javascript/mastodon/service_worker/web_push_notifications.js +++ b/app/javascript/mastodon/service_worker/web_push_notifications.js @@ -15,7 +15,7 @@ const notify = options => icon: '/android-chrome-192x192.png', tag: GROUP_TAG, data: { - url: (new URL('/web/notifications', self.location)).href, + url: (new URL('/notifications', self.location)).href, count: notifications.length + 1, preferred_locale: options.data.preferred_locale, }, @@ -90,7 +90,7 @@ export 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/@${notification.account.acct}/${notification.status.id}` : `/web/@${notification.account.acct}` }; + 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); @@ -115,7 +115,7 @@ export const handlePush = (event) => { tag: notification_id, timestamp: new Date(), badge: '/badge.png', - data: { access_token, preferred_locale, url: '/web/notifications' }, + data: { access_token, preferred_locale, url: '/notifications' }, }); }), ); @@ -166,24 +166,10 @@ const removeActionFromNotification = (notification, action) => { const openUrl = url => self.clients.matchAll({ type: 'window' }).then(clientList => { - if (clientList.length !== 0) { - const webClients = clientList.filter(client => /\/web\//.test(client.url)); - - if (webClients.length !== 0) { - const client = findBestClient(webClients); - const { pathname } = new URL(url, self.location); - - if (pathname.startsWith('/web/')) { - return client.focus().then(client => client.postMessage({ - type: 'navigate', - path: pathname.slice('/web/'.length - 1), - })); - } - } else if ('navigate' in clientList[0]) { // Chrome 42-48 does not support navigate - const client = findBestClient(clientList); + if (clientList.length !== 0 && 'navigate' in clientList[0]) { // Chrome 42-48 does not support navigate + const client = findBestClient(clientList); - return client.navigate(url).then(client => client.focus()); - } + return client.navigate(url).then(client => client.focus()); } return self.clients.openWindow(url); diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index e42468e0c..5ff45fa55 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -33,7 +33,6 @@ function main() { const { messages } = getLocale(); const React = require('react'); const ReactDOM = require('react-dom'); - const Rellax = require('rellax'); const { createBrowserHistory } = require('history'); const scrollToDetailedStatus = () => { @@ -112,12 +111,6 @@ function main() { scrollToDetailedStatus(); } - const parallaxComponents = document.querySelectorAll('.parallax'); - - if (parallaxComponents.length > 0 ) { - new Rellax('.parallax', { speed: -1 }); - } - delegate(document, '#registration_user_password_confirmation,#registration_user_password', 'input', () => { const password = document.getElementById('registration_user_password'); const confirmation = document.getElementById('registration_user_password_confirmation'); @@ -168,28 +161,6 @@ function main() { }); }); - delegate(document, '.webapp-btn', 'click', ({ target, button }) => { - if (button !== 0) { - return true; - } - window.location.href = target.href; - return false; - }); - - delegate(document, '.modal-button', 'click', e => { - e.preventDefault(); - - let href; - - if (e.target.nodeName !== 'A') { - href = e.target.parentNode.href; - } else { - href = e.target.href; - } - - window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); - }); - delegate(document, '#account_display_name', 'input', ({ target }) => { const name = document.querySelector('.card .display-name strong'); if (name) { diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss index e9f596e2f..81a040108 100644 --- a/app/javascript/styles/application.scss +++ b/app/javascript/styles/application.scss @@ -8,7 +8,6 @@ @import 'mastodon/branding'; @import 'mastodon/containers'; @import 'mastodon/lists'; -@import 'mastodon/footer'; @import 'mastodon/widgets'; @import 'mastodon/forms'; @import 'mastodon/accounts'; diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss index 22f5bcc94..27eb837df 100644 --- a/app/javascript/styles/contrast/diff.scss +++ b/app/javascript/styles/contrast/diff.scss @@ -68,10 +68,6 @@ color: $darker-text-color; } -.public-layout .public-account-header__tabs__tabs .counter.active::after { - border-bottom: 4px solid $ui-highlight-color; -} - .compose-form .autosuggest-textarea__textarea::placeholder, .compose-form .spoiler-input__input::placeholder { color: $inverted-text-color; diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 4b27e6b4f..20e973b8b 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -655,95 +655,6 @@ html { } } -.public-layout { - .account__section-headline { - border: 1px solid lighten($ui-base-color, 8%); - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 0; - } - } - - .header, - .public-account-header, - .public-account-bio { - box-shadow: none; - } - - .public-account-bio, - .hero-widget__text { - background: $account-background-color; - } - - .header { - background: $ui-base-color; - border: 1px solid lighten($ui-base-color, 8%); - - @media screen and (max-width: $no-gap-breakpoint) { - border: 0; - } - - .brand { - &:hover, - &:focus, - &:active { - background: lighten($ui-base-color, 4%); - } - } - } - - .public-account-header { - &__image { - background: lighten($ui-base-color, 12%); - - &::after { - box-shadow: none; - } - } - - &__bar { - &::before { - background: $account-background-color; - border: 1px solid lighten($ui-base-color, 8%); - border-top: 0; - } - - .avatar img { - border-color: $account-background-color; - } - - @media screen and (max-width: $no-columns-breakpoint) { - background: $account-background-color; - border: 1px solid lighten($ui-base-color, 8%); - border-top: 0; - } - } - - &__tabs { - &__name { - h1, - h1 small { - color: $white; - - @media screen and (max-width: $no-columns-breakpoint) { - color: $primary-text-color; - } - } - } - } - - &__extra { - .public-account-bio { - border: 0; - } - - .public-account-bio .account__header__fields { - border-color: lighten($ui-base-color, 8%); - } - } - } -} - .notification__filter-bar button.active::after, .account__section-headline a.active::after { border-color: transparent transparent $white; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 8e5ed03f0..b49b93984 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -104,785 +104,3 @@ margin-left: 10px; } } - -.grid-3 { - display: grid; - grid-gap: 10px; - grid-template-columns: 3fr 1fr; - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - grid-column: 1 / 3; - grid-row: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 2; - } - - .column-2 { - grid-column: 2; - grid-row: 2; - } - - .column-3 { - grid-column: 1 / 3; - grid-row: 3; - } - - @media screen and (max-width: $no-gap-breakpoint) { - grid-gap: 0; - grid-template-columns: minmax(0, 100%); - - .column-0 { - grid-column: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 3; - } - - .column-2 { - grid-column: 1; - grid-row: 2; - } - - .column-3 { - grid-column: 1; - grid-row: 4; - } - } -} - -.grid-4 { - display: grid; - grid-gap: 10px; - grid-template-columns: repeat(4, minmax(0, 1fr)); - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - grid-column: 1 / 5; - grid-row: 1; - } - - .column-1 { - grid-column: 1 / 4; - grid-row: 2; - } - - .column-2 { - grid-column: 4; - grid-row: 2; - } - - .column-3 { - grid-column: 2 / 5; - grid-row: 3; - } - - .column-4 { - grid-column: 1; - grid-row: 3; - } - - .landing-page__call-to-action { - min-height: 100%; - } - - .flash-message { - margin-bottom: 10px; - } - - @media screen and (max-width: 738px) { - grid-template-columns: minmax(0, 50%) minmax(0, 50%); - - .landing-page__call-to-action { - padding: 20px; - display: flex; - align-items: center; - justify-content: center; - } - - .row__information-board { - width: 100%; - justify-content: center; - align-items: center; - } - - .row__mascot { - display: none; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - grid-gap: 0; - grid-template-columns: minmax(0, 100%); - - .column-0 { - grid-column: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 3; - } - - .column-2 { - grid-column: 1; - grid-row: 2; - } - - .column-3 { - grid-column: 1; - grid-row: 5; - } - - .column-4 { - grid-column: 1; - grid-row: 4; - } - } -} - -.public-layout { - @media screen and (max-width: $no-gap-breakpoint) { - padding-top: 48px; - } - - .container { - max-width: 960px; - - @media screen and (max-width: $no-gap-breakpoint) { - padding: 0; - } - } - - .header { - background: lighten($ui-base-color, 8%); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - height: 48px; - margin: 10px 0; - display: flex; - align-items: stretch; - justify-content: center; - flex-wrap: nowrap; - overflow: hidden; - - @media screen and (max-width: $no-gap-breakpoint) { - position: fixed; - width: 100%; - top: 0; - left: 0; - margin: 0; - border-radius: 0; - box-shadow: none; - z-index: 110; - } - - & > div { - flex: 1 1 33.3%; - min-height: 1px; - } - - .nav-left { - display: flex; - align-items: stretch; - justify-content: flex-start; - flex-wrap: nowrap; - } - - .nav-center { - display: flex; - align-items: stretch; - justify-content: center; - flex-wrap: nowrap; - } - - .nav-right { - display: flex; - align-items: stretch; - justify-content: flex-end; - flex-wrap: nowrap; - } - - .brand { - display: block; - padding: 15px; - - .logo { - display: block; - height: 18px; - width: auto; - position: relative; - bottom: -2px; - fill: $primary-text-color; - - @media screen and (max-width: $no-gap-breakpoint) { - height: 20px; - } - } - - &:hover, - &:focus, - &:active { - background: lighten($ui-base-color, 12%); - } - } - - .nav-link { - display: flex; - align-items: center; - padding: 0 1rem; - font-size: 12px; - font-weight: 500; - text-decoration: none; - color: $darker-text-color; - white-space: nowrap; - text-align: center; - - &:hover, - &:focus, - &:active { - text-decoration: underline; - color: $primary-text-color; - } - - @media screen and (max-width: 550px) { - &.optional { - display: none; - } - } - } - - .nav-button { - background: lighten($ui-base-color, 16%); - margin: 8px; - margin-left: 0; - border-radius: 4px; - - &:hover, - &:focus, - &:active { - text-decoration: none; - background: lighten($ui-base-color, 20%); - } - } - } - - $no-columns-breakpoint: 600px; - - .grid { - display: grid; - grid-gap: 10px; - grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr); - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - grid-row: 1; - grid-column: 1; - } - - .column-1 { - grid-row: 1; - grid-column: 2; - } - - @media screen and (max-width: $no-columns-breakpoint) { - grid-template-columns: 100%; - grid-gap: 0; - - .column-1 { - display: none; - } - } - } - - .page-header { - @media screen and (max-width: $no-gap-breakpoint) { - border-bottom: 0; - } - } - - .public-account-header { - overflow: hidden; - margin-bottom: 10px; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - - &.inactive { - opacity: 0.5; - - .public-account-header__image, - .avatar { - filter: grayscale(100%); - } - - .logo-button { - background-color: $secondary-text-color; - } - } - - .logo-button { - padding: 3px 15px; - } - - &__image { - border-radius: 4px 4px 0 0; - overflow: hidden; - height: 300px; - position: relative; - background: darken($ui-base-color, 12%); - - &::after { - content: ""; - display: block; - position: absolute; - width: 100%; - height: 100%; - box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15); - top: 0; - left: 0; - } - - img { - object-fit: cover; - display: block; - width: 100%; - height: 100%; - margin: 0; - border-radius: 4px 4px 0 0; - } - - @media screen and (max-width: 600px) { - height: 200px; - } - } - - &--no-bar { - margin-bottom: 0; - - .public-account-header__image, - .public-account-header__image img { - border-radius: 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin-bottom: 0; - box-shadow: none; - - &__image::after { - display: none; - } - - &__image, - &__image img { - border-radius: 0; - } - } - - &__bar { - position: relative; - margin-top: -80px; - display: flex; - justify-content: flex-start; - - &::before { - content: ""; - display: block; - background: lighten($ui-base-color, 4%); - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 60px; - border-radius: 0 0 4px 4px; - z-index: -1; - } - - .avatar { - display: block; - width: 120px; - height: 120px; - padding-left: 20px - 4px; - flex: 0 0 auto; - - img { - display: block; - width: 100%; - height: 100%; - margin: 0; - border-radius: 50%; - border: 4px solid lighten($ui-base-color, 4%); - background: darken($ui-base-color, 8%); - } - } - - @media screen and (max-width: 600px) { - margin-top: 0; - background: lighten($ui-base-color, 4%); - border-radius: 0 0 4px 4px; - padding: 5px; - - &::before { - display: none; - } - - .avatar { - width: 48px; - height: 48px; - padding: 7px 0; - padding-left: 10px; - - img { - border: 0; - border-radius: 4px; - } - - @media screen and (max-width: 360px) { - display: none; - } - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - - @media screen and (max-width: $no-columns-breakpoint) { - flex-wrap: wrap; - } - } - - &__tabs { - flex: 1 1 auto; - margin-left: 20px; - - &__name { - padding-top: 20px; - padding-bottom: 8px; - - h1 { - font-size: 20px; - line-height: 18px * 1.5; - color: $primary-text-color; - font-weight: 500; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - text-shadow: 1px 1px 1px $base-shadow-color; - - small { - display: block; - font-size: 14px; - color: $primary-text-color; - font-weight: 400; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - - @media screen and (max-width: 600px) { - margin-left: 15px; - display: flex; - justify-content: space-between; - align-items: center; - - &__name { - padding-top: 0; - padding-bottom: 0; - - h1 { - font-size: 16px; - line-height: 24px; - text-shadow: none; - - small { - color: $darker-text-color; - } - } - } - } - - &__tabs { - display: flex; - justify-content: flex-start; - align-items: stretch; - height: 58px; - - .details-counters { - display: flex; - flex-direction: row; - min-width: 300px; - } - - @media screen and (max-width: $no-columns-breakpoint) { - .details-counters { - display: none; - } - } - - .counter { - min-width: 33.3%; - box-sizing: border-box; - flex: 0 0 auto; - color: $darker-text-color; - padding: 10px; - border-right: 1px solid lighten($ui-base-color, 4%); - cursor: default; - text-align: center; - position: relative; - - a { - display: block; - } - - &:last-child { - border-right: 0; - } - - &::after { - display: block; - content: ""; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - border-bottom: 4px solid $ui-primary-color; - opacity: 0.5; - transition: all 400ms ease; - } - - &.active { - &::after { - border-bottom: 4px solid $highlight-text-color; - opacity: 1; - } - - &.inactive::after { - border-bottom-color: $secondary-text-color; - } - } - - &:hover { - &::after { - opacity: 1; - transition-duration: 100ms; - } - } - - a { - text-decoration: none; - color: inherit; - } - - .counter-label { - font-size: 12px; - display: block; - } - - .counter-number { - font-weight: 500; - font-size: 18px; - margin-bottom: 5px; - color: $primary-text-color; - font-family: $font-display, sans-serif; - } - } - - .spacer { - flex: 1 1 auto; - height: 1px; - } - - &__buttons { - padding: 7px 8px; - } - } - } - - &__extra { - display: none; - margin-top: 4px; - - .public-account-bio { - border-radius: 0; - box-shadow: none; - background: transparent; - margin: 0 -5px; - - .account__header__fields { - border-top: 1px solid lighten($ui-base-color, 12%); - } - - .roles { - display: none; - } - } - - &__links { - margin-top: -15px; - font-size: 14px; - color: $darker-text-color; - - a { - display: inline-block; - color: $darker-text-color; - text-decoration: none; - padding: 15px; - font-weight: 500; - - strong { - font-weight: 700; - color: $primary-text-color; - } - } - } - - @media screen and (max-width: $no-columns-breakpoint) { - display: block; - flex: 100%; - } - } - } - - .account__section-headline { - border-radius: 4px 4px 0 0; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - } - - .detailed-status__meta { - margin-top: 25px; - } - - .public-account-bio { - background: lighten($ui-base-color, 8%); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - overflow: hidden; - margin-bottom: 10px; - - @media screen and (max-width: $no-gap-breakpoint) { - box-shadow: none; - margin-bottom: 0; - border-radius: 0; - } - - .account__header__fields { - margin: 0; - border-top: 0; - - a { - color: $highlight-text-color; - } - - dl:first-child .verified { - border-radius: 0 4px 0 0; - } - - .verified a { - color: $valid-value-color; - } - } - - .account__header__content { - padding: 20px; - padding-bottom: 0; - color: $primary-text-color; - } - - &__extra, - .roles { - padding: 20px; - font-size: 14px; - color: $darker-text-color; - } - - .roles { - padding-bottom: 0; - } - } - - .directory__list { - display: grid; - grid-gap: 10px; - grid-template-columns: minmax(0, 50%) minmax(0, 50%); - - .account-card { - display: flex; - flex-direction: column; - } - - @media screen and (max-width: $no-gap-breakpoint) { - display: block; - - .account-card { - margin-bottom: 10px; - display: block; - } - } - } - - .card-grid { - display: flex; - flex-wrap: wrap; - min-width: 100%; - margin: 0 -5px; - - & > div { - box-sizing: border-box; - flex: 1 0 auto; - width: 300px; - padding: 0 5px; - margin-bottom: 10px; - max-width: 33.333%; - - @media screen and (max-width: 900px) { - max-width: 50%; - } - - @media screen and (max-width: 600px) { - max-width: 100%; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin: 0; - border-top: 1px solid lighten($ui-base-color, 8%); - - & > div { - width: 100%; - padding: 0; - margin-bottom: 0; - border-bottom: 1px solid lighten($ui-base-color, 8%); - - &:last-child { - border-bottom: 0; - } - - .card__bar { - background: $ui-base-color; - - &:hover, - &:active, - &:focus { - background: lighten($ui-base-color, 4%); - } - } - } - } - } -} diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss deleted file mode 100644 index 0c3e42033..000000000 --- a/app/javascript/styles/mastodon/footer.scss +++ /dev/null @@ -1,152 +0,0 @@ -.public-layout { - .footer { - text-align: left; - padding-top: 20px; - padding-bottom: 60px; - font-size: 12px; - color: lighten($ui-base-color, 34%); - - @media screen and (max-width: $no-gap-breakpoint) { - padding-left: 20px; - padding-right: 20px; - } - - .grid { - display: grid; - grid-gap: 10px; - grid-template-columns: 1fr 1fr 2fr 1fr 1fr; - - .column-0 { - grid-column: 1; - grid-row: 1; - min-width: 0; - } - - .column-1 { - grid-column: 2; - grid-row: 1; - min-width: 0; - } - - .column-2 { - grid-column: 3; - grid-row: 1; - min-width: 0; - text-align: center; - - h4 a { - color: lighten($ui-base-color, 34%); - } - } - - .column-3 { - grid-column: 4; - grid-row: 1; - min-width: 0; - } - - .column-4 { - grid-column: 5; - grid-row: 1; - min-width: 0; - } - - @media screen and (max-width: 690px) { - grid-template-columns: 1fr 2fr 1fr; - - .column-0, - .column-1 { - grid-column: 1; - } - - .column-1 { - grid-row: 2; - } - - .column-2 { - grid-column: 2; - } - - .column-3, - .column-4 { - grid-column: 3; - } - - .column-4 { - grid-row: 2; - } - } - - @media screen and (max-width: 600px) { - .column-1 { - display: block; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - .column-0, - .column-1, - .column-3, - .column-4 { - display: none; - } - - .column-2 h4 { - display: none; - } - } - } - - .legal-xs { - display: none; - text-align: center; - padding-top: 20px; - - @media screen and (max-width: $no-gap-breakpoint) { - display: block; - } - } - - h4 { - text-transform: uppercase; - font-weight: 700; - margin-bottom: 8px; - color: $darker-text-color; - - a { - color: inherit; - text-decoration: none; - } - } - - ul a, - .legal-xs a { - text-decoration: none; - color: lighten($ui-base-color, 34%); - - &:hover, - &:active, - &:focus { - text-decoration: underline; - } - } - - .brand { - .logo { - display: block; - height: 36px; - width: auto; - margin: 0 auto; - color: lighten($ui-base-color, 34%); - } - - &:hover, - &:focus, - &:active { - .logo { - color: lighten($ui-base-color, 38%); - } - } - } - } -} diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss index 98eb1511c..ccec8e95e 100644 --- a/app/javascript/styles/mastodon/rtl.scss +++ b/app/javascript/styles/mastodon/rtl.scss @@ -53,16 +53,6 @@ body.rtl { right: -26px; } - .landing-page__logo { - margin-right: 0; - margin-left: 20px; - } - - .landing-page .features-list .features-list__row .visual { - margin-left: 0; - margin-right: 15px; - } - .column-link__icon, .column-header__icon { margin-right: 0; @@ -350,44 +340,6 @@ body.rtl { margin-left: 45px; } - .landing-page .header-wrapper .mascot { - right: 60px; - left: auto; - } - - .landing-page__call-to-action .row__information-board { - direction: rtl; - } - - .landing-page .header .hero .floats .float-1 { - left: -120px; - right: auto; - } - - .landing-page .header .hero .floats .float-2 { - left: 210px; - right: auto; - } - - .landing-page .header .hero .floats .float-3 { - left: 110px; - right: auto; - } - - .landing-page .header .links .brand img { - left: 0; - } - - .landing-page .fa-external-link { - padding-right: 5px; - padding-left: 0 !important; - } - - .landing-page .features #mastodon-timeline { - margin-right: 0; - margin-left: 30px; - } - @media screen and (min-width: 631px) { .column, .drawer { @@ -415,32 +367,6 @@ body.rtl { padding-right: 0; } - .public-layout { - .header { - .nav-button { - margin-left: 8px; - margin-right: 0; - } - } - - .public-account-header__tabs { - margin-left: 0; - margin-right: 20px; - } - } - - .landing-page__information { - .account__display-name { - margin-right: 0; - margin-left: 5px; - } - - .account__avatar-wrapper { - margin-left: 12px; - margin-right: 0; - } - } - .card__bar .display-name { margin-left: 0; margin-right: 15px; diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss index a3237a630..ce71d11e4 100644 --- a/app/javascript/styles/mastodon/statuses.scss +++ b/app/javascript/styles/mastodon/statuses.scss @@ -137,8 +137,7 @@ a.button.logo-button { justify-content: center; } -.embed, -.public-layout { +.embed { .status__content[data-spoiler="folded"] { .e-content { display: none; diff --git a/app/lib/permalink_redirector.rb b/app/lib/permalink_redirector.rb index 6d15f3963..cf1a37625 100644 --- a/app/lib/permalink_redirector.rb +++ b/app/lib/permalink_redirector.rb @@ -8,16 +8,14 @@ class PermalinkRedirector end def redirect_path - if path_segments[0] == 'web' - if path_segments[1].present? && path_segments[1].start_with?('@') && path_segments[2] =~ /\d/ - find_status_url_by_id(path_segments[2]) - elsif path_segments[1].present? && path_segments[1].start_with?('@') - find_account_url_by_name(path_segments[1]) - elsif path_segments[1] == 'statuses' && path_segments[2] =~ /\d/ - find_status_url_by_id(path_segments[2]) - elsif path_segments[1] == 'accounts' && path_segments[2] =~ /\d/ - find_account_url_by_id(path_segments[2]) - end + if path_segments[0].present? && path_segments[0].start_with?('@') && path_segments[1] =~ /\d/ + find_status_url_by_id(path_segments[1]) + elsif path_segments[0].present? && path_segments[0].start_with?('@') + find_account_url_by_name(path_segments[0]) + elsif path_segments[0] == 'statuses' && path_segments[1] =~ /\d/ + find_status_url_by_id(path_segments[1]) + elsif path_segments[0] == 'accounts' && path_segments[1] =~ /\d/ + find_account_url_by_id(path_segments[1]) end end @@ -29,18 +27,12 @@ class PermalinkRedirector def find_status_url_by_id(id) status = Status.find_by(id: id) - - return unless status&.distributable? - - ActivityPub::TagManager.instance.url_for(status) + ActivityPub::TagManager.instance.url_for(status) if status&.distributable? && !status.account.local? end def find_account_url_by_id(id) account = Account.find_by(id: id) - - return unless account - - ActivityPub::TagManager.instance.url_for(account) + ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local? end def find_account_url_by_name(name) @@ -48,12 +40,6 @@ class PermalinkRedirector domain = nil if TagManager.instance.local_domain?(domain) account = Account.find_remote(username, domain) - return unless account - - ActivityPub::TagManager.instance.url_for(account) - end - - def find_tag_url_by_name(name) - tag_path(CGI.unescape(name)) + ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local? end end diff --git a/app/models/account.rb b/app/models/account.rb index 1be7b4d12..df7fa8d50 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -134,6 +134,7 @@ class Account < ApplicationRecord :role, :locale, :shows_application?, + :prefers_noindex?, to: :user, prefix: true, allow_nil: true diff --git a/app/models/user.rb b/app/models/user.rb index 4767189a0..6d566b1c2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -281,6 +281,10 @@ class User < ApplicationRecord save! end + def prefers_noindex? + setting_noindex + end + def preferred_posting_language valid_locale_cascade(settings.default_language, locale, I18n.locale) end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index c52a89d87..e521dacaa 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -14,6 +14,7 @@ class REST::AccountSerializer < ActiveModel::Serializer attribute :suspended, if: :suspended? attribute :silenced, key: :limited, if: :silenced? + attribute :noindex, if: :local? class FieldSerializer < ActiveModel::Serializer include FormattingHelper @@ -103,7 +104,11 @@ class REST::AccountSerializer < ActiveModel::Serializer object.silenced? end - delegate :suspended?, :silenced?, to: :object + def noindex + object.user_prefers_noindex? + end + + delegate :suspended?, :silenced?, :local?, to: :object def moved_and_not_nested? object.moved? && object.moved_to_account.moved_to_account_id.nil? diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index aff28b9a9..05d8989ad 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -1,4 +1,7 @@ - content_for :page_title do = t('about.title') +- content_for :header_tags do + = render partial: 'shared/og' + = render partial: 'shared/web_app' diff --git a/app/views/accounts/_bio.html.haml b/app/views/accounts/_bio.html.haml deleted file mode 100644 index e2539b1d4..000000000 --- a/app/views/accounts/_bio.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -- fields = account.fields - -.public-account-bio - - unless fields.empty? - .account__header__fields - - fields.each do |field| - %dl - %dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis) - %dd{ title: field.value, class: custom_field_classes(field) } - - if field.verified? - %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) } - = fa_icon 'check' - = prerender_custom_emojis(account_field_value_format(field), account.emojis) - - = account_badge(account) - - - if account.note.present? - .account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis) - - .public-account-bio__extra - = t 'accounts.joined', date: l(account.created_at, format: :month) diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml deleted file mode 100644 index d9966723a..000000000 --- a/app/views/accounts/_header.html.haml +++ /dev/null @@ -1,43 +0,0 @@ -.public-account-header{:class => ("inactive" if account.moved?)} - .public-account-header__image - = image_tag (prefers_autoplay? ? account.header_original_url : account.header_static_url), class: 'parallax' - .public-account-header__bar - = link_to short_account_url(account), class: 'avatar' do - = image_tag (prefers_autoplay? ? account.avatar_original_url : account.avatar_static_url), id: 'profile_page_avatar', data: { original: full_asset_url(account.avatar_original_url), static: full_asset_url(account.avatar_static_url), autoplay: prefers_autoplay? } - .public-account-header__tabs - .public-account-header__tabs__name - %h1 - = display_name(account, custom_emojify: true) - %small - = acct(account) - = fa_icon('lock') if account.locked? - .public-account-header__tabs__tabs - .details-counters - .counter{ class: active_nav_class(short_account_url(account), short_account_with_replies_url(account), short_account_media_url(account)) } - = link_to short_account_url(account), class: 'u-url u-uid', title: number_with_delimiter(account.statuses_count) do - %span.counter-number= friendly_number_to_human account.statuses_count - %span.counter-label= t('accounts.posts', count: account.statuses_count) - - .counter{ class: active_nav_class(account_following_index_url(account)) } - = link_to account_following_index_url(account), title: number_with_delimiter(account.following_count) do - %span.counter-number= friendly_number_to_human account.following_count - %span.counter-label= t('accounts.following', count: account.following_count) - - .counter{ class: active_nav_class(account_followers_url(account)) } - = link_to account_followers_url(account), title: number_with_delimiter(account.followers_count) do - %span.counter-number= friendly_number_to_human account.followers_count - %span.counter-label= t('accounts.followers', count: account.followers_count) - .spacer - .public-account-header__tabs__tabs__buttons - = account_action_button(account) - - .public-account-header__extra - = render 'accounts/bio', account: account - - .public-account-header__extra__links - = link_to account_following_index_url(account) do - %strong= friendly_number_to_human account.following_count - = t('accounts.following', count: account.following_count) - = link_to account_followers_url(account) do - %strong= friendly_number_to_human account.followers_count - = t('accounts.followers', count: account.followers_count) diff --git a/app/views/accounts/_moved.html.haml b/app/views/accounts/_moved.html.haml deleted file mode 100644 index 2f46e0dd0..000000000 --- a/app/views/accounts/_moved.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -- moved_to_account = account.moved_to_account - -.moved-account-widget - .moved-account-widget__message - = fa_icon 'suitcase' - = t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.pretty_acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention')) - - .moved-account-widget__card - = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do - .detailed-status__display-avatar - .account__avatar-overlay - .account__avatar-overlay-base - = image_tag moved_to_account.avatar_static_url - .account__avatar-overlay-overlay - = image_tag account.avatar_static_url - - %span.display-name - %bdi - %strong.emojify= display_name(moved_to_account, custom_emojify: true) - %span @#{moved_to_account.pretty_acct} diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 7fa688bd3..a51dcd7be 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -2,85 +2,13 @@ = "#{display_name(@account)} (#{acct(@account)})" - content_for :header_tags do - - if @account.user&.setting_noindex + - if @account.user_prefers_noindex? %meta{ name: 'robots', content: 'noindex, noarchive' }/ %link{ rel: 'alternate', type: 'application/rss+xml', href: @rss_url }/ %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/ - - if @older_url - %link{ rel: 'next', href: @older_url }/ - - if @newer_url - %link{ rel: 'prev', href: @newer_url }/ - = opengraph 'og:type', 'profile' = render 'og', account: @account, url: short_account_url(@account, only_path: false) - -= render 'header', account: @account, with_bio: true - -.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.display_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= friendly_number_to_human featured_tag.statuses_count - - = render 'application/sidebar' += render partial: 'shared/web_app' diff --git a/app/views/follower_accounts/index.html.haml b/app/views/follower_accounts/index.html.haml index 92de35a9f..d93540c02 100644 --- a/app/views/follower_accounts/index.html.haml +++ b/app/views/follower_accounts/index.html.haml @@ -1,20 +1,6 @@ -- content_for :page_title do - = t('accounts.people_who_follow', name: display_name(@account)) - - content_for :header_tags do %meta{ name: 'robots', content: 'noindex' }/ - = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false) -= render 'accounts/header', account: @account - -- if @account.hide_collections? - .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 + = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false) - = paginate @follows += render 'shared/web_app' diff --git a/app/views/following_accounts/index.html.haml b/app/views/following_accounts/index.html.haml index 9bb1a9edd..d93540c02 100644 --- a/app/views/following_accounts/index.html.haml +++ b/app/views/following_accounts/index.html.haml @@ -1,20 +1,6 @@ -- content_for :page_title do - = t('accounts.people_followed_by', name: display_name(@account)) - - content_for :header_tags do %meta{ name: 'robots', content: 'noindex' }/ - = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false) -= render 'accounts/header', account: @account - -- if @account.hide_collections? - .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 + = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false) - = paginate @follows += render 'shared/web_app' diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 76a02e0f0..45990cd10 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,4 +1,7 @@ - content_for :header_tags do + - unless request.path == '/' + %meta{ name: 'robots', content: 'noindex' }/ + = render partial: 'shared/og' = render 'shared/web_app' diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml deleted file mode 100644 index 9b9e725e9..000000000 --- a/app/views/layouts/public.html.haml +++ /dev/null @@ -1,60 +0,0 @@ -- content_for :header_tags do - = render_initial_state - = javascript_pack_tag 'public', crossorigin: 'anonymous' - -- content_for :content do - .public-layout - - unless @hide_navbar - .container - %nav.header - .nav-left - = link_to root_url, class: 'brand' do - = logo_as_symbol(:wordmark) - - - unless whitelist_mode? - = link_to t('about.about_this'), about_more_path, class: 'nav-link optional' - = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional' - - .nav-center - - .nav-right - - if user_signed_in? - = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn' - - else - = link_to_login t('auth.login'), class: 'webapp-btn nav-link nav-button' - = link_to t('auth.register'), available_sign_up_path, class: 'webapp-btn nav-link nav-button' - - .container= yield - - .container - .footer - .grid - .column-0 - %h4= t 'footer.resources' - %ul - %li= link_to t('about.privacy_policy'), privacy_policy_path - .column-1 - %h4= t 'footer.developers' - %ul - %li= link_to t('about.documentation'), 'https://docs.joinmastodon.org/' - %li= link_to t('about.api'), 'https://docs.joinmastodon.org/client/intro/' - .column-2 - %h4= link_to t('about.what_is_mastodon'), 'https://joinmastodon.org/' - = link_to logo_as_symbol, root_url, class: 'brand' - .column-3 - %h4= site_hostname - %ul - - unless whitelist_mode? - %li= link_to t('about.about_this'), about_more_path - %li= "v#{Mastodon::Version.to_s}" - .column-4 - %h4= t 'footer.more' - %ul - %li= link_to t('about.source_code'), Mastodon::Version.source_url - %li= link_to t('about.apps'), 'https://joinmastodon.org/apps' - .legal-xs - = link_to "v#{Mastodon::Version.to_s}", Mastodon::Version.source_url - · - = link_to t('about.privacy_policy'), privacy_policy_path - -= render template: 'layouts/application' diff --git a/app/views/privacy/show.html.haml b/app/views/privacy/show.html.haml index cfc285925..95e506641 100644 --- a/app/views/privacy/show.html.haml +++ b/app/views/privacy/show.html.haml @@ -1,4 +1,7 @@ - content_for :page_title do = t('privacy_policy.title') +- content_for :header_tags do + = render partial: 'shared/og' + = render 'shared/web_app' diff --git a/app/views/remote_follow/new.html.haml b/app/views/remote_follow/new.html.haml deleted file mode 100644 index 4e9601f6a..000000000 --- a/app/views/remote_follow/new.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -- content_for :header_tags do - %meta{ name: 'robots', content: 'noindex' }/ - -.form-container - .follow-prompt - %h2= t('remote_follow.prompt') - - = render partial: 'application/card', locals: { account: @account } - - = simple_form_for @remote_follow, as: :remote_follow, url: account_remote_follow_path(@account) do |f| - = render 'shared/error_messages', object: @remote_follow - - = f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' } - - .actions - = f.button :button, t('remote_follow.proceed'), type: :submit - - %p.hint.subtle-hint - = t('remote_follow.reason_html', instance: site_hostname) - = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path) diff --git a/app/views/remote_interaction/new.html.haml b/app/views/remote_interaction/new.html.haml deleted file mode 100644 index 2cc0fcb93..000000000 --- a/app/views/remote_interaction/new.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -- content_for :header_tags do - %meta{ name: 'robots', content: 'noindex' }/ - -.form-container - .follow-prompt - %h2= t("remote_interaction.#{@interaction_type}.prompt") - - .public-layout - .activity-stream.activity-stream--highlighted - = render 'statuses/status', status: @status - - = simple_form_for @remote_follow, as: :remote_follow, url: remote_interaction_path(@status) do |f| - = render 'shared/error_messages', object: @remote_follow - - = hidden_field_tag :type, @interaction_type - - = f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' } - - .actions - = f.button :button, t("remote_interaction.#{@interaction_type}.proceed"), type: :submit - - %p.hint.subtle-hint - = t('remote_follow.reason_html', instance: site_hostname) - = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path) diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index c67f0e4d9..37001b022 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -56,7 +56,7 @@ - else = link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener noreferrer' · - = link_to remote_interaction_path(status, type: :reply), class: 'modal-button detailed-status__link' do + %span.detailed-status__link - if status.in_reply_to_id.nil? = fa_icon('reply') - else @@ -65,12 +65,12 @@ = " " · - if status.public_visibility? || status.unlisted_visibility? - = link_to remote_interaction_path(status, type: :reblog), class: 'modal-button detailed-status__link' do + %span.detailed-status__link = fa_icon('retweet') %span.detailed-status__reblogs>= friendly_number_to_human status.reblogs_count = " " · - = link_to remote_interaction_path(status, type: :favourite), class: 'modal-button detailed-status__link' do + %span.detailed-status__link = fa_icon('star') %span.detailed-status__favorites>= friendly_number_to_human status.favourites_count = " " diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index f16d2c186..bfde3a260 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -53,18 +53,18 @@ = t 'statuses.show_thread' .status__action-bar - = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button icon-button--with-counter modal-button' do + %span.status__action-bar-button.icon-button.icon-button--with-counter - if status.in_reply_to_id.nil? = fa_icon 'reply fw' - else = fa_icon 'reply-all fw' %span.icon-button__counter= obscured_counter status.replies_count - = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do + %span.status__action-bar-button.icon-button - if status.distributable? = fa_icon 'retweet fw' - elsif status.private_visibility? || status.limited_visibility? = fa_icon 'lock fw' - else = fa_icon 'at fw' - = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button' do + %span.status__action-bar-button.icon-button = fa_icon 'star fw' diff --git a/app/views/statuses/show.html.haml b/app/views/statuses/show.html.haml index 5a3c94b84..106c41725 100644 --- a/app/views/statuses/show.html.haml +++ b/app/views/statuses/show.html.haml @@ -2,7 +2,7 @@ = t('statuses.title', name: display_name(@account), quote: truncate(@status.spoiler_text.presence || @status.text, length: 50, omission: '…', escape: false)) - content_for :header_tags do - - if @account.user&.setting_noindex + - if @account.user_prefers_noindex? %meta{ name: 'robots', content: 'noindex, noarchive' }/ %link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: short_account_status_url(@account, @status), format: 'json') }/ diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml new file mode 100644 index 000000000..4b4967a8f --- /dev/null +++ b/app/views/tags/show.html.haml @@ -0,0 +1,5 @@ +- content_for :header_tags do + %meta{ name: 'robots', content: 'noindex' }/ + = render partial: 'shared/og' + += render partial: 'shared/web_app' diff --git a/config/locales/en.yml b/config/locales/en.yml index 504f1b364..412178ca3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2,47 +2,26 @@ en: about: about_mastodon_html: 'The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!' - api: API - apps: Mobile apps contact_missing: Not set contact_unavailable: N/A - documentation: Documentation hosted_on: Mastodon hosted on %{domain} - privacy_policy: Privacy Policy - source_code: Source code title: About - what_is_mastodon: What is Mastodon? accounts: - choices_html: "%{name}'s choices:" - endorsements_hint: You can endorse people you follow from the web interface, and they will show up here. - featured_tags_hint: You can feature specific hashtags that will be displayed here. follow: Follow followers: one: Follower other: Followers following: Following instance_actor_flash: This account is a virtual actor used to represent the server itself and not any individual user. It is used for federation purposes and should not be suspended. - joined: Joined %{date} last_active: last active link_verified_on: Ownership of this link was checked on %{date} - media: Media - moved_html: "%{name} has moved to %{new_profile_link}:" - network_hidden: This information is not available nothing_here: There is nothing here! - people_followed_by: People whom %{name} follows - people_who_follow: People who follow %{name} pin_errors: following: You must be already following the person you want to endorse posts: one: Post other: Posts posts_tab_heading: Posts - posts_with_replies: Posts and replies - roles: - bot: Bot - group: Group - unavailable: Profile unavailable - unfollow: Unfollow admin: account_actions: action: Perform action @@ -1176,9 +1155,6 @@ en: hint: This filter applies to select individual posts regardless of other criteria. You can add more posts to this filter from the web interface. title: Filtered posts footer: - developers: Developers - more: More… - resources: Resources trending_now: Trending now generic: all: All @@ -1221,7 +1197,6 @@ en: following: Following list muting: Muting list upload: Upload - in_memoriam_html: In Memoriam. invites: delete: Deactivate expired: Expired @@ -1402,22 +1377,7 @@ en: remove_selected_follows: Unfollow selected users status: Account status remote_follow: - acct: Enter your username@domain you want to act from missing_resource: Could not find the required redirect URL for your account - no_account_html: Don't have an account? You can sign up here - proceed: Proceed to follow - prompt: 'You are going to follow:' - reason_html: "Why is this step necessary? %{instance} might not be the server where you are registered, so we need to redirect you to your home server first." - remote_interaction: - favourite: - proceed: Proceed to favourite - prompt: 'You want to favourite this post:' - reblog: - proceed: Proceed to boost - prompt: 'You want to boost this post:' - reply: - proceed: Proceed to reply - prompt: 'You want to reply to this post:' reports: errors: invalid_rules: does not reference valid rules diff --git a/config/routes.rb b/config/routes.rb index 29ec0f8a5..1ed585f19 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,31 @@ require 'sidekiq_unique_jobs/web' require 'sidekiq-scheduler/web' +# Paths of routes on the web app that to not require to be indexed or +# have alternative format representations requiring separate controllers +WEB_APP_PATHS = %w( + /getting-started + /keyboard-shortcuts + /home + /public + /public/local + /conversations + /lists/(*any) + /notifications + /favourites + /bookmarks + /pinned + /start + /directory + /explore/(*any) + /search + /publish + /follow_requests + /blocks + /domain_blocks + /mutes +).freeze + Rails.application.routes.draw do root 'home#index' @@ -59,9 +84,6 @@ Rails.application.routes.draw do get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" } resources :accounts, path: 'users', only: [:show], param: :username do - get :remote_follow, to: 'remote_follow#new' - post :remote_follow, to: 'remote_follow#create' - resources :statuses, only: [:show] do member do get :activity @@ -85,16 +107,21 @@ Rails.application.routes.draw do resource :inbox, only: [:create], module: :activitypub - get '/@:username', to: 'accounts#show', as: :short_account - 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 '/@:account_username/:id', to: 'statuses#show', as: :short_account_status - get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status + constraints(username: /[^@\/.]+/) do + get '/@:username', to: 'accounts#show', as: :short_account + 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 + end - get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction - post '/interact/:id', to: 'remote_interaction#create' + constraints(account_username: /[^@\/.]+/) do + get '/@:account_username/following', to: 'following_accounts#index' + get '/@:account_username/followers', to: 'follower_accounts#index' + get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status + get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status + end + get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: /([^\/])+?/ }, format: false get '/settings', to: redirect('/settings/profile') namespace :settings do @@ -187,9 +214,6 @@ Rails.application.routes.draw do resource :relationships, only: [:show, :update] resource :statuses_cleanup, controller: :statuses_cleanup, only: [:show, :update] - get '/explore', to: redirect('/web/explore') - get '/public', to: redirect('/web/public') - get '/public/local', to: redirect('/web/public/local') get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy resource :authorize_interaction, only: [:show, :create] @@ -642,8 +666,11 @@ Rails.application.routes.draw do end end - get '/web/(*any)', to: 'home#index', as: :web + WEB_APP_PATHS.each do |path| + get path, to: 'home#index' + end + get '/web/(*any)', to: redirect('/%{any}', status: 302), as: :web get '/about', to: 'about#show' get '/about/more', to: redirect('/about') diff --git a/package.json b/package.json index 5d8f20abf..0a57336d6 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,6 @@ "redux-immutable": "^4.0.0", "redux-thunk": "^2.4.1", "regenerator-runtime": "^0.13.9", - "rellax": "^1.12.1", "requestidlecallback": "^0.3.0", "reselect": "^4.1.6", "rimraf": "^3.0.2", diff --git a/spec/controllers/account_follow_controller_spec.rb b/spec/controllers/account_follow_controller_spec.rb deleted file mode 100644 index d33cd0499..000000000 --- a/spec/controllers/account_follow_controller_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'rails_helper' - -describe AccountFollowController do - render_views - - let(:user) { Fabricate(:user) } - let(:alice) { Fabricate(:account, username: 'alice') } - - describe 'POST #create' do - let(:service) { double } - - subject { post :create, params: { account_username: alice.username } } - - before do - allow(FollowService).to receive(:new).and_return(service) - allow(service).to receive(:call) - end - - context 'when account is permanently suspended' do - before do - alice.suspend! - alice.deletion_request.destroy - subject - end - - it 'returns http gone' do - expect(response).to have_http_status(410) - end - end - - context 'when account is temporarily suspended' do - before do - alice.suspend! - subject - end - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - context 'when signed out' do - before do - subject - end - - it 'does not follow' do - expect(FollowService).not_to receive(:new) - end - end - - context 'when signed in' do - before do - sign_in(user) - subject - end - - it 'redirects to account path' do - expect(service).to have_received(:call).with(user.account, alice, with_rate_limit: true) - expect(response).to redirect_to(account_path(alice)) - end - end - end -end diff --git a/spec/controllers/account_unfollow_controller_spec.rb b/spec/controllers/account_unfollow_controller_spec.rb deleted file mode 100644 index a11f7aa68..000000000 --- a/spec/controllers/account_unfollow_controller_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'rails_helper' - -describe AccountUnfollowController do - render_views - - let(:user) { Fabricate(:user) } - let(:alice) { Fabricate(:account, username: 'alice') } - - describe 'POST #create' do - let(:service) { double } - - subject { post :create, params: { account_username: alice.username } } - - before do - allow(UnfollowService).to receive(:new).and_return(service) - allow(service).to receive(:call) - end - - context 'when account is permanently suspended' do - before do - alice.suspend! - alice.deletion_request.destroy - subject - end - - it 'returns http gone' do - expect(response).to have_http_status(410) - end - end - - context 'when account is temporarily suspended' do - before do - alice.suspend! - subject - end - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - context 'when signed out' do - before do - subject - end - - it 'does not unfollow' do - expect(UnfollowService).not_to receive(:new) - end - end - - context 'when signed in' do - before do - sign_in(user) - subject - end - - it 'redirects to account path' do - expect(service).to have_received(:call).with(user.account, alice) - expect(response).to redirect_to(account_path(alice)) - end - end - end -end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index 12266c800..defa8b2d3 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -99,100 +99,6 @@ RSpec.describe AccountsController, type: :controller do end it_behaves_like 'common response characteristics' - - it 'renders public status' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status)) - end - - it 'renders self-reply' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end - - it 'renders status with media' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media)) - end - - it 'renders reblog' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) - end - - it 'renders pinned status' do - expect(response.body).to include(I18n.t('stream_entries.pinned')) - end - - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end - - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end - - it 'does not render reply to someone else' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply)) - end - end - - context 'when signed-in' do - let(:user) { Fabricate(:user) } - - before do - sign_in(user) - end - - context 'when user follows account' do - before do - user.account.follow!(account) - get :show, params: { username: account.username, format: format } - end - - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end - end - - context 'when user is blocked' do - before do - account.block!(user.account) - get :show, params: { username: account.username, format: format } - end - - it 'renders unavailable message' do - expect(response.body).to include(I18n.t('accounts.unavailable')) - end - - it 'does not render public status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status)) - end - - it 'does not render self-reply' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end - - it 'does not render status with media' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media)) - end - - it 'does not render reblog' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) - end - - it 'does not render pinned status' do - expect(response.body).to_not include(I18n.t('stream_entries.pinned')) - end - - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end - - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end - - it 'does not render reply to someone else' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply)) - end - end end context 'with replies' do @@ -202,38 +108,6 @@ RSpec.describe AccountsController, type: :controller do end it_behaves_like 'common response characteristics' - - it 'renders public status' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status)) - end - - it 'renders self-reply' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end - - it 'renders status with media' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media)) - end - - it 'renders reblog' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) - end - - it 'does not render pinned status' do - expect(response.body).to_not include(I18n.t('stream_entries.pinned')) - end - - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end - - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end - - it 'renders reply to someone else' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply)) - end end context 'with media' do @@ -243,38 +117,6 @@ RSpec.describe AccountsController, type: :controller do end it_behaves_like 'common response characteristics' - - it 'does not render public status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status)) - end - - it 'does not render self-reply' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end - - it 'renders status with media' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media)) - end - - it 'does not render reblog' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) - end - - it 'does not render pinned status' do - expect(response.body).to_not include(I18n.t('stream_entries.pinned')) - end - - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end - - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end - - it 'does not render reply to someone else' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply)) - end end context 'with tag' do @@ -289,42 +131,6 @@ RSpec.describe AccountsController, type: :controller do end it_behaves_like 'common response characteristics' - - it 'does not render public status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status)) - end - - it 'does not render self-reply' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end - - it 'does not render status with media' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media)) - end - - it 'does not render reblog' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) - end - - it 'does not render pinned status' do - expect(response.body).to_not include(I18n.t('stream_entries.pinned')) - end - - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end - - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end - - it 'does not render reply to someone else' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply)) - end - - it 'renders status with tag' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag)) - end end end diff --git a/spec/controllers/authorize_interactions_controller_spec.rb b/spec/controllers/authorize_interactions_controller_spec.rb index 99f3f6ffc..44f52df69 100644 --- a/spec/controllers/authorize_interactions_controller_spec.rb +++ b/spec/controllers/authorize_interactions_controller_spec.rb @@ -39,7 +39,7 @@ describe AuthorizeInteractionsController do end it 'sets resource from url' do - account = Account.new + account = Fabricate(:account) service = double allow(ResolveURLService).to receive(:new).and_return(service) allow(service).to receive(:call).with('http://example.com').and_return(account) @@ -51,7 +51,7 @@ describe AuthorizeInteractionsController do end it 'sets resource from acct uri' do - account = Account.new + account = Fabricate(:account) service = double allow(ResolveAccountService).to receive(:new).and_return(service) allow(service).to receive(:call).with('found@hostname').and_return(account) diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb index 4d2a6e01a..ab2e82e85 100644 --- a/spec/controllers/follower_accounts_controller_spec.rb +++ b/spec/controllers/follower_accounts_controller_spec.rb @@ -34,27 +34,6 @@ describe FollowerAccountsController do expect(response).to have_http_status(403) end end - - it 'assigns follows' do - expect(response).to have_http_status(200) - - assigned = assigns(:follows).to_a - expect(assigned.size).to eq 2 - expect(assigned[0]).to eq follow1 - expect(assigned[1]).to eq follow0 - end - - it 'does not assign blocked users' do - user = Fabricate(:user) - user.account.block!(follower0) - sign_in(user) - - expect(response).to have_http_status(200) - - assigned = assigns(:follows).to_a - expect(assigned.size).to eq 1 - expect(assigned[0]).to eq follow1 - end end context 'when format is json' do diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb index bb6d221ca..e43dbf882 100644 --- a/spec/controllers/following_accounts_controller_spec.rb +++ b/spec/controllers/following_accounts_controller_spec.rb @@ -34,27 +34,6 @@ describe FollowingAccountsController do expect(response).to have_http_status(403) end end - - it 'assigns follows' do - expect(response).to have_http_status(200) - - assigned = assigns(:follows).to_a - expect(assigned.size).to eq 2 - expect(assigned[0]).to eq follow1 - expect(assigned[1]).to eq follow0 - end - - it 'does not assign blocked users' do - user = Fabricate(:user) - user.account.block!(followee0) - sign_in(user) - - expect(response).to have_http_status(200) - - assigned = assigns(:follows).to_a - expect(assigned.size).to eq 1 - expect(assigned[0]).to eq follow1 - end end context 'when format is json' do diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb deleted file mode 100644 index 01d43f48c..000000000 --- a/spec/controllers/remote_follow_controller_spec.rb +++ /dev/null @@ -1,135 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe RemoteFollowController do - render_views - - describe '#new' do - it 'returns success when session is empty' do - account = Fabricate(:account) - get :new, params: { account_username: account.to_param } - - expect(response).to have_http_status(200) - expect(response).to render_template(:new) - expect(assigns(:remote_follow).acct).to be_nil - end - - it 'populates the remote follow with session data when session exists' do - session[:remote_follow] = 'user@example.com' - account = Fabricate(:account) - get :new, params: { account_username: account.to_param } - - expect(response).to have_http_status(200) - expect(response).to render_template(:new) - expect(assigns(:remote_follow).acct).to eq 'user@example.com' - end - end - - describe '#create' do - before do - @account = Fabricate(:account, username: 'test_user') - end - - context 'with a valid acct' do - context 'when webfinger values are wrong' do - it 'renders new when redirect url is nil' do - resource_with_nil_link = double(link: nil) - allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_nil_link) - post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } } - - expect(response).to render_template(:new) - expect(response.body).to include(I18n.t('remote_follow.missing_resource')) - end - - it 'renders new when template is nil' do - resource_with_link = double(link: nil) - allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link) - post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } } - - expect(response).to render_template(:new) - expect(response.body).to include(I18n.t('remote_follow.missing_resource')) - end - end - - context 'when webfinger values are good' do - before do - resource_with_link = double(link: 'http://example.com/follow_me?acct={uri}') - allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link) - post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } } - end - - it 'saves the session' do - expect(session[:remote_follow]).to eq 'user@example.com' - end - - it 'redirects to the remote location' do - expect(response).to redirect_to("http://example.com/follow_me?acct=https%3A%2F%2F#{Rails.configuration.x.local_domain}%2Fusers%2Ftest_user") - end - end - end - - context 'with an invalid acct' do - it 'renders new when acct is missing' do - post :create, params: { account_username: @account.to_param, remote_follow: { acct: '' } } - - expect(response).to render_template(:new) - end - - it 'renders new with error when webfinger fails' do - allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Webfinger::Error) - post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } } - - expect(response).to render_template(:new) - expect(response.body).to include(I18n.t('remote_follow.missing_resource')) - end - - it 'renders new when occur HTTP::ConnectionError' do - allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@unknown').and_raise(HTTP::ConnectionError) - post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@unknown' } } - - expect(response).to render_template(:new) - expect(response.body).to include(I18n.t('remote_follow.missing_resource')) - end - end - end - - context 'with a permanently suspended account' do - before do - @account = Fabricate(:account) - @account.suspend! - @account.deletion_request.destroy - end - - it 'returns http gone on GET to #new' do - get :new, params: { account_username: @account.to_param } - - expect(response).to have_http_status(410) - end - - it 'returns http gone on POST to #create' do - post :create, params: { account_username: @account.to_param } - - expect(response).to have_http_status(410) - end - end - - context 'with a temporarily suspended account' do - before do - @account = Fabricate(:account) - @account.suspend! - end - - it 'returns http forbidden on GET to #new' do - get :new, params: { account_username: @account.to_param } - - expect(response).to have_http_status(403) - end - - it 'returns http forbidden on POST to #create' do - post :create, params: { account_username: @account.to_param } - - expect(response).to have_http_status(403) - end - end -end diff --git a/spec/controllers/remote_interaction_controller_spec.rb b/spec/controllers/remote_interaction_controller_spec.rb deleted file mode 100644 index bb0074b11..000000000 --- a/spec/controllers/remote_interaction_controller_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe RemoteInteractionController, type: :controller do - render_views - - let(:status) { Fabricate(:status) } - - describe 'GET #new' do - it 'returns 200' do - get :new, params: { id: status.id } - expect(response).to have_http_status(200) - end - end - - describe 'POST #create' do - context '@remote_follow is valid' do - it 'returns 302' do - allow_any_instance_of(RemoteFollow).to receive(:valid?) { true } - allow_any_instance_of(RemoteFollow).to receive(:addressable_template) do - Addressable::Template.new('https://hoge.com') - end - - post :create, params: { id: status.id, remote_follow: { acct: '@hoge' } } - expect(response).to have_http_status(302) - end - end - - context '@remote_follow is invalid' do - it 'returns 200' do - allow_any_instance_of(RemoteFollow).to receive(:valid?) { false } - post :create, params: { id: status.id, remote_follow: { acct: '@hoge' } } - - expect(response).to have_http_status(200) - end - end - end -end diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb index 1fd8494d6..547bcfb39 100644 --- a/spec/controllers/tags_controller_spec.rb +++ b/spec/controllers/tags_controller_spec.rb @@ -10,16 +10,15 @@ RSpec.describe TagsController, type: :controller do let!(:late) { Fabricate(:status, tags: [tag], text: 'late #test') } context 'when tag exists' do - it 'redirects to web version' do + it 'returns http success' do get :show, params: { id: 'test', max_id: late.id } - expect(response).to redirect_to('/web/tags/test') + expect(response).to have_http_status(200) end end context 'when tag does not exist' do - it 'returns http missing for non-existent tag' do + it 'returns http not found' do get :show, params: { id: 'none' } - expect(response).to have_http_status(404) end end diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index b6de3e9d1..ec4f9a53f 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -18,36 +18,16 @@ feature 'Profile' do visit account_path('alice') is_expected.to have_title("alice (@alice@#{local_domain})") - - within('.public-account-header h1') do - is_expected.to have_content("alice @alice@#{local_domain}") - end - - bio_elem = first('.public-account-bio') - expect(bio_elem).to have_content(alice_bio) - # The bio has hashtags made clickable - expect(bio_elem).to have_link('cryptology') - expect(bio_elem).to have_link('science') - # Nicknames are make clickable - expect(bio_elem).to have_link('@alice') - expect(bio_elem).to have_link('@bob') - # Nicknames not on server are not clickable - expect(bio_elem).not_to have_link('@pepe') end scenario 'I can change my account' do visit settings_profile_path + fill_in 'Display name', with: 'Bob' fill_in 'Bio', with: 'Bob is silent' - first('.btn[type=submit]').click - is_expected.to have_content 'Changes successfully saved!' - # View my own public profile and see the changes - click_link "Bob @bob@#{local_domain}" + first('button[type=submit]').click - within('.public-account-header h1') do - is_expected.to have_content("Bob @bob@#{local_domain}") - end - expect(first('.public-account-bio')).to have_content('Bob is silent') + is_expected.to have_content 'Changes successfully saved!' end end diff --git a/spec/lib/permalink_redirector_spec.rb b/spec/lib/permalink_redirector_spec.rb index abda57da4..a00913656 100644 --- a/spec/lib/permalink_redirector_spec.rb +++ b/spec/lib/permalink_redirector_spec.rb @@ -3,40 +3,31 @@ require 'rails_helper' describe PermalinkRedirector do + let(:remote_account) { Fabricate(:account, username: 'alice', domain: 'example.com', url: 'https://example.com/@alice', id: 2) } + describe '#redirect_url' do before do - account = Fabricate(:account, username: 'alice', id: 1) - Fabricate(:status, account: account, id: 123) + Fabricate(:status, account: remote_account, id: 123, url: 'https://example.com/status-123') end it 'returns path for legacy account links' do - redirector = described_class.new('web/accounts/1') - expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice' + redirector = described_class.new('accounts/2') + expect(redirector.redirect_path).to eq 'https://example.com/@alice' end it 'returns path for legacy status links' do - redirector = described_class.new('web/statuses/123') - expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice/123' - end - - it 'returns path for legacy tag links' do - redirector = described_class.new('web/timelines/tag/hoge') - expect(redirector.redirect_path).to be_nil + redirector = described_class.new('statuses/123') + expect(redirector.redirect_path).to eq 'https://example.com/status-123' end it 'returns path for pretty account links' do - redirector = described_class.new('web/@alice') - expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice' + redirector = described_class.new('@alice@example.com') + expect(redirector.redirect_path).to eq 'https://example.com/@alice' end it 'returns path for pretty status links' do - redirector = described_class.new('web/@alice/123') - expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice/123' - end - - it 'returns path for pretty tag links' do - redirector = described_class.new('web/tags/hoge') - expect(redirector.redirect_path).to be_nil + redirector = described_class.new('@alice/123') + expect(redirector.redirect_path).to eq 'https://example.com/status-123' end end end diff --git a/spec/requests/account_show_page_spec.rb b/spec/requests/account_show_page_spec.rb index 4e51cf7ef..e84c46c47 100644 --- a/spec/requests/account_show_page_spec.rb +++ b/spec/requests/account_show_page_spec.rb @@ -3,17 +3,6 @@ require 'rails_helper' describe 'The account show page' do - it 'Has an h-feed with correct number of h-entry objects in it' do - alice = Fabricate(:account, username: 'alice', display_name: 'Alice') - _status = Fabricate(:status, account: alice, text: 'Hello World') - _status2 = Fabricate(:status, account: alice, text: 'Hello World Again') - _status3 = Fabricate(:status, account: alice, text: 'Are You Still There World?') - - get '/@alice' - - expect(h_feed_entries.size).to eq(3) - end - it 'has valid opengraph tags' do alice = Fabricate(:account, username: 'alice', display_name: 'Alice') _status = Fabricate(:status, account: alice, text: 'Hello World') @@ -33,8 +22,4 @@ describe 'The account show page' do def head_section Nokogiri::Slop(response.body).html.head end - - def h_feed_entries - Nokogiri::HTML(response.body).search('.h-feed .h-entry') - end end diff --git a/spec/routing/accounts_routing_spec.rb b/spec/routing/accounts_routing_spec.rb index d04cb27f0..3f0e9b3e9 100644 --- a/spec/routing/accounts_routing_spec.rb +++ b/spec/routing/accounts_routing_spec.rb @@ -1,31 +1,83 @@ require 'rails_helper' describe 'Routes under accounts/' do - describe 'the route for accounts who are followers of an account' do - it 'routes to the followers action with the right username' do - expect(get('/users/name/followers')). - to route_to('follower_accounts#index', account_username: 'name') + context 'with local username' do + let(:username) { 'alice' } + + it 'routes /@:username' do + expect(get("/@#{username}")).to route_to('accounts#show', username: username) end - end - describe 'the route for accounts who are followed by an account' do - it 'routes to the following action with the right username' do - expect(get('/users/name/following')). - to route_to('following_accounts#index', account_username: 'name') + it 'routes /@:username.json' do + expect(get("/@#{username}.json")).to route_to('accounts#show', username: username, format: 'json') + end + + it 'routes /@:username.rss' do + expect(get("/@#{username}.rss")).to route_to('accounts#show', username: username, format: 'rss') + end + + it 'routes /@:username/:id' do + expect(get("/@#{username}/123")).to route_to('statuses#show', account_username: username, id: '123') + end + + it 'routes /@:username/:id/embed' do + expect(get("/@#{username}/123/embed")).to route_to('statuses#embed', account_username: username, id: '123') + end + + it 'routes /@:username/following' do + expect(get("/@#{username}/following")).to route_to('following_accounts#index', account_username: username) + end + + it 'routes /@:username/followers' do + expect(get("/@#{username}/followers")).to route_to('follower_accounts#index', account_username: username) + end + + it 'routes /@:username/with_replies' do + expect(get("/@#{username}/with_replies")).to route_to('accounts#show', username: username) + end + + it 'routes /@:username/media' do + expect(get("/@#{username}/media")).to route_to('accounts#show', username: username) end - end - describe 'the route for following an account' do - it 'routes to the follow create action with the right username' do - expect(post('/users/name/follow')). - to route_to('account_follow#create', account_username: 'name') + it 'routes /@:username/tagged/:tag' do + expect(get("/@#{username}/tagged/foo")).to route_to('accounts#show', username: username, tag: 'foo') end end - describe 'the route for unfollowing an account' do - it 'routes to the unfollow create action with the right username' do - expect(post('/users/name/unfollow')). - to route_to('account_unfollow#create', account_username: 'name') + context 'with remote username' do + let(:username) { 'alice@example.com' } + + it 'routes /@:username' do + expect(get("/@#{username}")).to route_to('home#index', username_with_domain: username) + end + + it 'routes /@:username/:id' do + expect(get("/@#{username}/123")).to route_to('home#index', username_with_domain: username, any: '123') + end + + it 'routes /@:username/:id/embed' do + expect(get("/@#{username}/123/embed")).to route_to('home#index', username_with_domain: username, any: '123/embed') + end + + it 'routes /@:username/following' do + expect(get("/@#{username}/following")).to route_to('home#index', username_with_domain: username, any: 'following') + end + + it 'routes /@:username/followers' do + expect(get("/@#{username}/followers")).to route_to('home#index', username_with_domain: username, any: 'followers') + end + + it 'routes /@:username/with_replies' do + expect(get("/@#{username}/with_replies")).to route_to('home#index', username_with_domain: username, any: 'with_replies') + end + + it 'routes /@:username/media' do + expect(get("/@#{username}/media")).to route_to('home#index', username_with_domain: username, any: 'media') + end + + it 'routes /@:username/tagged/:tag' do + expect(get("/@#{username}/tagged/foo")).to route_to('home#index', username_with_domain: username, any: 'tagged/foo') end end end diff --git a/yarn.lock b/yarn.lock index 6ae965464..98666f23d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9663,11 +9663,6 @@ regjsparser@^0.8.2: dependencies: jsesc "~0.5.0" -rellax@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/rellax/-/rellax-1.12.1.tgz#1b433ef7ac4aa3573449a33efab391c112f6b34d" - integrity sha512-XBIi0CDpW5FLTujYjYBn1CIbK2CJL6TsAg/w409KghP2LucjjzBjsujXDAjyBLWgsfupfUcL5WzdnIPcGfK7XA== - remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" -- cgit From 7c152acb2cc545a87610de349a94e14f45fbed5d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 22 Oct 2022 11:44:41 +0200 Subject: Change settings area to be separated into categories in admin UI (#19407) And update all descriptions --- app/controllers/admin/settings/about_controller.rb | 9 ++ .../admin/settings/appearance_controller.rb | 9 ++ .../admin/settings/branding_controller.rb | 9 ++ .../admin/settings/content_retention_controller.rb | 9 ++ .../admin/settings/discovery_controller.rb | 9 ++ .../admin/settings/registrations_controller.rb | 9 ++ app/controllers/admin/settings_controller.rb | 10 +- app/controllers/admin/site_uploads_controller.rb | 2 +- app/helpers/admin/settings_helper.rb | 7 -- app/javascript/styles/mastodon/admin.scss | 82 +++++++++++++---- app/javascript/styles/mastodon/components.scss | 5 + app/javascript/styles/mastodon/forms.scss | 16 +++- app/models/form/admin_settings.rb | 57 +++++++----- .../rest/extended_description_serializer.rb | 12 ++- app/views/admin/settings/about/show.html.haml | 33 +++++++ app/views/admin/settings/appearance/show.html.haml | 34 +++++++ app/views/admin/settings/branding/show.html.haml | 39 ++++++++ .../settings/content_retention/show.html.haml | 22 +++++ app/views/admin/settings/discovery/show.html.haml | 40 ++++++++ app/views/admin/settings/edit.html.haml | 102 --------------------- .../admin/settings/registrations/show.html.haml | 27 ++++++ app/views/admin/settings/shared/_links.html.haml | 8 ++ app/views/layouts/admin.html.haml | 11 ++- config/locales/en.yml | 87 +++++------------- config/locales/simple_form.en.yml | 37 ++++++++ config/navigation.rb | 2 +- config/routes.rb | 13 ++- .../admin/settings/branding_controller_spec.rb | 53 +++++++++++ spec/controllers/admin/settings_controller_spec.rb | 71 -------------- 29 files changed, 528 insertions(+), 296 deletions(-) create mode 100644 app/controllers/admin/settings/about_controller.rb create mode 100644 app/controllers/admin/settings/appearance_controller.rb create mode 100644 app/controllers/admin/settings/branding_controller.rb create mode 100644 app/controllers/admin/settings/content_retention_controller.rb create mode 100644 app/controllers/admin/settings/discovery_controller.rb create mode 100644 app/controllers/admin/settings/registrations_controller.rb create mode 100644 app/views/admin/settings/about/show.html.haml create mode 100644 app/views/admin/settings/appearance/show.html.haml create mode 100644 app/views/admin/settings/branding/show.html.haml create mode 100644 app/views/admin/settings/content_retention/show.html.haml create mode 100644 app/views/admin/settings/discovery/show.html.haml delete mode 100644 app/views/admin/settings/edit.html.haml create mode 100644 app/views/admin/settings/registrations/show.html.haml create mode 100644 app/views/admin/settings/shared/_links.html.haml create mode 100644 spec/controllers/admin/settings/branding_controller_spec.rb delete mode 100644 spec/controllers/admin/settings_controller_spec.rb (limited to 'app/helpers') diff --git a/app/controllers/admin/settings/about_controller.rb b/app/controllers/admin/settings/about_controller.rb new file mode 100644 index 000000000..86327fe39 --- /dev/null +++ b/app/controllers/admin/settings/about_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Admin::Settings::AboutController < Admin::SettingsController + private + + def after_update_redirect_path + admin_settings_about_path + end +end diff --git a/app/controllers/admin/settings/appearance_controller.rb b/app/controllers/admin/settings/appearance_controller.rb new file mode 100644 index 000000000..39b2448d8 --- /dev/null +++ b/app/controllers/admin/settings/appearance_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Admin::Settings::AppearanceController < Admin::SettingsController + private + + def after_update_redirect_path + admin_settings_appearance_path + end +end diff --git a/app/controllers/admin/settings/branding_controller.rb b/app/controllers/admin/settings/branding_controller.rb new file mode 100644 index 000000000..4a4d76f49 --- /dev/null +++ b/app/controllers/admin/settings/branding_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Admin::Settings::BrandingController < Admin::SettingsController + private + + def after_update_redirect_path + admin_settings_branding_path + end +end diff --git a/app/controllers/admin/settings/content_retention_controller.rb b/app/controllers/admin/settings/content_retention_controller.rb new file mode 100644 index 000000000..b88336a2c --- /dev/null +++ b/app/controllers/admin/settings/content_retention_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Admin::Settings::ContentRetentionController < Admin::SettingsController + private + + def after_update_redirect_path + admin_settings_content_retention_path + end +end diff --git a/app/controllers/admin/settings/discovery_controller.rb b/app/controllers/admin/settings/discovery_controller.rb new file mode 100644 index 000000000..be4b57f79 --- /dev/null +++ b/app/controllers/admin/settings/discovery_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Admin::Settings::DiscoveryController < Admin::SettingsController + private + + def after_update_redirect_path + admin_settings_discovery_path + end +end diff --git a/app/controllers/admin/settings/registrations_controller.rb b/app/controllers/admin/settings/registrations_controller.rb new file mode 100644 index 000000000..b4a74349c --- /dev/null +++ b/app/controllers/admin/settings/registrations_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Admin::Settings::RegistrationsController < Admin::SettingsController + private + + def after_update_redirect_path + admin_settings_registrations_path + end +end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index dc1c79b7f..338a3638c 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -2,7 +2,7 @@ module Admin class SettingsController < BaseController - def edit + def show authorize :settings, :show? @admin_settings = Form::AdminSettings.new @@ -15,14 +15,18 @@ module Admin if @admin_settings.save flash[:notice] = I18n.t('generic.changes_saved_msg') - redirect_to edit_admin_settings_path + redirect_to after_update_redirect_path else - render :edit + render :show end end private + def after_update_redirect_path + raise NotImplementedError + end + def settings_params params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS) end diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb index cacecedb0..a5d2cf41c 100644 --- a/app/controllers/admin/site_uploads_controller.rb +++ b/app/controllers/admin/site_uploads_controller.rb @@ -9,7 +9,7 @@ module Admin @site_upload.destroy! - redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') + redirect_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') end private diff --git a/app/helpers/admin/settings_helper.rb b/app/helpers/admin/settings_helper.rb index baf14ab25..a133b4e7d 100644 --- a/app/helpers/admin/settings_helper.rb +++ b/app/helpers/admin/settings_helper.rb @@ -1,11 +1,4 @@ # frozen_string_literal: true module Admin::SettingsHelper - def site_upload_delete_hint(hint, var) - upload = SiteUpload.find_by(var: var.to_s) - return hint unless upload - - link = link_to t('admin.site_uploads.delete'), admin_site_upload_path(upload), data: { method: :delete } - safe_join([hint, link], '
'.html_safe) - end end diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 1c5494cde..affe1c79c 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -188,21 +188,70 @@ $content-width: 840px; padding-top: 30px; } - &-heading { - display: flex; + &__heading { padding-bottom: 36px; border-bottom: 1px solid lighten($ui-base-color, 8%); - margin: -15px -15px 40px 0; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; + margin-bottom: 40px; - & > * { - margin-top: 15px; - margin-right: 15px; + &__row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + margin: -15px -15px 0 0; + + & > * { + margin-top: 15px; + margin-right: 15px; + } } - &-actions { + &__tabs { + margin-top: 30px; + margin-bottom: -31px; + + & > div { + display: flex; + gap: 10px; + } + + a { + font-size: 14px; + display: inline-flex; + align-items: center; + padding: 7px 15px; + border-radius: 4px; + color: $darker-text-color; + text-decoration: none; + position: relative; + font-weight: 500; + gap: 5px; + white-space: nowrap; + + &.selected { + font-weight: 700; + color: $primary-text-color; + + &::after { + content: ""; + display: block; + width: 100%; + border-bottom: 1px solid $ui-highlight-color; + position: absolute; + bottom: -5px; + left: 0; + } + } + + &:hover, + &:focus, + &:active { + background: lighten($ui-base-color, 4%); + } + } + } + + &__actions { display: inline-flex; & > :not(:first-child) { @@ -228,11 +277,7 @@ $content-width: 840px; color: $secondary-text-color; font-size: 24px; line-height: 36px; - font-weight: 400; - - @media screen and (max-width: $no-columns-breakpoint) { - font-weight: 700; - } + font-weight: 700; } h3 { @@ -437,6 +482,11 @@ body, } } + & > div { + display: flex; + gap: 5px; + } + strong { font-weight: 500; text-transform: uppercase; @@ -1143,7 +1193,7 @@ a.name-tag, path:first-child { fill: rgba($highlight-text-color, 0.25) !important; - fill-opacity: 1 !important; + fill-opacity: 100% !important; } path:last-child { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 587eba663..5d0ff8536 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -29,6 +29,11 @@ background: transparent; padding: 0; cursor: pointer; + text-decoration: none; + + &--destructive { + color: $error-value-color; + } &:hover, &:active { diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 69a0b22d6..25c9c9fe5 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -254,7 +254,7 @@ code { & > label { font-family: inherit; - font-size: 16px; + font-size: 14px; color: $primary-text-color; display: block; font-weight: 500; @@ -291,6 +291,20 @@ code { .input:last-child { margin-bottom: 0; } + + &__thumbnail { + display: block; + margin: 0; + margin-bottom: 10px; + max-width: 100%; + height: auto; + border-radius: 4px; + background: url("images/void.png"); + + &:last-child { + margin-bottom: 0; + } + } } .fields-row { diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index b6bb3d795..957a32b7c 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -8,7 +8,6 @@ class Form::AdminSettings site_contact_email site_title site_short_description - site_description site_extended_description site_terms registrations_mode @@ -53,45 +52,55 @@ class Form::AdminSettings attr_accessor(*KEYS) - validates :site_short_description, :site_description, html: { wrap_with: :p } - validates :site_extended_description, :site_terms, :closed_registrations_message, html: true - validates :registrations_mode, inclusion: { in: %w(open approved none) } - validates :site_contact_email, :site_contact_username, presence: true - validates :site_contact_username, existing_username: true - validates :bootstrap_timeline_accounts, existing_username: { multiple: true } - validates :show_domain_blocks, inclusion: { in: %w(disabled users all) } - validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) } - validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true + validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) } + validates :site_contact_email, :site_contact_username, presence: true, if: -> { defined?(@site_contact_username) || defined?(@site_contact_email) } + validates :site_contact_username, existing_username: true, if: -> { defined?(@site_contact_username) } + validates :bootstrap_timeline_accounts, existing_username: { multiple: true }, if: -> { defined?(@bootstrap_timeline_accounts) } + validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) } + validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) } + validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) } + validates :site_short_description, length: { maximum: 200 }, if: -> { defined?(@site_short_description) } - def initialize(_attributes = {}) - super - initialize_attributes + KEYS.each do |key| + define_method(key) do + return instance_variable_get("@#{key}") if instance_variable_defined?("@#{key}") + + stored_value = begin + if UPLOAD_KEYS.include?(key) + SiteUpload.where(var: key).first_or_initialize(var: key) + else + Setting.public_send(key) + end + end + + instance_variable_set("@#{key}", stored_value) + end + end + + UPLOAD_KEYS.each do |key| + define_method("#{key}=") do |file| + value = public_send(key) + value.file = file + end end def save return false unless valid? KEYS.each do |key| - value = instance_variable_get("@#{key}") + next unless instance_variable_defined?("@#{key}") - if UPLOAD_KEYS.include?(key) && !value.nil? - upload = SiteUpload.where(var: key).first_or_initialize(var: key) - upload.update(file: value) + if UPLOAD_KEYS.include?(key) + public_send(key).save else setting = Setting.where(var: key).first_or_initialize(var: key) - setting.update(value: typecast_value(key, value)) + setting.update(value: typecast_value(key, instance_variable_get("@#{key}"))) end end end private - def initialize_attributes - KEYS.each do |key| - instance_variable_set("@#{key}", Setting.public_send(key)) if instance_variable_get("@#{key}").nil? - end - end - def typecast_value(key, value) if BOOLEAN_KEYS.include?(key) value == '1' diff --git a/app/serializers/rest/extended_description_serializer.rb b/app/serializers/rest/extended_description_serializer.rb index 0c3649033..c0fa3450d 100644 --- a/app/serializers/rest/extended_description_serializer.rb +++ b/app/serializers/rest/extended_description_serializer.rb @@ -8,6 +8,16 @@ class REST::ExtendedDescriptionSerializer < ActiveModel::Serializer end def content - object.text + if object.text.present? + markdown.render(object.text) + else + '' + end + end + + private + + def markdown + @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML) end end diff --git a/app/views/admin/settings/about/show.html.haml b/app/views/admin/settings/about/show.html.haml new file mode 100644 index 000000000..366d213f6 --- /dev/null +++ b/app/views/admin/settings/about/show.html.haml @@ -0,0 +1,33 @@ +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :page_title do + = t('admin.settings.about.title') + +- content_for :heading do + %h2= t('admin.settings.title') + = render partial: 'admin/settings/shared/links' + += simple_form_for @admin_settings, url: admin_settings_about_path, html: { method: :patch } do |f| + = render 'shared/error_messages', object: @admin_settings + + %p.lead= t('admin.settings.about.preamble') + + .fields-group + = f.input :site_extended_description, wrapper: :with_block_label, as: :text, input_html: { rows: 8 } + + %p.hint + = t 'admin.settings.about.rules_hint' + = link_to t('admin.settings.about.manage_rules'), admin_rules_path + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + .fields-row__column.fields-row__column-6.fields-group + = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + + .fields-group + = f.input :site_terms, wrapper: :with_block_label, as: :text, input_html: { rows: 8 } + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml new file mode 100644 index 000000000..d321c4b04 --- /dev/null +++ b/app/views/admin/settings/appearance/show.html.haml @@ -0,0 +1,34 @@ +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :page_title do + = t('admin.settings.appearance.title') + +- content_for :heading do + %h2= t('admin.settings.title') + = render partial: 'admin/settings/shared/links' + += simple_form_for @admin_settings, url: admin_settings_appearance_path, html: { method: :patch } do |f| + = render 'shared/error_messages', object: @admin_settings + + %p.lead= t('admin.settings.appearance.preamble') + + .fields-group + = f.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false + + .fields-group + = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 } + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :mascot, as: :file, wrapper: :with_block_label + + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.mascot.persisted? + = image_tag @admin_settings.mascot.file.url, class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.mascot), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml new file mode 100644 index 000000000..74a6fadf9 --- /dev/null +++ b/app/views/admin/settings/branding/show.html.haml @@ -0,0 +1,39 @@ +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :page_title do + = t('admin.settings.branding.title') + +- content_for :heading do + %h2= t('admin.settings.title') + = render partial: 'admin/settings/shared/links' + += simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f| + = render 'shared/error_messages', object: @admin_settings + + %p.lead= t('admin.settings.branding.preamble') + + .fields-group + = f.input :site_title, wrapper: :with_label + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :site_contact_username, wrapper: :with_label + .fields-row__column.fields-row__column-6.fields-group + = f.input :site_contact_email, wrapper: :with_label + + .fields-group + = f.input :site_short_description, wrapper: :with_block_label, as: :text, input_html: { rows: 2, maxlength: 200 } + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :thumbnail, as: :file, wrapper: :with_block_label + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.thumbnail.persisted? + = image_tag @admin_settings.thumbnail.file.url(:'@1x'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.thumbnail), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml new file mode 100644 index 000000000..36856127f --- /dev/null +++ b/app/views/admin/settings/content_retention/show.html.haml @@ -0,0 +1,22 @@ +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :page_title do + = t('admin.settings.content_retention.title') + +- content_for :heading do + %h2= t('admin.settings.title') + = render partial: 'admin/settings/shared/links' + += simple_form_for @admin_settings, url: admin_settings_content_retention_path, html: { method: :patch } do |f| + = render 'shared/error_messages', object: @admin_settings + + %p.lead= t('admin.settings.content_retention.preamble') + + .fields-group + = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/discovery/show.html.haml b/app/views/admin/settings/discovery/show.html.haml new file mode 100644 index 000000000..e63c853fb --- /dev/null +++ b/app/views/admin/settings/discovery/show.html.haml @@ -0,0 +1,40 @@ +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :page_title do + = t('admin.settings.discovery.title') + +- content_for :heading do + %h2= t('admin.settings.title') + = render partial: 'admin/settings/shared/links' + += simple_form_for @admin_settings, url: admin_settings_discovery_path, html: { method: :patch } do |f| + = render 'shared/error_messages', object: @admin_settings + + %p.lead= t('admin.settings.discovery.preamble') + + %h4= t('admin.settings.discovery.trends') + + .fields-group + = f.input :trends, as: :boolean, wrapper: :with_label + + .fields-group + = f.input :trendable_by_default, as: :boolean, wrapper: :with_label + + %h4= t('admin.settings.discovery.public_timelines') + + .fields-group + = f.input :timeline_preview, as: :boolean, wrapper: :with_label + + %h4= t('admin.settings.discovery.follow_recommendations') + + .fields-group + = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label + + %h4= t('admin.settings.discovery.profile_directory') + + .fields-group + = f.input :profile_directory, as: :boolean, wrapper: :with_label + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml deleted file mode 100644 index 15b1a2498..000000000 --- a/app/views/admin/settings/edit.html.haml +++ /dev/null @@ -1,102 +0,0 @@ -- content_for :header_tags do - = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' - -- content_for :page_title do - = t('admin.settings.title') - - - content_for :heading_actions do - = button_tag t('generic.save_changes'), class: 'button', form: 'edit_admin' - -= simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch, id: 'edit_admin' } do |f| - = render 'shared/error_messages', object: @admin_settings - - .fields-group - = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title') - - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :theme, collection: Themes.instance.names, label: t('simple_form.labels.defaults.setting_theme'), label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false - .fields-row__column.fields-row__column-6.fields-group - = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, label: t('admin.settings.registrations_mode.title'), include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") } - - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :site_contact_username, wrapper: :with_label, label: t('admin.settings.contact_information.username') - .fields-row__column.fields-row__column-6.fields-group - = f.input :site_contact_email, wrapper: :with_label, label: t('admin.settings.contact_information.email') - - .fields-group - = f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 } - - .fields-group - = f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 2 } - - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :thumbnail, as: :file, wrapper: :with_block_label, label: t('admin.settings.thumbnail.title'), hint: site_upload_delete_hint(t('admin.settings.thumbnail.desc_html'), :thumbnail) - - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :mascot, as: :file, wrapper: :with_block_label, label: t('admin.settings.mascot.title'), hint: site_upload_delete_hint(t('admin.settings.mascot.desc_html'), :mascot) - - %hr.spacer/ - - .fields-group - = f.input :require_invite_text, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.require_invite_text.title'), hint: t('admin.settings.registrations.require_invite_text.desc_html'), disabled: !approved_registrations? - - %hr.spacer/ - - .fields-group - = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html') - - %hr.spacer/ - - - unless whitelist_mode? - .fields-group - = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html') - - - unless whitelist_mode? - .fields-group - = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html'), recommended: true - - .fields-group - = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html'), recommended: true - - .fields-group - = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html') - - .fields-group - = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html') - - .fields-group - = f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html') - - .fields-group - = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, label: t('admin.settings.trendable_by_default.title'), hint: t('admin.settings.trendable_by_default.desc_html'), recommended: :not_recommended - - .fields-group - = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html') - - %hr.spacer/ - - .fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' - .fields-row__column.fields-row__column-6.fields-group - = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' - - .fields-group - = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode? - = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 } - = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 } - = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html') - - %hr.spacer/ - - .fields-group - = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } - = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } - = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } - - .actions - = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml new file mode 100644 index 000000000..0129332d7 --- /dev/null +++ b/app/views/admin/settings/registrations/show.html.haml @@ -0,0 +1,27 @@ +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :page_title do + = t('admin.settings.registrations.title') + +- content_for :heading do + %h2= t('admin.settings.title') + = render partial: 'admin/settings/shared/links' + += simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f| + = render 'shared/error_messages', object: @admin_settings + + %p.lead= t('admin.settings.registrations.preamble') + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") } + + .fields-row__column.fields-row__column-6.fields-group + = f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations? + + .fields-group + = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, input_html: { rows: 2 } + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/shared/_links.html.haml b/app/views/admin/settings/shared/_links.html.haml new file mode 100644 index 000000000..1294c26ce --- /dev/null +++ b/app/views/admin/settings/shared/_links.html.haml @@ -0,0 +1,8 @@ +.content__heading__tabs + = render_navigation renderer: :links do |primary| + - primary.item :branding, safe_join([fa_icon('pencil fw'), t('admin.settings.branding.title')]), admin_settings_branding_path + - primary.item :about, safe_join([fa_icon('file-text fw'), t('admin.settings.about.title')]), admin_settings_about_path + - primary.item :registrations, safe_join([fa_icon('users fw'), t('admin.settings.registrations.title')]), admin_settings_registrations_path + - primary.item :discovery, safe_join([fa_icon('search fw'), t('admin.settings.discovery.title')]), admin_settings_discovery_path + - primary.item :content_retention, safe_join([fa_icon('history fw'), t('admin.settings.content_retention.title')]), admin_settings_content_retention_path + - primary.item :appearance, safe_join([fa_icon('desktop fw'), t('admin.settings.appearance.title')]), admin_settings_appearance_path diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index e577b9803..59021ad88 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -22,15 +22,16 @@ .content-wrapper .content - .content-heading + .content__heading - if content_for?(:heading) = yield :heading - else - %h2= yield :page_title + .content__heading__row + %h2= yield :page_title - - if :heading_actions - .content-heading-actions - = yield :heading_actions + - if content_for?(:heading_actions) + .content__heading__actions + = yield :heading_actions = render 'application/flashes' diff --git a/config/locales/en.yml b/config/locales/en.yml index 412178ca3..70850d478 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -667,79 +667,40 @@ en: empty: No server rules have been defined yet. title: Server rules settings: - activity_api_enabled: - desc_html: Counts of locally published posts, active users, and new registrations in weekly buckets - title: Publish aggregate statistics about user activity in the API - bootstrap_timeline_accounts: - desc_html: Separate multiple usernames by comma. These accounts will be guaranteed to be shown in follow recommendations - title: Recommend these accounts to new users - contact_information: - email: Business e-mail - username: Contact username - custom_css: - desc_html: Modify the look with CSS loaded on every page - title: Custom CSS - default_noindex: - desc_html: Affects all users who have not changed this setting themselves - title: Opt users out of search engine indexing by default + about: + manage_rules: Manage server rules + preamble: Provide in-depth information about how the server is operated, moderated, funded. + rules_hint: There is a dedicated area for rules that your users are expected to adhere to. + title: About + appearance: + preamble: Customize Mastodon's web interface. + title: Appearance + branding: + preamble: Your server's branding differentiates it from other servers in the network. This information may be displayed across a variety of environments, such as Mastodon's web interface, native applications, in link previews on other websites and within messaging apps, and so on. For this reason, it is best to keep this information clear, short and concise. + title: Branding + content_retention: + preamble: Control how user-generated content is stored in Mastodon. + title: Content retention + discovery: + follow_recommendations: Follow recommendations + preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone Mastodon. Control how various discovery features work on your server. + profile_directory: Profile directory + public_timelines: Public timelines + title: Discovery + trends: Trends domain_blocks: all: To everyone disabled: To no one - title: Show domain blocks users: To logged-in local users - domain_blocks_rationale: - title: Show rationale - mascot: - desc_html: Displayed on multiple pages. At least 293×205px recommended. When not set, falls back to default mascot - title: Mascot image - peers_api_enabled: - desc_html: Domain names this server has encountered in the fediverse - title: Publish list of discovered servers in the API - preview_sensitive_media: - desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive - title: Show sensitive media in OpenGraph previews - profile_directory: - desc_html: Allow users to be discoverable - title: Enable profile directory registrations: - closed_message: - desc_html: Displayed on frontpage when registrations are closed. You can use HTML tags - title: Closed registration message - require_invite_text: - desc_html: When registrations require manual approval, make the “Why do you want to join?” text input mandatory rather than optional - title: Require new users to enter a reason to join + preamble: Control who can create an account on your server. + title: Registrations registrations_mode: modes: approved: Approval required for sign up none: Nobody can sign up open: Anyone can sign up - title: Registrations mode - site_description: - desc_html: Introductory paragraph on the API. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <a> and <em>. - title: Server description - site_description_extended: - desc_html: A good place for your code of conduct, rules, guidelines and other things that set your server apart. You can use HTML tags - title: Custom extended information - site_short_description: - desc_html: Displayed in sidebar and meta tags. Describe what Mastodon is and what makes this server special in a single paragraph. - title: Short server description - site_terms: - desc_html: You can write your own privacy policy. You can use HTML tags - title: Custom privacy policy - site_title: Server name - thumbnail: - desc_html: Used for previews via OpenGraph and API. 1200x630px recommended - title: Server thumbnail - timeline_preview: - desc_html: Display link to public timeline on landing page and allow API access to the public timeline without authentication - title: Allow unauthenticated access to public timeline - title: Site settings - trendable_by_default: - desc_html: Specific trending content can still be explicitly disallowed - title: Allow trends without prior review - trends: - desc_html: Publicly display previously reviewed content that is currently trending - title: Trends + title: Server Settings site_uploads: delete: Delete uploaded file destroyed_msg: Site upload successfully deleted! diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index db5b45e41..64281d7b7 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -75,8 +75,25 @@ en: warn: Hide the filtered content behind a warning mentioning the filter's title form_admin_settings: backups_retention_period: Keep generated user archives for the specified number of days. + bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. + closed_registrations_message: Displayed when sign-ups are closed content_cache_retention_period: Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible. + custom_css: You can apply custom styles on the web version of Mastodon. + mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. + profile_directory: The profile directory lists all users who have opted-in to be discoverable. + require_invite_text: When sign-ups require manual approval, make the “Why do you want to join?” text input mandatory rather than optional + site_contact_email: How people can reach you for legal or support inquiries. + site_contact_username: How people can reach you on Mastodon. + site_extended_description: Any additional information that may be useful to visitors and your users. Can be structured with Markdown syntax. + site_short_description: A short description to help uniquely identify your server. Who is running it, who is it for? + site_terms: Use your own privacy policy or leave blank to use the default. Can be structured with Markdown syntax. + site_title: How people may refer to your server besides its domain name. + theme: Theme that logged out visitors and new users see. + thumbnail: A roughly 2:1 image displayed alongside your server information. + timeline_preview: Logged out visitors will be able to browse the most recent public posts available on the server. + trendable_by_default: Skip manual review of trending content. Individual items can still be removed from trends after the fact. + trends: Trends show which posts, hashtags and news stories are gaining traction on your server. form_challenge: current_password: You are entering a secure area imports: @@ -213,8 +230,28 @@ en: warn: Hide with a warning form_admin_settings: backups_retention_period: User archive retention period + bootstrap_timeline_accounts: Always recommend these accounts to new users + closed_registrations_message: Custom message when sign-ups are not available content_cache_retention_period: Content cache retention period + custom_css: Custom CSS + mascot: Custom mascot (legacy) media_cache_retention_period: Media cache retention period + profile_directory: Enable profile directory + registrations_mode: Who can sign-up + require_invite_text: Require a reason to join + show_domain_blocks: Show domain blocks + show_domain_blocks_rationale: Show why domains were blocked + site_contact_email: Contact e-mail + site_contact_username: Contact username + site_extended_description: Extended description + site_short_description: Server description + site_terms: Privacy Policy + site_title: Server name + theme: Default theme + thumbnail: Server thumbnail + timeline_preview: Allow unauthenticated access to public timelines + trendable_by_default: Allow trends without prior review + trends: Enable trends interactions: must_be_follower: Block notifications from non-followers must_be_following: Block notifications from people you don't follow diff --git a/config/navigation.rb b/config/navigation.rb index 706de0471..e901fb932 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -52,7 +52,7 @@ SimpleNavigation::Configuration.run do |navigation| n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), nil, if: -> { current_user.can?(:view_dashboard, :manage_settings, :manage_rules, :manage_announcements, :manage_custom_emojis, :manage_webhooks, :manage_federation) } do |s| s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_path, if: -> { current_user.can?(:view_dashboard) } - s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings} + s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings} s.item :rules, safe_join([fa_icon('gavel fw'), t('admin.rules.title')]), admin_rules_path, highlights_on: %r{/admin/rules}, if: -> { current_user.can?(:manage_rules) } s.item :roles, safe_join([fa_icon('vcard fw'), t('admin.roles.title')]), admin_roles_path, highlights_on: %r{/admin/roles}, if: -> { current_user.can?(:manage_roles) } s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}, if: -> { current_user.can?(:manage_announcements) } diff --git a/config/routes.rb b/config/routes.rb index 1ed585f19..b44479e77 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -241,7 +241,18 @@ Rails.application.routes.draw do end end - resource :settings, only: [:edit, :update] + get '/settings', to: redirect('/admin/settings/branding') + get '/settings/edit', to: redirect('/admin/settings/branding') + + namespace :settings do + resource :branding, only: [:show, :update], controller: 'branding' + resource :registrations, only: [:show, :update], controller: 'registrations' + resource :content_retention, only: [:show, :update], controller: 'content_retention' + resource :about, only: [:show, :update], controller: 'about' + resource :appearance, only: [:show, :update], controller: 'appearance' + resource :discovery, only: [:show, :update], controller: 'discovery' + end + resources :site_uploads, only: [:destroy] resources :invites, only: [:index, :create, :destroy] do diff --git a/spec/controllers/admin/settings/branding_controller_spec.rb b/spec/controllers/admin/settings/branding_controller_spec.rb new file mode 100644 index 000000000..ee1c441bc --- /dev/null +++ b/spec/controllers/admin/settings/branding_controller_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Admin::Settings::BrandingController, type: :controller do + render_views + + describe 'When signed in as an admin' do + before do + sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user + end + + describe 'GET #show' do + it 'returns http success' do + get :show + + expect(response).to have_http_status(200) + end + end + + describe 'PUT #update' do + before do + allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true) + end + + around do |example| + before = Setting.site_short_description + Setting.site_short_description = nil + example.run + Setting.site_short_description = before + Setting.new_setting_key = nil + end + + it 'cannot create a setting value for a non-admin key' do + expect(Setting.new_setting_key).to be_blank + + patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } } + + expect(response).to redirect_to(admin_settings_branding_path) + expect(Setting.new_setting_key).to be_nil + end + + it 'creates a settings value that didnt exist before for eligible key' do + expect(Setting.site_short_description).to be_blank + + patch :update, params: { form_admin_settings: { site_short_description: 'New key value' } } + + expect(response).to redirect_to(admin_settings_branding_path) + expect(Setting.site_short_description).to eq 'New key value' + end + end + end +end diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb deleted file mode 100644 index 46749f76c..000000000 --- a/spec/controllers/admin/settings_controller_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::SettingsController, type: :controller do - render_views - - describe 'When signed in as an admin' do - before do - sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user - end - - describe 'GET #edit' do - it 'returns http success' do - get :edit - - expect(response).to have_http_status(200) - end - end - - describe 'PUT #update' do - before do - allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true) - end - - describe 'for a record that doesnt exist' do - around do |example| - before = Setting.site_extended_description - Setting.site_extended_description = nil - example.run - Setting.site_extended_description = before - Setting.new_setting_key = nil - end - - it 'cannot create a setting value for a non-admin key' do - expect(Setting.new_setting_key).to be_blank - - patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } } - - expect(response).to redirect_to(edit_admin_settings_path) - expect(Setting.new_setting_key).to be_nil - end - - it 'creates a settings value that didnt exist before for eligible key' do - expect(Setting.site_extended_description).to be_blank - - patch :update, params: { form_admin_settings: { site_extended_description: 'New key value' } } - - expect(response).to redirect_to(edit_admin_settings_path) - expect(Setting.site_extended_description).to eq 'New key value' - end - end - - context do - around do |example| - site_title = Setting.site_title - example.run - Setting.site_title = site_title - end - - it 'updates a settings value' do - Setting.site_title = 'Original' - patch :update, params: { form_admin_settings: { site_title: 'New title' } } - - expect(response).to redirect_to(edit_admin_settings_path) - expect(Setting.site_title).to eq 'New title' - end - end - end - end -end -- cgit From 6f01111863bfb15b3574c95c8d60d59ae1d79772 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 25 Oct 2022 21:43:33 +0200 Subject: Fix wrong size of avatars in admin UI (#19457) --- app/helpers/home_helper.rb | 2 +- app/javascript/styles/mastodon/widgets.scss | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'app/helpers') diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index 4da68500a..f41104709 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -23,7 +23,7 @@ module HomeHelper else link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do content_tag(:div, class: 'account__avatar-wrapper') do - image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar') + image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar', width: 46, height: 46) end + content_tag(:span, class: 'display-name') do content_tag(:bdi) do diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 260a97c6d..0e39dc87b 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -359,8 +359,9 @@ vertical-align: initial !important; } - &__interrelationships { + tbody td.accounts-table__interrelationships { width: 21px; + padding-right: 16px; } .fa { -- cgit