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 07cb72be9..68a456c1b 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 d8a21f64a..3f3c292ea 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 4828445c4..9a55b72cc 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 5e5f75d09..2d4baafa1 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 6cb8a105c..6442a4f82 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 60914b462..726b5aa30 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 2d1ac6670..e31ca79af 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 c22745ee5..8125b5b3b 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 bd565c72c..2ce94d9d8 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 00fd0d2cd..187e04ad1 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 4c41958aa..1d26f7a69 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 af6c08134..ee87cf2cd 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 c64a970ea..d446989ab 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 f17d06be5..8530e664c 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 95231c52a..a80e91e94 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 000000000..4ee52b0ca --- /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 000000000..e1edf2374 --- /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 000000000..c3e774936 --- /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 8a32e45ce..f452fef7b 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 60399c9de..3c88ce450 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 f741f6834..e6d3060eb 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 66421ed45..e293657ad 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 c0f0f3acc..1fc2e391e 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 22454cf3d..2ad7678ca 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 3774ee042..a11956ac5 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 ef2ae0e4c..000000000 --- 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 000000000..af34f5d6a --- /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 e61a14af2..fb041078b 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 b3f9bf1ac..756b8cd05 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 42d3d93fe..987b7c827 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 09a755e87..a134452e7 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 9a55b72cc..c719a5775 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 5dd5273b2..6cf180cd5 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 2d4baafa1..3026dde0a 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 16fb9ef57..27de4740c 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 6442a4f82..2b3a66c55 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 f881c4fa1..70b4026d1 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 1ca3fe550..6d26abf4f 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 192355886..8997f1bc6 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 978c031aa..456fc7d7c 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 87ddcf94f..cf1c63f9e 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 db6a3c4a5..be03b8f5f 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 1d80a9cd5..f41e8e6f2 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 292767bad..b63796a8b 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 1743edae9..c167d93dc 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 ddae8c028..e76790a9f 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 1d26f7a69..0ed94d34c 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 962b19344..7cfd252e0 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 4a55f25cb..740537fa6 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 6c4fefb0d..6a71bb246 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 04651bab1..bb6747c00 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 4ae6f1dcf..b02517440 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 5700e0f54..d5edb45b3 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 dd21fa046..708bd757c 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 25de95fd3..dad36134c 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 b14d0410d..1c3872cd5 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 29c9abe68..16047b26d 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 348f53899..000000000 --- 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 000000000..6afbcc367 --- /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 5711b3d4f..3383a6b28 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 a80273635..b52d7c6ce 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 86ab59b29..18ba00244 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 660adef80..b10748035 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')