From e58c36d308f7e7e26154837d53da0185a0db7f16 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 24 May 2023 12:38:50 +0200 Subject: [PATCH 1/6] Update inconsistent `defaultMessage` (#25114) --- .../mastodon/components/account.jsx | 2 +- app/javascript/mastodon/components/status.jsx | 2 +- .../mastodon/containers/domain_container.jsx | 2 +- .../mastodon/containers/status_container.jsx | 2 +- .../containers/header_container.jsx | 2 +- .../compose/components/action_bar.jsx | 2 +- .../features/getting_started/index.jsx | 2 +- .../report/components/status_check_box.jsx | 2 +- .../features/status/components/action_bar.jsx | 4 +-- .../status/components/detailed_status.jsx | 4 +-- .../mastodon/features/status/index.jsx | 2 +- .../features/ui/components/boost_modal.jsx | 4 +-- .../mastodon/locales/defaultMessages.json | 30 +++++++++---------- 13 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx index 07cb72be98..68a456c1b1 100644 --- a/app/javascript/mastodon/components/account.jsx +++ b/app/javascript/mastodon/components/account.jsx @@ -23,7 +23,7 @@ import { RelativeTimestamp } from './relative_timestamp'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, - requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, + requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' }, diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index d8a21f64a0..3f3c292ea3 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -59,7 +59,7 @@ export const defaultMediaVisibility = (status) => { const messages = defineMessages({ public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, - private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, + private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, edited: { id: 'status.edited', defaultMessage: 'Edited {date}' }, }); diff --git a/app/javascript/mastodon/containers/domain_container.jsx b/app/javascript/mastodon/containers/domain_container.jsx index 4828445c48..9a55b72cc3 100644 --- a/app/javascript/mastodon/containers/domain_container.jsx +++ b/app/javascript/mastodon/containers/domain_container.jsx @@ -19,7 +19,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch, { intl }) => ({ onBlockDomain (domain) { dispatch(openModal('CONFIRM', { - message: {domain} }} />, + message: {domain} }} />, confirm: intl.formatMessage(messages.blockDomainConfirm), onConfirm: () => dispatch(blockDomain(domain)), })); diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index 5e5f75d092..2d4baafa1b 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -59,7 +59,7 @@ const messages = defineMessages({ replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' }, editMessage: { id: 'confirmations.edit.message', defaultMessage: 'Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, - blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, + blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' }, }); const makeMapStateToProps = () => { diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx index 6cb8a105cc..6442a4f82f 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx @@ -28,7 +28,7 @@ import Header from '../components/header'; const messages = defineMessages({ cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, - blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, + blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' }, }); const makeMapStateToProps = () => { diff --git a/app/javascript/mastodon/features/compose/components/action_bar.jsx b/app/javascript/mastodon/features/compose/components/action_bar.jsx index 60914b4626..726b5aa30d 100644 --- a/app/javascript/mastodon/features/compose/components/action_bar.jsx +++ b/app/javascript/mastodon/features/compose/components/action_bar.jsx @@ -16,7 +16,7 @@ const messages = defineMessages({ lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, - domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, + domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, diff --git a/app/javascript/mastodon/features/getting_started/index.jsx b/app/javascript/mastodon/features/getting_started/index.jsx index 2d1ac6670d..e31ca79af1 100644 --- a/app/javascript/mastodon/features/getting_started/index.jsx +++ b/app/javascript/mastodon/features/getting_started/index.jsx @@ -34,7 +34,7 @@ const messages = defineMessages({ follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, - domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, + domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, diff --git a/app/javascript/mastodon/features/report/components/status_check_box.jsx b/app/javascript/mastodon/features/report/components/status_check_box.jsx index c22745ee5d..8125b5b3bb 100644 --- a/app/javascript/mastodon/features/report/components/status_check_box.jsx +++ b/app/javascript/mastodon/features/report/components/status_check_box.jsx @@ -17,7 +17,7 @@ import Option from './option'; const messages = defineMessages({ public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, - private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, + private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, }); diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index bd565c72c6..2ce94d9d84 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -38,9 +38,9 @@ const messages = defineMessages({ unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, embed: { id: 'status.embed', defaultMessage: 'Embed' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, - admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' }, + admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' }, admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, - copy: { id: 'status.copy', defaultMessage: 'Copy link to status' }, + copy: { id: 'status.copy', defaultMessage: 'Copy link to post' }, blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index 00fd0d2cd6..187e04ad17 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -26,8 +26,8 @@ import Card from './card'; const messages = defineMessages({ public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, - private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, - direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, + private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' }, + direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, }); class DetailedStatus extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 4c41958aae..1d26f7a69d 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -79,7 +79,7 @@ const messages = defineMessages({ detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, - blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, + blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' }, }); const makeMapStateToProps = () => { diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.jsx b/app/javascript/mastodon/features/ui/components/boost_modal.jsx index af6c08134a..ee87cf2cdc 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/boost_modal.jsx @@ -24,8 +24,8 @@ const messages = defineMessages({ reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, - private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, - direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, + private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' }, + direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, }); const mapStateToProps = state => { diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index c64a970ead..d446989ab6 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -57,7 +57,7 @@ "id": "account.unfollow" }, { - "defaultMessage": "Awaiting approval", + "defaultMessage": "Awaiting approval. Click to cancel follow request", "id": "account.requested" }, { @@ -721,7 +721,7 @@ "id": "privacy.unlisted.short" }, { - "defaultMessage": "Followers-only", + "defaultMessage": "Followers only", "id": "privacy.private.short" }, { @@ -792,7 +792,7 @@ "id": "confirmations.domain_block.confirm" }, { - "defaultMessage": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", + "defaultMessage": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.", "id": "confirmations.domain_block.message" } ], @@ -833,7 +833,7 @@ "id": "confirmations.edit.message" }, { - "defaultMessage": "Hide entire domain", + "defaultMessage": "Block entire domain", "id": "confirmations.domain_block.confirm" }, { @@ -980,7 +980,7 @@ "id": "confirmations.unfollow.confirm" }, { - "defaultMessage": "Hide entire domain", + "defaultMessage": "Block entire domain", "id": "confirmations.domain_block.confirm" }, { @@ -1412,7 +1412,7 @@ "id": "navigation_bar.blocks" }, { - "defaultMessage": "Hidden domains", + "defaultMessage": "Blocked domains", "id": "navigation_bar.domain_blocks" }, { @@ -2430,7 +2430,7 @@ "id": "navigation_bar.blocks" }, { - "defaultMessage": "Hidden domains", + "defaultMessage": "Blocked domains", "id": "navigation_bar.domain_blocks" }, { @@ -3535,7 +3535,7 @@ "id": "privacy.unlisted.short" }, { - "defaultMessage": "Followers-only", + "defaultMessage": "Followers only", "id": "privacy.private.short" }, { @@ -3743,7 +3743,7 @@ "id": "status.admin_account" }, { - "defaultMessage": "Open this status in the moderation interface", + "defaultMessage": "Open this post in the moderation interface", "id": "status.admin_status" }, { @@ -3751,7 +3751,7 @@ "id": "status.admin_domain" }, { - "defaultMessage": "Copy link to status", + "defaultMessage": "Copy link to post", "id": "status.copy" }, { @@ -3797,11 +3797,11 @@ "id": "privacy.unlisted.short" }, { - "defaultMessage": "Followers-only", + "defaultMessage": "Followers only", "id": "privacy.private.short" }, { - "defaultMessage": "Direct", + "defaultMessage": "Mentioned people only", "id": "privacy.direct.short" }, { @@ -3854,7 +3854,7 @@ "id": "confirmations.reply.message" }, { - "defaultMessage": "Hide entire domain", + "defaultMessage": "Block entire domain", "id": "confirmations.domain_block.confirm" }, { @@ -3925,11 +3925,11 @@ "id": "privacy.unlisted.short" }, { - "defaultMessage": "Followers-only", + "defaultMessage": "Followers only", "id": "privacy.private.short" }, { - "defaultMessage": "Direct", + "defaultMessage": "Mentioned people only", "id": "privacy.direct.short" }, { From 1d588d58f123e97d4a864ee9f595fb8dcdf24a82 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 25 May 2023 09:27:16 +0200 Subject: [PATCH 2/6] Improve various queries against account domains (#25126) --- app/models/account.rb | 2 +- app/models/instance.rb | 1 + ...524190515_add_index_accounts_on_domain_and_id.rb | 9 +++++++++ .../20230524192812_fix_account_domain_casing.rb | 13 +++++++++++++ ...4194155_add_index_instances_on_reverse_domain.rb | 9 +++++++++ db/schema.rb | 4 +++- lib/tasks/tests.rake | 7 ++++++- 7 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20230524190515_add_index_accounts_on_domain_and_id.rb create mode 100644 db/migrate/20230524192812_fix_account_domain_casing.rb create mode 100644 db/migrate/20230524194155_add_index_instances_on_reverse_domain.rb diff --git a/app/models/account.rb b/app/models/account.rb index f17d06be58..8530e664c7 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -119,7 +119,7 @@ class Account < ApplicationRecord scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) } scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) } scope :popular, -> { order('account_stats.followers_count desc') } - scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) } + scope :by_domain_and_subdomains, ->(domain) { where(domain: Instance.by_domain_and_subdomain(domain).select(:domain)) } scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) } scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) } diff --git a/app/models/instance.rb b/app/models/instance.rb index 95231c52a7..a80e91e94c 100644 --- a/app/models/instance.rb +++ b/app/models/instance.rb @@ -22,6 +22,7 @@ class Instance < ApplicationRecord end scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } + scope :by_domain_and_subdomain, ->(domain) { where("reverse('.' || domain) LIKE reverse(?)", "%.#{domain}") } def self.refresh Scenic.database.refresh_materialized_view(table_name, concurrently: true, cascade: false) diff --git a/db/migrate/20230524190515_add_index_accounts_on_domain_and_id.rb b/db/migrate/20230524190515_add_index_accounts_on_domain_and_id.rb new file mode 100644 index 0000000000..4ee52b0ca8 --- /dev/null +++ b/db/migrate/20230524190515_add_index_accounts_on_domain_and_id.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddIndexAccountsOnDomainAndId < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def change + add_index :accounts, [:domain, :id], name: :index_accounts_on_domain_and_id, algorithm: :concurrently + end +end diff --git a/db/migrate/20230524192812_fix_account_domain_casing.rb b/db/migrate/20230524192812_fix_account_domain_casing.rb new file mode 100644 index 0000000000..e1edf23749 --- /dev/null +++ b/db/migrate/20230524192812_fix_account_domain_casing.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class FixAccountDomainCasing < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def up + safety_assured do + execute 'UPDATE accounts SET domain = lower(domain) WHERE domain IS NOT NULL AND domain != lower(domain)' + end + end + + def down; end +end diff --git a/db/migrate/20230524194155_add_index_instances_on_reverse_domain.rb b/db/migrate/20230524194155_add_index_instances_on_reverse_domain.rb new file mode 100644 index 0000000000..c3e774936c --- /dev/null +++ b/db/migrate/20230524194155_add_index_instances_on_reverse_domain.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddIndexInstancesOnReverseDomain < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def change + add_index :instances, "reverse('.' || domain), domain", name: :index_instances_on_reverse_domain, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index 8a32e45ce0..f452fef7ba 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_03_30_155710) do +ActiveRecord::Schema.define(version: 2023_05_24_194155) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -188,6 +188,7 @@ ActiveRecord::Schema.define(version: 2023_03_30_155710) do t.datetime "requested_review_at" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true + t.index ["domain", "id"], name: "index_accounts_on_domain_and_id" t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id", where: "(moved_to_account_id IS NOT NULL)" t.index ["uri"], name: "index_accounts_on_uri" t.index ["url"], name: "index_accounts_on_url", opclass: :text_pattern_ops, where: "(url IS NOT NULL)" @@ -1283,6 +1284,7 @@ ActiveRecord::Schema.define(version: 2023_03_30_155710) do FROM (domain_allows LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_allows.domain)::text))); SQL + add_index "instances", "reverse(('.'::text || (domain)::text)), domain", name: "index_instances_on_reverse_domain" add_index "instances", ["domain"], name: "index_instances_on_domain", unique: true create_view "user_ips", sql_definition: <<-SQL diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake index 60399c9de1..3c88ce4509 100644 --- a/lib/tasks/tests.rake +++ b/lib/tasks/tests.rake @@ -58,6 +58,11 @@ namespace :tests do puts 'User settings not kept as expected' exit(1) end + + unless Account.find_remote('bob', 'ActivityPub.com').domain == 'activitypub.com' + puts 'Account domains not properly normalized' + exit(1) + end end desc 'Populate the database with test data for 2.4.3' @@ -160,7 +165,7 @@ namespace :tests do INSERT INTO "accounts" (id, username, domain, private_key, public_key, created_at, updated_at, protocol, inbox_url, outbox_url, followers_url) VALUES - (6, 'bob', 'activitypub.com', NULL, #{remote_public_key_ap}, now(), now(), + (6, 'bob', 'ActivityPub.com', NULL, #{remote_public_key_ap}, now(), now(), 1, 'https://activitypub.com/users/bob/inbox', 'https://activitypub.com/users/bob/outbox', 'https://activitypub.com/users/bob/followers'); INSERT INTO "accounts" From 4197b5e4c8d01806bd974d97a3b1e38eb3208f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?= Date: Thu, 25 May 2023 20:03:37 +0900 Subject: [PATCH 3/6] Fix export style of `` based on `` (#25128) --- .../mastodon/components/load_gap.tsx | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/javascript/mastodon/components/load_gap.tsx b/app/javascript/mastodon/components/load_gap.tsx index f741f68341..e6d3060eb3 100644 --- a/app/javascript/mastodon/components/load_gap.tsx +++ b/app/javascript/mastodon/components/load_gap.tsx @@ -16,21 +16,21 @@ interface Props { intl: InjectedIntl; } -export const LoadGap = injectIntl( - ({ disabled, maxId, onClick, intl }) => { - const handleClick = useCallback(() => { - onClick(maxId); - }, [maxId, onClick]); +const _LoadGap: React.FC = ({ disabled, maxId, onClick, intl }) => { + const handleClick = useCallback(() => { + onClick(maxId); + }, [maxId, onClick]); - return ( - - ); - } -); + return ( + + ); +}; + +export const LoadGap = injectIntl(_LoadGap); From 38c6216082e67581e83d04b3096cdb020ad0edea Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com> Date: Thu, 25 May 2023 22:42:37 +0900 Subject: [PATCH 4/6] Rewrite actions/modal and reducers/modal with typescript (#24833) --- app/javascript/mastodon/actions/blocks.js | 2 +- app/javascript/mastodon/actions/boosts.js | 5 +- app/javascript/mastodon/actions/compose.js | 5 +- app/javascript/mastodon/actions/filters.js | 9 +- app/javascript/mastodon/actions/modal.js | 18 ---- app/javascript/mastodon/actions/modal.ts | 17 ++++ app/javascript/mastodon/actions/mutes.js | 2 +- app/javascript/mastodon/actions/reports.js | 9 +- .../components/edited_timestamp/index.jsx | 5 +- .../mastodon/containers/account_container.jsx | 11 ++- .../mastodon/containers/domain_container.jsx | 11 ++- .../containers/dropdown_menu_container.js | 16 +++- .../mastodon/containers/status_container.jsx | 73 +++++++++----- .../features/account_gallery/index.jsx | 15 ++- .../containers/header_container.jsx | 67 ++++++++----- .../containers/navigation_container.js | 13 ++- .../containers/privacy_dropdown_container.js | 10 +- .../mastodon/features/compose/index.jsx | 13 ++- .../containers/conversation_container.js | 11 ++- .../directory/components/account_card.jsx | 35 ++++--- .../features/interaction_modal/index.jsx | 7 +- .../mastodon/features/list_timeline/index.jsx | 28 +++--- .../containers/column_settings_container.js | 11 ++- .../picture_in_picture/components/footer.jsx | 44 +++++---- .../containers/detailed_status_container.js | 41 +++++--- .../mastodon/features/status/index.jsx | 81 ++++++++++------ .../features/ui/components/block_modal.jsx | 5 +- .../ui/components/compare_history_modal.jsx | 5 +- .../ui/components/disabled_account_banner.jsx | 13 ++- .../features/ui/components/header.jsx | 2 +- .../features/ui/components/link_footer.jsx | 13 ++- .../features/ui/components/modal_root.jsx | 2 +- .../features/ui/components/mute_modal.jsx | 5 +- .../features/ui/components/sign_in_banner.jsx | 2 +- .../features/ui/containers/modal_container.js | 20 ++-- app/javascript/mastodon/reducers/index.ts | 4 +- app/javascript/mastodon/reducers/modal.js | 40 -------- app/javascript/mastodon/reducers/modal.ts | 94 +++++++++++++++++++ 38 files changed, 503 insertions(+), 261 deletions(-) delete mode 100644 app/javascript/mastodon/actions/modal.js create mode 100644 app/javascript/mastodon/actions/modal.ts delete mode 100644 app/javascript/mastodon/reducers/modal.js create mode 100644 app/javascript/mastodon/reducers/modal.ts diff --git a/app/javascript/mastodon/actions/blocks.js b/app/javascript/mastodon/actions/blocks.js index 66421ed455..e293657ad3 100644 --- a/app/javascript/mastodon/actions/blocks.js +++ b/app/javascript/mastodon/actions/blocks.js @@ -95,6 +95,6 @@ export function initBlockModal(account) { account, }); - dispatch(openModal('BLOCK')); + dispatch(openModal({ modalType: 'BLOCK' })); }; } diff --git a/app/javascript/mastodon/actions/boosts.js b/app/javascript/mastodon/actions/boosts.js index c0f0f3acc5..1fc2e391e2 100644 --- a/app/javascript/mastodon/actions/boosts.js +++ b/app/javascript/mastodon/actions/boosts.js @@ -14,7 +14,10 @@ export function initBoostModal(props) { privacy, }); - dispatch(openModal('BOOST', props)); + dispatch(openModal({ + modalType: 'BOOST', + modalProps: props, + })); }; } diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 22454cf3df..2ad7678caa 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -381,7 +381,10 @@ export function initMediaEditModal(id) { id, }); - dispatch(openModal('FOCAL_POINT', { id })); + dispatch(openModal({ + modalType: 'FOCAL_POINT', + modalProps: { id }, + })); }; } diff --git a/app/javascript/mastodon/actions/filters.js b/app/javascript/mastodon/actions/filters.js index 3774ee042a..a11956ac56 100644 --- a/app/javascript/mastodon/actions/filters.js +++ b/app/javascript/mastodon/actions/filters.js @@ -15,9 +15,12 @@ export const FILTERS_CREATE_SUCCESS = 'FILTERS_CREATE_SUCCESS'; export const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL'; export const initAddFilter = (status, { contextType }) => dispatch => - dispatch(openModal('FILTER', { - statusId: status?.get('id'), - contextType: contextType, + dispatch(openModal({ + modalType: 'FILTER', + modalProps: { + statusId: status?.get('id'), + contextType: contextType, + }, })); export const fetchFilters = () => (dispatch, getState) => { diff --git a/app/javascript/mastodon/actions/modal.js b/app/javascript/mastodon/actions/modal.js deleted file mode 100644 index ef2ae0e4c7..0000000000 --- a/app/javascript/mastodon/actions/modal.js +++ /dev/null @@ -1,18 +0,0 @@ -export const MODAL_OPEN = 'MODAL_OPEN'; -export const MODAL_CLOSE = 'MODAL_CLOSE'; - -export function openModal(type, props) { - return { - type: MODAL_OPEN, - modalType: type, - modalProps: props, - }; -} - -export function closeModal(type, options = { ignoreFocus: false }) { - return { - type: MODAL_CLOSE, - modalType: type, - ignoreFocus: options.ignoreFocus, - }; -} diff --git a/app/javascript/mastodon/actions/modal.ts b/app/javascript/mastodon/actions/modal.ts new file mode 100644 index 0000000000..af34f5d6af --- /dev/null +++ b/app/javascript/mastodon/actions/modal.ts @@ -0,0 +1,17 @@ +import { createAction } from '@reduxjs/toolkit'; + +import type { MODAL_COMPONENTS } from '../features/ui/components/modal_root'; + +export type ModalType = keyof typeof MODAL_COMPONENTS; + +interface OpenModalPayload { + modalType: ModalType; + modalProps: unknown; +} +export const openModal = createAction('MODAL_OPEN'); + +interface CloseModalPayload { + modalType: ModalType | undefined; + ignoreFocus: boolean; +} +export const closeModal = createAction('MODAL_CLOSE'); diff --git a/app/javascript/mastodon/actions/mutes.js b/app/javascript/mastodon/actions/mutes.js index e61a14af24..fb041078b8 100644 --- a/app/javascript/mastodon/actions/mutes.js +++ b/app/javascript/mastodon/actions/mutes.js @@ -97,7 +97,7 @@ export function initMuteModal(account) { account, }); - dispatch(openModal('MUTE')); + dispatch(openModal({ modalType: 'MUTE' })); }; } diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js index b3f9bf1ac3..756b8cd05e 100644 --- a/app/javascript/mastodon/actions/reports.js +++ b/app/javascript/mastodon/actions/reports.js @@ -7,9 +7,12 @@ export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS'; export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; export const initReport = (account, status) => dispatch => - dispatch(openModal('REPORT', { - accountId: account.get('id'), - statusId: status?.get('id'), + dispatch(openModal({ + modalType: 'REPORT', + modalProps: { + accountId: account.get('id'), + statusId: status?.get('id'), + }, })); export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => { diff --git a/app/javascript/mastodon/components/edited_timestamp/index.jsx b/app/javascript/mastodon/components/edited_timestamp/index.jsx index 42d3d93fe8..987b7c8272 100644 --- a/app/javascript/mastodon/components/edited_timestamp/index.jsx +++ b/app/javascript/mastodon/components/edited_timestamp/index.jsx @@ -15,7 +15,10 @@ import DropdownMenu from './containers/dropdown_menu_container'; const mapDispatchToProps = (dispatch, { statusId }) => ({ onItemClick (index) { - dispatch(openModal('COMPARE_HISTORY', { index, statusId })); + dispatch(openModal({ + modalType: 'COMPARE_HISTORY', + modalProps: { index, statusId }, + })); }, }); diff --git a/app/javascript/mastodon/containers/account_container.jsx b/app/javascript/mastodon/containers/account_container.jsx index 09a755e877..a134452e77 100644 --- a/app/javascript/mastodon/containers/account_container.jsx +++ b/app/javascript/mastodon/containers/account_container.jsx @@ -35,10 +35,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onFollow (account) { if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { if (unfollowModal) { - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.unfollowConfirm), + onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + }, })); } else { dispatch(unfollowAccount(account.get('id'))); diff --git a/app/javascript/mastodon/containers/domain_container.jsx b/app/javascript/mastodon/containers/domain_container.jsx index 9a55b72cc3..c719a5775c 100644 --- a/app/javascript/mastodon/containers/domain_container.jsx +++ b/app/javascript/mastodon/containers/domain_container.jsx @@ -18,10 +18,13 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch, { intl }) => ({ onBlockDomain (domain) { - dispatch(openModal('CONFIRM', { - message: {domain} }} />, - confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: {domain} }} />, + confirm: intl.formatMessage(messages.blockDomainConfirm), + onConfirm: () => dispatch(blockDomain(domain)), + }, })); }, diff --git a/app/javascript/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js index 5dd5273b27..6cf180cd53 100644 --- a/app/javascript/mastodon/containers/dropdown_menu_container.js +++ b/app/javascript/mastodon/containers/dropdown_menu_container.js @@ -18,15 +18,21 @@ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({ dispatch(fetchRelationships([status.getIn(['account', 'id'])])); } - dispatch(isUserTouching() ? openModal('ACTIONS', { - status, - actions: items, - onClick: onItemClick, + dispatch(isUserTouching() ? openModal({ + modalType: 'ACTIONS', + modalProps: { + status, + actions: items, + onClick: onItemClick, + }, }) : openDropdownMenu(id, keyboard, scrollKey)); }, onClose(id) { - dispatch(closeModal('ACTIONS')); + dispatch(closeModal({ + modalType: 'ACTIONS', + ignoreFocus: false, + })); dispatch(closeDropdownMenu(id)); }, }); diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index 2d4baafa1b..3026dde0a8 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -82,10 +82,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ let state = getState(); if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(status, router)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(status, router)) }, })); } else { dispatch(replyCompose(status, router)); @@ -134,9 +136,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ }, onEmbed (status) { - dispatch(openModal('EMBED', { - url: status.get('url'), - onError: error => dispatch(showAlertForError(error)), + dispatch(openModal({ + modalType: 'EMBED', + modalProps: { + url: status.get('url'), + onError: error => dispatch(showAlertForError(error)), + }, })); }, @@ -144,10 +149,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ if (!deleteModal) { dispatch(deleteStatus(status.get('id'), history, withRedraft)); } else { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), - confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), + confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), + onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), + }, })); } }, @@ -156,10 +164,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ dispatch((_, getState) => { let state = getState(); if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.editMessage), - confirm: intl.formatMessage(messages.editConfirm), - onConfirm: () => dispatch(editStatus(status.get('id'), history)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.editMessage), + confirm: intl.formatMessage(messages.editConfirm), + onConfirm: () => dispatch(editStatus(status.get('id'), history)), + }, })); } else { dispatch(editStatus(status.get('id'), history)); @@ -184,11 +195,17 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ }, onOpenMedia (statusId, media, index, lang) { - dispatch(openModal('MEDIA', { statusId, media, index, lang })); + dispatch(openModal({ + modalType: 'MEDIA', + modalProps: { statusId, media, index, lang }, + })); }, onOpenVideo (statusId, media, lang, options) { - dispatch(openModal('VIDEO', { statusId, media, lang, options })); + dispatch(openModal({ + modalType: 'VIDEO', + modalProps: { statusId, media, lang, options }, + })); }, onBlock (status) { @@ -237,10 +254,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ }, onBlockDomain (domain) { - dispatch(openModal('CONFIRM', { - message: {domain} }} />, - confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: {domain} }} />, + confirm: intl.formatMessage(messages.blockDomainConfirm), + onConfirm: () => dispatch(blockDomain(domain)), + }, })); }, @@ -253,10 +273,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ }, onInteractionModal (type, status) { - dispatch(openModal('INTERACTION', { - type, - accountId: status.getIn(['account', 'id']), - url: status.get('url'), + dispatch(openModal({ + modalType: 'INTERACTION', + modalProps: { + type, + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + }, })); }, diff --git a/app/javascript/mastodon/features/account_gallery/index.jsx b/app/javascript/mastodon/features/account_gallery/index.jsx index 16fb9ef577..27de4740ca 100644 --- a/app/javascript/mastodon/features/account_gallery/index.jsx +++ b/app/javascript/mastodon/features/account_gallery/index.jsx @@ -143,14 +143,23 @@ class AccountGallery extends ImmutablePureComponent { const lang = attachment.getIn(['status', 'language']); if (attachment.get('type') === 'video') { - dispatch(openModal('VIDEO', { media: attachment, statusId, lang, options: { autoPlay: true } })); + dispatch(openModal({ + modalType: 'VIDEO', + modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } }, + })); } else if (attachment.get('type') === 'audio') { - dispatch(openModal('AUDIO', { media: attachment, statusId, lang, options: { autoPlay: true } })); + dispatch(openModal({ + modalType: 'AUDIO', + modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } }, + })); } else { const media = attachment.getIn(['status', 'media_attachments']); const index = media.findIndex(x => x.get('id') === attachment.get('id')); - dispatch(openModal('MEDIA', { media, index, statusId, lang })); + dispatch(openModal({ + modalType: 'MEDIA', + modalProps: { media, index, statusId, lang }, + })); } }; diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx index 6442a4f82f..2b3a66c55e 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx @@ -48,20 +48,26 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onFollow (account) { if (account.getIn(['relationship', 'following'])) { if (unfollowModal) { - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.unfollowConfirm), + onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + }, })); } else { dispatch(unfollowAccount(account.get('id'))); } } else if (account.getIn(['relationship', 'requested'])) { if (unfollowModal) { - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), + onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + }, })); } else { dispatch(unfollowAccount(account.get('id'))); @@ -72,10 +78,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onInteractionModal (account) { - dispatch(openModal('INTERACTION', { - type: 'follow', - accountId: account.get('id'), - url: account.get('url'), + dispatch(openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'follow', + accountId: account.get('id'), + url: account.get('url'), + }, })); }, @@ -132,10 +141,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onBlockDomain (domain) { - dispatch(openModal('CONFIRM', { - message: {domain} }} />, - confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: {domain} }} />, + confirm: intl.formatMessage(messages.blockDomainConfirm), + onConfirm: () => dispatch(blockDomain(domain)), + }, })); }, @@ -144,21 +156,30 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onAddToList (account) { - dispatch(openModal('LIST_ADDER', { - accountId: account.get('id'), + dispatch(openModal({ + modalType: 'LIST_ADDER', + modalProps: { + accountId: account.get('id'), + }, })); }, onChangeLanguages (account) { - dispatch(openModal('SUBSCRIBED_LANGUAGES', { - accountId: account.get('id'), + dispatch(openModal({ + modalType: 'SUBSCRIBED_LANGUAGES', + modalProps: { + accountId: account.get('id'), + }, })); }, onOpenAvatar (account) { - dispatch(openModal('IMAGE', { - src: account.get('avatar'), - alt: account.get('acct'), + dispatch(openModal({ + modalType: 'IMAGE', + modalProps: { + src: account.get('avatar'), + alt: account.get('acct'), + }, })); }, diff --git a/app/javascript/mastodon/features/compose/containers/navigation_container.js b/app/javascript/mastodon/features/compose/containers/navigation_container.js index f881c4fa13..70b4026d14 100644 --- a/app/javascript/mastodon/features/compose/containers/navigation_container.js +++ b/app/javascript/mastodon/features/compose/containers/navigation_container.js @@ -21,11 +21,14 @@ const mapStateToProps = state => { const mapDispatchToProps = (dispatch, { intl }) => ({ onLogout () { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.logoutMessage), + confirm: intl.formatMessage(messages.logoutConfirm), + closeWhenConfirm: false, + onConfirm: () => logOut(), + }, })); }, }); diff --git a/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js index 1ca3fe550a..6d26abf4f6 100644 --- a/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js +++ b/app/javascript/mastodon/features/compose/containers/privacy_dropdown_container.js @@ -16,8 +16,14 @@ const mapDispatchToProps = dispatch => ({ }, isUserTouching, - onModalOpen: props => dispatch(openModal('ACTIONS', props)), - onModalClose: () => dispatch(closeModal()), + onModalOpen: props => dispatch(openModal({ + modalType: 'ACTIONS', + modalProps: props, + })), + onModalClose: () => dispatch(closeModal({ + modalType: undefined, + ignoreFocus: false, + })), }); diff --git a/app/javascript/mastodon/features/compose/index.jsx b/app/javascript/mastodon/features/compose/index.jsx index 1923558862..8997f1bc6c 100644 --- a/app/javascript/mastodon/features/compose/index.jsx +++ b/app/javascript/mastodon/features/compose/index.jsx @@ -71,11 +71,14 @@ class Compose extends PureComponent { e.preventDefault(); e.stopPropagation(); - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.logoutMessage), + confirm: intl.formatMessage(messages.logoutConfirm), + closeWhenConfirm: false, + onConfirm: () => logOut(), + }, })); return false; diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js index 978c031aab..456fc7d7cc 100644 --- a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js +++ b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js @@ -41,10 +41,13 @@ const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({ let state = getState(); if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(status, router)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(status, router)), + }, })); } else { dispatch(replyCompose(status, router)); diff --git a/app/javascript/mastodon/features/directory/components/account_card.jsx b/app/javascript/mastodon/features/directory/components/account_card.jsx index 87ddcf94f7..cf1c63f9e4 100644 --- a/app/javascript/mastodon/features/directory/components/account_card.jsx +++ b/app/javascript/mastodon/features/directory/components/account_card.jsx @@ -50,27 +50,32 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ if (account.getIn(['relationship', 'following'])) { if (unfollowModal) { dispatch( - openModal('CONFIRM', { - message: ( - @{account.get('acct')} }} - /> - ), - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - }), + openModal({ + modalType: 'CONFIRM', + modalProps: { + message: ( + @{account.get('acct')} }} + /> + ), + confirm: intl.formatMessage(messages.unfollowConfirm), + onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + } }), ); } else { dispatch(unfollowAccount(account.get('id'))); } } else if (account.getIn(['relationship', 'requested'])) { if (unfollowModal) { - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), + onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + }, })); } else { dispatch(unfollowAccount(account.get('id'))); diff --git a/app/javascript/mastodon/features/interaction_modal/index.jsx b/app/javascript/mastodon/features/interaction_modal/index.jsx index db6a3c4a53..be03b8f5fe 100644 --- a/app/javascript/mastodon/features/interaction_modal/index.jsx +++ b/app/javascript/mastodon/features/interaction_modal/index.jsx @@ -18,8 +18,11 @@ const mapStateToProps = (state, { accountId }) => ({ const mapDispatchToProps = (dispatch) => ({ onSignupClick() { - dispatch(closeModal()); - dispatch(openModal('CLOSED_REGISTRATIONS')); + dispatch(closeModal({ + modalType: undefined, + ignoreFocus: false, + })); + dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' })); }, }); diff --git a/app/javascript/mastodon/features/list_timeline/index.jsx b/app/javascript/mastodon/features/list_timeline/index.jsx index 1d80a9cd50..f41e8e6f23 100644 --- a/app/javascript/mastodon/features/list_timeline/index.jsx +++ b/app/javascript/mastodon/features/list_timeline/index.jsx @@ -114,24 +114,30 @@ class ListTimeline extends PureComponent { }; handleEditClick = () => { - this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id })); + this.props.dispatch(openModal({ + modalType: 'LIST_EDITOR', + modalProps: { listId: this.props.params.id }, + })); }; handleDeleteClick = () => { const { dispatch, columnId, intl } = this.props; const { id } = this.props.params; - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => { - dispatch(deleteList(id)); + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.deleteMessage), + confirm: intl.formatMessage(messages.deleteConfirm), + onConfirm: () => { + dispatch(deleteList(id)); - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - this.context.router.history.push('/lists'); - } + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + this.context.router.history.push('/lists'); + } + }, }, })); }; diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js index 292767badb..b63796a8b2 100644 --- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js @@ -59,10 +59,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onClear () { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.clearMessage), - confirm: intl.formatMessage(messages.clearConfirm), - onConfirm: () => dispatch(clearNotifications()), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.clearMessage), + confirm: intl.formatMessage(messages.clearConfirm), + onConfirm: () => dispatch(clearNotifications()), + }, })); }, diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx index 1743edae99..c167d93dce 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx @@ -74,19 +74,25 @@ class Footer extends ImmutablePureComponent { if (signedIn) { if (askReplyConfirmation) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: this._performReply, + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: this._performReply, + }, })); } else { this._performReply(); } } else { - dispatch(openModal('INTERACTION', { - type: 'reply', - accountId: status.getIn(['account', 'id']), - url: status.get('url'), + dispatch(openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'reply', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + }, })); } }; @@ -102,10 +108,13 @@ class Footer extends ImmutablePureComponent { dispatch(favourite(status)); } } else { - dispatch(openModal('INTERACTION', { - type: 'favourite', - accountId: status.getIn(['account', 'id']), - url: status.get('url'), + dispatch(openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'favourite', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + }, })); } }; @@ -128,10 +137,13 @@ class Footer extends ImmutablePureComponent { dispatch(initBoostModal({ status, onReblog: this._performReblog })); } } else { - dispatch(openModal('INTERACTION', { - type: 'reblog', - accountId: status.getIn(['account', 'id']), - url: status.get('url'), + dispatch(openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'reblog', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + }, })); } }; diff --git a/app/javascript/mastodon/features/status/containers/detailed_status_container.js b/app/javascript/mastodon/features/status/containers/detailed_status_container.js index ddae8c0283..e76790a9f6 100644 --- a/app/javascript/mastodon/features/status/containers/detailed_status_container.js +++ b/app/javascript/mastodon/features/status/containers/detailed_status_container.js @@ -60,10 +60,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch((_, getState) => { let state = getState(); if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(status, router)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(status, router)), + }, })); } else { dispatch(replyCompose(status, router)); @@ -104,9 +107,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onEmbed (status) { - dispatch(openModal('EMBED', { - url: status.get('url'), - onError: error => dispatch(showAlertForError(error)), + dispatch(openModal({ + modalType: 'EMBED', + modalProps: { + url: status.get('url'), + onError: error => dispatch(showAlertForError(error)), + }, })); }, @@ -114,10 +120,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ if (!deleteModal) { dispatch(deleteStatus(status.get('id'), history, withRedraft)); } else { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), - confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), + confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), + onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), + }, })); } }, @@ -131,11 +140,17 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onOpenMedia (media, index, lang) { - dispatch(openModal('MEDIA', { media, index, lang })); + dispatch(openModal({ + modalType: 'MEDIA', + modalProps: { media, index, lang }, + })); }, onOpenVideo (media, lang, options) { - dispatch(openModal('VIDEO', { media, lang, options })); + dispatch(openModal({ + modalType: 'VIDEO', + modalProps: { media, lang, options }, + })); }, onBlock (status) { diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 1d26f7a69d..0ed94d34c0 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -251,10 +251,13 @@ class Status extends ImmutablePureComponent { dispatch(favourite(status)); } } else { - dispatch(openModal('INTERACTION', { - type: 'favourite', - accountId: status.getIn(['account', 'id']), - url: status.get('url'), + dispatch(openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'favourite', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + }, })); } }; @@ -273,19 +276,25 @@ class Status extends ImmutablePureComponent { if (signedIn) { if (askReplyConfirmation) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(status, this.context.router.history)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(status, this.context.router.history)), + }, })); } else { dispatch(replyCompose(status, this.context.router.history)); } } else { - dispatch(openModal('INTERACTION', { - type: 'reply', - accountId: status.getIn(['account', 'id']), - url: status.get('url'), + dispatch(openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'reply', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + }, })); } }; @@ -309,10 +318,13 @@ class Status extends ImmutablePureComponent { } } } else { - dispatch(openModal('INTERACTION', { - type: 'reblog', - accountId: status.getIn(['account', 'id']), - url: status.get('url'), + dispatch(openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'reblog', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + }, })); } }; @@ -331,10 +343,13 @@ class Status extends ImmutablePureComponent { if (!deleteModal) { dispatch(deleteStatus(status.get('id'), history, withRedraft)); } else { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), - confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), + confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), + onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), + }, })); } }; @@ -352,11 +367,17 @@ class Status extends ImmutablePureComponent { }; handleOpenMedia = (media, index, lang) => { - this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index, lang })); + this.props.dispatch(openModal({ + modalType: 'MEDIA', + modalProps: { statusId: this.props.status.get('id'), media, index, lang }, + })); }; handleOpenVideo = (media, lang, options) => { - this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, lang, options })); + this.props.dispatch(openModal({ + modalType: 'VIDEO', + modalProps: { statusId: this.props.status.get('id'), media, lang, options }, + })); }; handleHotkeyOpenMedia = e => { @@ -425,7 +446,10 @@ class Status extends ImmutablePureComponent { }; handleEmbed = (status) => { - this.props.dispatch(openModal('EMBED', { url: status.get('url') })); + this.props.dispatch(openModal({ + modalType: 'EMBED', + modalProps: { url: status.get('url') }, + })); }; handleUnmuteClick = account => { @@ -437,10 +461,13 @@ class Status extends ImmutablePureComponent { }; handleBlockDomainClick = domain => { - this.props.dispatch(openModal('CONFIRM', { - message: {domain} }} />, - confirm: this.props.intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => this.props.dispatch(blockDomain(domain)), + this.props.dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: {domain} }} />, + confirm: this.props.intl.formatMessage(messages.blockDomainConfirm), + onConfirm: () => this.props.dispatch(blockDomain(domain)), + }, })); }; diff --git a/app/javascript/mastodon/features/ui/components/block_modal.jsx b/app/javascript/mastodon/features/ui/components/block_modal.jsx index 962b19344e..7cfd252e0b 100644 --- a/app/javascript/mastodon/features/ui/components/block_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/block_modal.jsx @@ -33,7 +33,10 @@ const mapDispatchToProps = dispatch => { }, onClose() { - dispatch(closeModal()); + dispatch(closeModal({ + modalType: undefined, + ignoreFocus: false, + })); }, }; }; diff --git a/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx index 4a55f25cbe..740537fa6e 100644 --- a/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx @@ -23,7 +23,10 @@ const mapStateToProps = (state, { statusId }) => ({ const mapDispatchToProps = dispatch => ({ onClose() { - dispatch(closeModal()); + dispatch(closeModal({ + modalType: undefined, + ignoreFocus: false, + })); }, }); diff --git a/app/javascript/mastodon/features/ui/components/disabled_account_banner.jsx b/app/javascript/mastodon/features/ui/components/disabled_account_banner.jsx index 6c4fefb0d5..6a71bb2465 100644 --- a/app/javascript/mastodon/features/ui/components/disabled_account_banner.jsx +++ b/app/javascript/mastodon/features/ui/components/disabled_account_banner.jsx @@ -23,11 +23,14 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({ onLogout () { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.logoutMessage), + confirm: intl.formatMessage(messages.logoutConfirm), + closeWhenConfirm: false, + onConfirm: () => logOut(), + }, })); }, }); diff --git a/app/javascript/mastodon/features/ui/components/header.jsx b/app/javascript/mastodon/features/ui/components/header.jsx index 04651bab18..bb6747c00c 100644 --- a/app/javascript/mastodon/features/ui/components/header.jsx +++ b/app/javascript/mastodon/features/ui/components/header.jsx @@ -26,7 +26,7 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ openClosedRegistrationsModal() { - dispatch(openModal('CLOSED_REGISTRATIONS')); + dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' })); }, }); diff --git a/app/javascript/mastodon/features/ui/components/link_footer.jsx b/app/javascript/mastodon/features/ui/components/link_footer.jsx index 4ae6f1dcf4..b025174409 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.jsx +++ b/app/javascript/mastodon/features/ui/components/link_footer.jsx @@ -19,11 +19,14 @@ const messages = defineMessages({ const mapDispatchToProps = (dispatch, { intl }) => ({ onLogout () { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), + dispatch(openModal({ + modalType: 'CONFIRM', + modalProps: { + message: intl.formatMessage(messages.logoutMessage), + confirm: intl.formatMessage(messages.logoutConfirm), + closeWhenConfirm: false, + onConfirm: () => logOut(), + }, })); }, }); diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 5700e0f54c..d5edb45b36 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -32,7 +32,7 @@ import MediaModal from './media_modal'; import ModalLoading from './modal_loading'; import VideoModal from './video_modal'; -const MODAL_COMPONENTS = { +export const MODAL_COMPONENTS = { 'MEDIA': () => Promise.resolve({ default: MediaModal }), 'VIDEO': () => Promise.resolve({ default: VideoModal }), 'AUDIO': () => Promise.resolve({ default: AudioModal }), diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.jsx b/app/javascript/mastodon/features/ui/components/mute_modal.jsx index dd21fa0465..708bd757cf 100644 --- a/app/javascript/mastodon/features/ui/components/mute_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/mute_modal.jsx @@ -34,7 +34,10 @@ const mapDispatchToProps = dispatch => { }, onClose() { - dispatch(closeModal()); + dispatch(closeModal({ + modalType: undefined, + ignoreFocus: false, + })); }, onToggleNotifications() { diff --git a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx index 25de95fd37..dad36134cf 100644 --- a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx +++ b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx @@ -11,7 +11,7 @@ const SignInBanner = () => { const dispatch = useAppDispatch(); const openClosedRegistrationsModal = useCallback( - () => dispatch(openModal('CLOSED_REGISTRATIONS')), + () => dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' })), [dispatch], ); diff --git a/app/javascript/mastodon/features/ui/containers/modal_container.js b/app/javascript/mastodon/features/ui/containers/modal_container.js index b14d0410d1..1c3872cd50 100644 --- a/app/javascript/mastodon/features/ui/containers/modal_container.js +++ b/app/javascript/mastodon/features/ui/containers/modal_container.js @@ -13,14 +13,22 @@ const mapDispatchToProps = dispatch => ({ onClose (confirmationMessage, ignoreFocus = false) { if (confirmationMessage) { dispatch( - openModal('CONFIRM', { - message: confirmationMessage.message, - confirm: confirmationMessage.confirm, - onConfirm: () => dispatch(closeModal(undefined, { ignoreFocus })), - }), + openModal({ + modalType: 'CONFIRM', + modalProps: { + message: confirmationMessage.message, + confirm: confirmationMessage.confirm, + onConfirm: () => dispatch(closeModal({ + modalType: undefined, + ignoreFocus: { ignoreFocus }, + })), + } }), ); } else { - dispatch(closeModal(undefined, { ignoreFocus })); + dispatch(closeModal({ + modalType: undefined, + ignoreFocus: { ignoreFocus }, + })); } }, }); diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 29c9abe68b..16047b26d8 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -25,7 +25,7 @@ import markers from './markers'; import media_attachments from './media_attachments'; import meta from './meta'; import { missedUpdatesReducer } from './missed_updates'; -import modal from './modal'; +import { modalReducer } from './modal'; import mutes from './mutes'; import notifications from './notifications'; import picture_in_picture from './picture_in_picture'; @@ -50,7 +50,7 @@ const reducers = { meta, alerts, loadingBar: loadingBarReducer, - modal, + modal: modalReducer, user_lists, domain_lists, status_lists, diff --git a/app/javascript/mastodon/reducers/modal.js b/app/javascript/mastodon/reducers/modal.js deleted file mode 100644 index 348f538991..0000000000 --- a/app/javascript/mastodon/reducers/modal.js +++ /dev/null @@ -1,40 +0,0 @@ -import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable'; - -import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose'; -import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal'; -import { TIMELINE_DELETE } from '../actions/timelines'; - -const initialState = ImmutableMap({ - ignoreFocus: false, - stack: ImmutableStack(), -}); - -const popModal = (state, { modalType, ignoreFocus }) => { - if (modalType === undefined || modalType === state.getIn(['stack', 0, 'modalType'])) { - return state.set('ignoreFocus', !!ignoreFocus).update('stack', stack => stack.shift()); - } else { - return state; - } -}; - -const pushModal = (state, modalType, modalProps) => { - return state.withMutations(map => { - map.set('ignoreFocus', false); - map.update('stack', stack => stack.unshift(ImmutableMap({ modalType, modalProps }))); - }); -}; - -export default function modal(state = initialState, action) { - switch(action.type) { - case MODAL_OPEN: - return pushModal(state, action.modalType, action.modalProps); - case MODAL_CLOSE: - return popModal(state, action); - case COMPOSE_UPLOAD_CHANGE_SUCCESS: - return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false }); - case TIMELINE_DELETE: - return state.update('stack', stack => stack.filterNot((modal) => modal.get('modalProps').statusId === action.id)); - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/modal.ts b/app/javascript/mastodon/reducers/modal.ts new file mode 100644 index 0000000000..6afbcc367c --- /dev/null +++ b/app/javascript/mastodon/reducers/modal.ts @@ -0,0 +1,94 @@ +import { Record as ImmutableRecord, Stack } from 'immutable'; + +import type { PayloadAction } from '@reduxjs/toolkit'; + +import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose'; +import type { ModalType } from '../actions/modal'; +import { openModal, closeModal } from '../actions/modal'; +import { TIMELINE_DELETE } from '../actions/timelines'; + +type ModalProps = Record; +interface Modal { + modalType: ModalType; + modalProps: ModalProps; +} + +const Modal = ImmutableRecord({ + modalType: 'ACTIONS', + modalProps: ImmutableRecord({})(), +}); + +interface ModalState { + ignoreFocus: boolean; + stack: Stack>; +} + +const initialState = ImmutableRecord({ + ignoreFocus: false, + stack: Stack(), +})(); +type State = typeof initialState; + +interface PopModalOption { + modalType: ModalType | undefined; + ignoreFocus: boolean; +} +const popModal = ( + state: State, + { modalType, ignoreFocus }: PopModalOption +): State => { + if ( + modalType === undefined || + modalType === state.get('stack').get(0)?.get('modalType') + ) { + return state + .set('ignoreFocus', !!ignoreFocus) + .update('stack', (stack) => stack.shift()); + } else { + return state; + } +}; + +const pushModal = ( + state: State, + modalType: ModalType, + modalProps: ModalProps +): State => { + return state.withMutations((record) => { + record.set('ignoreFocus', false); + record.update('stack', (stack) => + stack.unshift(Modal({ modalType, modalProps })) + ); + }); +}; + +export function modalReducer( + state: State = initialState, + action: PayloadAction<{ + modalType: ModalType; + ignoreFocus: boolean; + modalProps: Record; + }> +) { + switch (action.type) { + case openModal.type: + return pushModal( + state, + action.payload.modalType, + action.payload.modalProps + ); + case closeModal.type: + return popModal(state, action.payload); + case COMPOSE_UPLOAD_CHANGE_SUCCESS: + return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false }); + case TIMELINE_DELETE: + return state.update('stack', (stack) => + stack.filterNot( + // @ts-expect-error TIMELINE_DELETE action is not typed yet. + (modal) => modal.get('modalProps').statusId === action.id + ) + ); + default: + return state; + } +} From 0f2c16ac4b6a26a6394f80af6e2cd9338dfffde0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 26 May 2023 03:41:12 -0400 Subject: [PATCH 5/6] Fix RSpec/NoExpectationExample cop (#25103) --- .rubocop_todo.yml | 7 ------ .../auth/registrations_controller_spec.rb | 23 ++++++++++++++++--- spec/services/precompute_feed_service_spec.rb | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5711b3d4f2..3383a6b280 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -528,13 +528,6 @@ RSpec/MultipleMemoizedHelpers: RSpec/NestedGroups: Max: 6 -# Configuration parameters: AllowedPatterns. -# AllowedPatterns: ^expect_, ^assert_ -RSpec/NoExpectationExample: - Exclude: - - 'spec/controllers/auth/registrations_controller_spec.rb' - - 'spec/services/precompute_feed_service_spec.rb' - RSpec/PendingWithoutReason: Exclude: - 'spec/models/account_spec.rb' diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index a80273635d..b52d7c6ce4 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -244,9 +244,26 @@ RSpec.describe Auth::RegistrationsController do end end - it 'does nothing if user already exists' do - Fabricate(:account, username: 'test') - subject + context 'with an already taken username' do + subject do + Setting.registrations_mode = 'open' + post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' } } + end + + before do + Fabricate(:account, username: 'test') + end + + it 'responds with an error message about the username' do + subject + + expect(response).to have_http_status(:success) + expect(username_error_text).to eq(I18n.t('errors.messages.taken')) + end + + def username_error_text + Nokogiri::Slop(response.body).css('.user_account_username .error').text + end end include_examples 'checks for enabled registrations', :create diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb index 86ab59b29e..18ba00244d 100644 --- a/spec/services/precompute_feed_service_spec.rb +++ b/spec/services/precompute_feed_service_spec.rb @@ -19,7 +19,7 @@ RSpec.describe PrecomputeFeedService, type: :service do it 'does not raise an error even if it could not find any status' do account = Fabricate(:account) - subject.call(account) + expect { subject.call(account) }.to_not raise_error end it 'filters statuses' do From 55785b160320783392ffe3f24c5ca48e6ee7a5f2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 26 May 2023 03:42:16 -0400 Subject: [PATCH 6/6] Extract methods for user de-duping in maintenance CLI (#25117) --- lib/mastodon/cli/maintenance.rb | 66 +++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb index 660adef80e..b107480359 100644 --- a/lib/mastodon/cli/maintenance.rb +++ b/lib/mastodon/cli/maintenance.rb @@ -231,34 +231,9 @@ module Mastodon::CLI end end - ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE confirmation_token IS NOT NULL GROUP BY confirmation_token HAVING count(*) > 1").each do |row| - users = User.where(id: row['ids'].split(',')).sort_by(&:created_at).reverse.drop(1) - @prompt.warn "Unsetting confirmation token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}" - - users.each do |user| - user.update!(confirmation_token: nil) - end - end - - if ActiveRecord::Migrator.current_version < 2022_01_18_183010 - ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE remember_token IS NOT NULL GROUP BY remember_token HAVING count(*) > 1").each do |row| - users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1) - @prompt.warn "Unsetting remember token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}" - - users.each do |user| - user.update!(remember_token: nil) - end - end - end - - ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE reset_password_token IS NOT NULL GROUP BY reset_password_token HAVING count(*) > 1").each do |row| - users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1) - @prompt.warn "Unsetting password reset token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}" - - users.each do |user| - user.update!(reset_password_token: nil) - end - end + deduplicate_users_process_confirmation_token + deduplicate_users_process_remember_token + deduplicate_users_process_password_token @prompt.say 'Restoring users indexes…' ActiveRecord::Base.connection.add_index :users, ['confirmation_token'], name: 'index_users_on_confirmation_token', unique: true @@ -272,6 +247,41 @@ module Mastodon::CLI end end + def deduplicate_users_process_confirmation_token + ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE confirmation_token IS NOT NULL GROUP BY confirmation_token HAVING count(*) > 1").each do |row| + users = User.where(id: row['ids'].split(',')).sort_by(&:created_at).reverse.drop(1) + @prompt.warn "Unsetting confirmation token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}" + + users.each do |user| + user.update!(confirmation_token: nil) + end + end + end + + def deduplicate_users_process_remember_token + if ActiveRecord::Migrator.current_version < 2022_01_18_183010 + ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE remember_token IS NOT NULL GROUP BY remember_token HAVING count(*) > 1").each do |row| + users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1) + @prompt.warn "Unsetting remember token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}" + + users.each do |user| + user.update!(remember_token: nil) + end + end + end + end + + def deduplicate_users_process_password_token + ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE reset_password_token IS NOT NULL GROUP BY reset_password_token HAVING count(*) > 1").each do |row| + users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1) + @prompt.warn "Unsetting password reset token for those accounts: #{users.map(&:account).map(&:acct).join(', ')}" + + users.each do |user| + user.update!(reset_password_token: nil) + end + end + end + def deduplicate_account_domain_blocks! remove_index_if_exists!(:account_domain_blocks, 'index_account_domain_blocks_on_account_id_and_domain')