From 6b1e1899fd5ce13141423918222aacd0f7a875c8 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 15 Apr 2026 17:05:58 +0200 Subject: [PATCH 01/37] Change discoverable accounts to only allow followers to feature them if they are locked (#38672) --- app/models/account.rb | 2 +- .../account/interaction_policy_concern.rb | 2 +- .../activitypub/actor_serializer.rb | 10 +++++- spec/models/account_spec.rb | 31 ++++++++++++++++--- .../activitypub/actor_serializer_spec.rb | 12 +++++++ .../rest/account_serializer_spec.rb | 12 +++++++ 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 141a9fb7e6..59df236959 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -473,7 +473,7 @@ class Account < ApplicationRecord end def featureable_by?(other_account) - return discoverable? if local? + return discoverable? && (!locked? || followed_by?(other_account)) if local? feature_policy_for_account(other_account).in?(%i(automatic manual)) end diff --git a/app/models/concerns/account/interaction_policy_concern.rb b/app/models/concerns/account/interaction_policy_concern.rb index 8fe9eda1ba..3966053e05 100644 --- a/app/models/concerns/account/interaction_policy_concern.rb +++ b/app/models/concerns/account/interaction_policy_concern.rb @@ -64,6 +64,6 @@ module Account::InteractionPolicyConcern def local_feature_policy(kind) return [] if kind == :manual || !discoverable? - [:public] + [locked? ? :followers : :public] end end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index a8c639fe34..8212b607f6 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -176,7 +176,15 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer end def interaction_policy - uri = object.discoverable? ? ActivityPub::TagManager::COLLECTIONS[:public] : ActivityPub::TagManager.instance.uri_for(object) + uri = begin + if !object.discoverable? + ActivityPub::TagManager.instance.uri_for(object) + elsif object.locked? + ActivityPub::TagManager.instance.followers_uri_for(object) + else + ActivityPub::TagManager::COLLECTIONS[:public] + end + end { canFeature: { diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index a128a9b2f8..0d76b26887 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -772,19 +772,42 @@ RSpec.describe Account do end describe '#featureable_by?' do - subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:, feature_approval_policy:) } + subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:, locked:, feature_approval_policy:) } + let(:locked) { false } let(:local_account) { Fabricate(:account) } context 'when account is local' do let(:local) { true } - let(:feature_approval_policy) { nil } + let(:feature_approval_policy) { 0 } context 'when account is discoverable' do let(:discoverable) { true } - it 'returns `true`' do - expect(subject.featureable_by?(local_account)).to be true + context 'when the account is not locked' do + let(:locked) { false } + + it 'returns `true`' do + expect(subject.featureable_by?(local_account)).to be true + end + end + + context 'when the account is locked' do + let(:locked) { true } + + it 'returns `false`' do + expect(subject.featureable_by?(local_account)).to be false + end + + context 'when the other account is a follower' do + before do + local_account.follow!(subject) + end + + it 'returns `true`' do + expect(subject.featureable_by?(local_account)).to be true + end + end end end diff --git a/spec/serializers/activitypub/actor_serializer_spec.rb b/spec/serializers/activitypub/actor_serializer_spec.rb index 734da6673c..73702c979c 100644 --- a/spec/serializers/activitypub/actor_serializer_spec.rb +++ b/spec/serializers/activitypub/actor_serializer_spec.rb @@ -56,6 +56,18 @@ RSpec.describe ActivityPub::ActorSerializer do }, }) end + + context 'when actor is locked' do + let(:record) { Fabricate(:account, locked: true) } + + it 'includes an automatic policy allowing followers' do + expect(subject).to include('interactionPolicy' => { + 'canFeature' => { + 'automaticApproval' => [ActivityPub::TagManager.instance.followers_uri_for(record)], + }, + }) + end + end end context 'when actor is not discoverable' do diff --git a/spec/serializers/rest/account_serializer_spec.rb b/spec/serializers/rest/account_serializer_spec.rb index 998de6b0fb..0e3ce1caca 100644 --- a/spec/serializers/rest/account_serializer_spec.rb +++ b/spec/serializers/rest/account_serializer_spec.rb @@ -93,6 +93,18 @@ RSpec.describe REST::AccountSerializer do 'current_user' => 'automatic', }) end + + context 'when account is locked' do + let(:account) { Fabricate(:account, locked: true) } + + it 'includes a policy that allows featuring for followers' do + expect(subject['feature_approval']).to include({ + 'automatic' => ['followers'], + 'manual' => [], + 'current_user' => 'automatic', + }) + end + end end context 'when account is not discoverable' do From 543db6d24c8764fe3975a138c777ffb1c28ae29e Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 15 Apr 2026 20:03:48 +0200 Subject: [PATCH 02/37] Add more actions to collections notifications & context menus (#38698) --- .../components/collection_menu.tsx | 118 +++++++++++++----- .../features/collections/detail/index.tsx | 8 +- .../notification_collection.module.scss | 17 +++ .../components/notification_collection.tsx | 43 ++++++- app/javascript/mastodon/locales/en.json | 4 + 5 files changed, 151 insertions(+), 39 deletions(-) create mode 100644 app/javascript/mastodon/features/notifications_v2/components/notification_collection.module.scss diff --git a/app/javascript/mastodon/features/collections/components/collection_menu.tsx b/app/javascript/mastodon/features/collections/components/collection_menu.tsx index 90c6315cbb..1cbb7279e9 100644 --- a/app/javascript/mastodon/features/collections/components/collection_menu.tsx +++ b/app/javascript/mastodon/features/collections/components/collection_menu.tsx @@ -4,6 +4,8 @@ import { defineMessages, useIntl } from 'react-intl'; import { matchPath } from 'react-router'; +import { showAlert } from '@/mastodon/actions/alerts'; +import { initBlockModal } from '@/mastodon/actions/blocks'; import { useAccount } from '@/mastodon/hooks/useAccount'; import MoreVertIcon from '@/material-icons/400-24px/more_vert.svg?react'; import { openModal } from 'mastodon/actions/modal'; @@ -21,6 +23,18 @@ const messages = defineMessages({ id: 'collections.view_collection', defaultMessage: 'View collection', }, + share: { + id: 'collections.share_short', + defaultMessage: 'Share', + }, + copyLink: { + id: 'collections.copy_link', + defaultMessage: 'Copy link', + }, + copyLinkConfirmation: { + id: 'collections.copy_link_confirmation', + defaultMessage: 'Copied collection link to clipboard', + }, viewOtherCollections: { id: 'collections.view_other_collections_by_user', defaultMessage: 'View other collections by this user', @@ -33,6 +47,10 @@ const messages = defineMessages({ id: 'collections.report_collection', defaultMessage: 'Report this collection', }, + blockOwner: { + id: 'collections.block_collection_owner', + defaultMessage: 'Block account', + }, revoke: { id: 'collections.revoke_collection_inclusion', defaultMessage: 'Remove myself from this collection', @@ -42,15 +60,29 @@ const messages = defineMessages({ export const CollectionMenu: React.FC<{ collection: ApiCollectionJSON; - context: 'list' | 'collection'; + context: 'list' | 'notifications' | 'collection'; className?: string; }> = ({ collection, context, className }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const { id, name, account_id } = collection; - const isOwnCollection = account_id === me; + const { id, name, account_id, items } = collection; const ownerAccount = useAccount(account_id); + const isOwnCollection = account_id === me; + const currentAccountInCollection = items.find( + (item) => item.account_id === me, + ); + + const openShareModal = useCallback(() => { + dispatch( + openModal({ + modalType: 'SHARE_COLLECTION', + modalProps: { + collection, + }, + }), + ); + }, [collection, dispatch]); const openDeleteConfirmation = useCallback(() => { dispatch( @@ -75,9 +107,9 @@ export const CollectionMenu: React.FC<{ ); }, [collection, dispatch]); - const currentAccountInCollection = collection.items.find( - (item) => item.account_id === me, - ); + const openBlockModal = useCallback(() => { + dispatch(initBlockModal(ownerAccount)); + }, [ownerAccount, dispatch]); const openRevokeConfirmation = useCallback(() => { void dispatch( @@ -92,8 +124,28 @@ export const CollectionMenu: React.FC<{ }, [collection.id, currentAccountInCollection?.id, dispatch]); const menu = useMemo(() => { + const viewCollectionItem: MenuItem = { + text: intl.formatMessage(messages.view), + to: `/collections/${id}`, + }; + const shareItems: MenuItem[] = [ + { + text: intl.formatMessage(messages.share), + action: openShareModal, + }, + { + text: intl.formatMessage(messages.copyLink), + action: () => { + void navigator.clipboard.writeText(`/collections/${id}`); + dispatch(showAlert({ message: messages.copyLinkConfirmation })); + }, + }, + ]; + if (isOwnCollection) { - const commonItems: MenuItem[] = [ + const ownerItems: MenuItem[] = [ + ...shareItems, + null, { text: intl.formatMessage(editorMessages.manageAccounts), to: `/collections/${id}/edit`, @@ -111,18 +163,14 @@ export const CollectionMenu: React.FC<{ ]; if (context === 'list') { - return [ - { text: intl.formatMessage(messages.view), to: `/collections/${id}` }, - null, - ...commonItems, - ]; + return [viewCollectionItem, ...ownerItems]; } else { - return commonItems; + return ownerItems; } } else { - const items: MenuItem[] = []; + const nonOwnerItems: MenuItem[] = [viewCollectionItem, ...shareItems]; - if (ownerAccount) { + if (context !== 'notifications' && ownerAccount) { const featuredCollectionsPath = `/@${ownerAccount.acct}/featured`; // Don't show menu link to featured collections while on that very page if ( @@ -131,42 +179,50 @@ export const CollectionMenu: React.FC<{ exact: true, }) ) { - items.push( - ...[ - { - text: intl.formatMessage(messages.viewOtherCollections), - to: featuredCollectionsPath, - }, - null, - ], - ); + nonOwnerItems.push({ + text: intl.formatMessage(messages.viewOtherCollections), + to: featuredCollectionsPath, + }); } } - if (currentAccountInCollection) { - items.push({ + nonOwnerItems.push(null); + + // Collection notifications already have a prominent 'Remove me' button + if (currentAccountInCollection && context !== 'notifications') { + nonOwnerItems.push({ text: intl.formatMessage(messages.revoke), action: openRevokeConfirmation, }); } - items.push({ + nonOwnerItems.push({ text: intl.formatMessage(messages.report), action: openReportModal, }); - return items; + if (currentAccountInCollection) { + nonOwnerItems.push({ + text: intl.formatMessage(messages.blockOwner), + action: openBlockModal, + }); + } + + return nonOwnerItems; } }, [ - isOwnCollection, intl, id, + openShareModal, + isOwnCollection, + dispatch, openDeleteConfirmation, context, - currentAccountInCollection, - openRevokeConfirmation, ownerAccount, + currentAccountInCollection, openReportModal, + openBlockModal, + openRevokeConfirmation, ]); return ( diff --git a/app/javascript/mastodon/features/collections/detail/index.tsx b/app/javascript/mastodon/features/collections/detail/index.tsx index 404cd8a074..d3fbd4c2cb 100644 --- a/app/javascript/mastodon/features/collections/detail/index.tsx +++ b/app/javascript/mastodon/features/collections/detail/index.tsx @@ -115,7 +115,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ ); const isCurrentUserInCollection = !isOwnCollection && currentUserIndex > -1; - const handleShare = useCallback(() => { + const openShareModal = useCallback(() => { dispatch( openModal({ modalType: 'SHARE_COLLECTION', @@ -132,9 +132,9 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ if (isNewCollection) { // Replace with current pathname to clear `newCollection` state history.replace(location.pathname); - handleShare(); + openShareModal(); } - }, [history, handleShare, isNewCollection, location.pathname]); + }, [history, openShareModal, isNewCollection, location.pathname]); return (
@@ -150,7 +150,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ icon='share-icon' title={intl.formatMessage(messages.share)} className={classes.iconButton} - onClick={handleShare} + onClick={openShareModal} /> = ({ notification, unread }) => { const { collection, type } = notification; const collectionCreatorAccount = useAccount(collection.account_id); + const confirmRevoke = useConfirmRevoke(collection); return (
+ ), }} /> @@ -54,7 +64,12 @@ export const NotificationCollection: React.FC<{ defaultMessage='{name} edited a collection you’re in' values={{ name: ( - + ), }} /> @@ -63,6 +78,26 @@ export const NotificationCollection: React.FC<{
+ +
+ + + +
); diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 8b05678fc7..3e2ce2c696 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# account} other {# accounts}}", "collections.accounts.empty_description": "Add up to {count} accounts you follow", "collections.accounts.empty_title": "This collection is empty", + "collections.block_collection_owner": "Block account", "collections.by_account": "by {account_handle}", "collections.collection_description": "Description", "collections.collection_language": "Language", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Are you sure you want to remove this account from this collection?", "collections.content_warning": "Content warning", "collections.continue": "Continue", + "collections.copy_link": "Copy link", + "collections.copy_link_confirmation": "Copied collection link to clipboard", "collections.create.accounts_subtitle": "Only accounts you follow who have opted into discovery can be added.", "collections.create.accounts_title": "Who will you feature in this collection?", "collections.create.basic_details_title": "Basic details", @@ -407,6 +410,7 @@ "collections.search_accounts_label": "Search for accounts to add…", "collections.search_accounts_max_reached": "You have added the maximum number of accounts", "collections.sensitive": "Sensitive", + "collections.share_short": "Share", "collections.topic_hint": "Add a hashtag that helps others understand the main topic of this collection.", "collections.topic_special_chars_hint": "Special characters will be removed when saving", "collections.unlisted_collections_description": "These don’t appear on your profile to others. Anyone with the link can discover them.", From fee38e57f04b543789b842b179b3db4ec1a60151 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 16 Apr 2026 09:26:34 +0200 Subject: [PATCH 03/37] Federate and store a collection `url` (#38697) --- app/models/collection.rb | 1 + .../activitypub/featured_collection_serializer.rb | 15 +++++++++++++-- .../process_featured_collection_service.rb | 8 ++++++++ .../20260415133505_add_url_to_collections.rb | 7 +++++++ db/schema.rb | 3 ++- .../featured_collection_serializer_spec.rb | 1 + .../process_featured_collection_service_spec.rb | 14 ++++++++++++++ 7 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20260415133505_add_url_to_collections.rb diff --git a/app/models/collection.rb b/app/models/collection.rb index c8d4fb99bc..5956b7c087 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -15,6 +15,7 @@ # original_number_of_items :integer # sensitive :boolean not null # uri :string +# url :string # created_at :datetime not null # updated_at :datetime not null # account_id :bigint(8) not null diff --git a/app/serializers/activitypub/featured_collection_serializer.rb b/app/serializers/activitypub/featured_collection_serializer.rb index 00fdb368e6..60b4d8459c 100644 --- a/app/serializers/activitypub/featured_collection_serializer.rb +++ b/app/serializers/activitypub/featured_collection_serializer.rb @@ -1,13 +1,16 @@ # frozen_string_literal: true class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer - attributes :id, :type, :total_items, :name, :attributed_to, + # include Rails.application.routes.url_helpers + include RoutingHelper + + attributes :id, :type, :total_items, :name, :attributed_to, :url, :sensitive, :discoverable, :published, :updated attribute :summary, unless: :language_present? attribute :summary_map, if: :language_present? - has_one :tag, key: :topic, serializer: ActivityPub::NoteSerializer::TagSerializer + has_one :topic, serializer: ActivityPub::NoteSerializer::TagSerializer has_many :collection_items, key: :ordered_items, serializer: ActivityPub::FeaturedItemSerializer @@ -31,6 +34,10 @@ class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer ActivityPub::TagManager.instance.uri_for(object.account) end + def url + account_collection_url(object.account, object) + end + def total_items object.accepted_collection_items.size end @@ -50,4 +57,8 @@ class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer def collection_items object.accepted_collection_items end + + def topic + object.tag + end end diff --git a/app/services/activitypub/process_featured_collection_service.rb b/app/services/activitypub/process_featured_collection_service.rb index f7ae08dd26..18e0da32d7 100644 --- a/app/services/activitypub/process_featured_collection_service.rb +++ b/app/services/activitypub/process_featured_collection_service.rb @@ -46,6 +46,13 @@ class ActivityPub::ProcessFeaturedCollectionService @json['summaryMap']&.keys&.first end + def url + url = url_to_href(@json['url'], 'text/html') + return @json['id'] if url.blank? || unsupported_uri_scheme?(url) + + url + end + def collection_attributes { local: false, @@ -56,6 +63,7 @@ class ActivityPub::ProcessFeaturedCollectionService discoverable: @json['discoverable'], original_number_of_items: @json['totalItems'] || 0, tag_name: @json.dig('topic', 'name'), + url:, } end diff --git a/db/migrate/20260415133505_add_url_to_collections.rb b/db/migrate/20260415133505_add_url_to_collections.rb new file mode 100644 index 0000000000..b40a792fc4 --- /dev/null +++ b/db/migrate/20260415133505_add_url_to_collections.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddURLToCollections < ActiveRecord::Migration[8.1] + def change + add_column :collections, :url, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 6dddbc0efa..677ed7caa9 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[8.1].define(version: 2026_04_10_083500) do +ActiveRecord::Schema[8.1].define(version: 2026_04_15_133505) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -400,6 +400,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_04_10_083500) do t.bigint "tag_id" t.datetime "updated_at", null: false t.string "uri" + t.string "url" t.index ["account_id"], name: "index_collections_on_account_id" t.index ["tag_id"], name: "index_collections_on_tag_id" t.index ["uri"], name: "index_collections_on_uri", unique: true, where: "(uri IS NOT NULL)" diff --git a/spec/serializers/activitypub/featured_collection_serializer_spec.rb b/spec/serializers/activitypub/featured_collection_serializer_spec.rb index 78b9daf613..64b2fdebbe 100644 --- a/spec/serializers/activitypub/featured_collection_serializer_spec.rb +++ b/spec/serializers/activitypub/featured_collection_serializer_spec.rb @@ -25,6 +25,7 @@ RSpec.describe ActivityPub::FeaturedCollectionSerializer do 'attributedTo' => ActivityPub::TagManager.instance.uri_for(collection.account), 'sensitive' => false, 'discoverable' => false, + 'url' => account_collection_url(collection.account, collection), 'topic' => { 'href' => match(%r{/tags/people$}), 'type' => 'Hashtag', diff --git a/spec/services/activitypub/process_featured_collection_service_spec.rb b/spec/services/activitypub/process_featured_collection_service_spec.rb index 343d3ac3b2..6a74eaa398 100644 --- a/spec/services/activitypub/process_featured_collection_service_spec.rb +++ b/spec/services/activitypub/process_featured_collection_service_spec.rb @@ -16,6 +16,7 @@ RSpec.describe ActivityPub::ProcessFeaturedCollectionService do 'name' => 'Good people from other servers', 'sensitive' => false, 'discoverable' => true, + 'url' => 'https://example.com/c/1', 'topic' => { 'type' => 'Hashtag', 'name' => '#people', @@ -50,6 +51,7 @@ RSpec.describe ActivityPub::ProcessFeaturedCollectionService do expect(new_collection.description_html).to eq '

A list of remote actors you should follow.

' expect(new_collection.sensitive).to be false expect(new_collection.discoverable).to be true + expect(new_collection.url).to eq 'https://example.com/c/1' expect(new_collection.tag.formatted_name).to eq '#people' expect(ActivityPub::ProcessFeaturedItemWorker).to have_enqueued_sidekiq_job.with(new_collection.id, 'https://example.com/featured_items/1', 1, nil) @@ -75,6 +77,18 @@ RSpec.describe ActivityPub::ProcessFeaturedCollectionService do end end + context 'when the json does not include a `url`' do + let(:featured_collection_json) do + base_json.except('url') + end + + it 'uses the `id` instead' do + subject.call(account, featured_collection_json) + + expect(Collection.last.url).to eq 'https://example.com/featured_collections/1' + end + end + context 'when the collection already exists' do let(:collection) { Fabricate(:remote_collection, account:, uri: base_json['id'], name: 'placeholder') } From 21a6ecbfb4d1450f7966894322b10b7ebc2ce0bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:17:37 +0200 Subject: [PATCH 04/37] Update dependency faker to v3.7.1 (#38681) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 25c9d571f5..0eb4bc5ffd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -233,7 +233,7 @@ GEM excon (1.4.0) logger fabrication (3.0.0) - faker (3.6.1) + faker (3.7.1) i18n (>= 1.8.11, < 2) faraday (2.14.1) faraday-net_http (>= 2.0, < 3.5) From 18c79e4e451f592d8ce7d4c98e96ee80d3ed8c41 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:32:46 +0200 Subject: [PATCH 05/37] New Crowdin Translations (automated) (#38705) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 2 ++ app/javascript/mastodon/locales/da.json | 6 ++++++ app/javascript/mastodon/locales/de.json | 6 ++++++ app/javascript/mastodon/locales/el.json | 6 ++++++ app/javascript/mastodon/locales/es-AR.json | 6 ++++++ app/javascript/mastodon/locales/es-MX.json | 6 ++++++ app/javascript/mastodon/locales/es.json | 6 ++++++ app/javascript/mastodon/locales/fi.json | 8 +++++++- app/javascript/mastodon/locales/fr-CA.json | 1 + app/javascript/mastodon/locales/fr.json | 1 + app/javascript/mastodon/locales/ga.json | 3 +++ app/javascript/mastodon/locales/gl.json | 9 ++++++++- app/javascript/mastodon/locales/is.json | 6 ++++++ app/javascript/mastodon/locales/it.json | 6 ++++++ app/javascript/mastodon/locales/nl.json | 8 +++++++- app/javascript/mastodon/locales/pt-PT.json | 4 ++++ app/javascript/mastodon/locales/sq.json | 2 ++ app/javascript/mastodon/locales/sv.json | 3 +++ app/javascript/mastodon/locales/tr.json | 2 ++ app/javascript/mastodon/locales/vi.json | 6 ++++++ app/javascript/mastodon/locales/zh-CN.json | 6 ++++++ app/javascript/mastodon/locales/zh-TW.json | 6 ++++++ config/locales/de.yml | 2 +- config/locales/gl.yml | 2 +- config/locales/simple_form.gl.yml | 2 +- 25 files changed, 109 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 00a81da5b0..d8b0d73049 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -409,6 +409,8 @@ "collections.sensitive": "Адчувальная", "collections.topic_hint": "Дадайце хэштэг, які дапаможа іншым зразумець галоўную тэму гэтай калекцыі.", "collections.topic_special_chars_hint": "Спецыяльныя сімвалы будуць прыбраныя пры захаванні", + "collections.unlisted_collections_description": "Яны не паказваюцца ў Вашым профілі іншым карыстальнікам. Іх можа пабачыць любы, хто мае спасылку на іх.", + "collections.unlisted_collections_with_count": "Схаваныя калекцыі ({count})", "collections.view_collection": "Глядзець калекцыю", "collections.view_other_collections_by_user": "Паглядзець іншыя калекцыі гэтага карыстальніка", "collections.visibility_public": "Публічная", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 9dbc85b070..b0906c357d 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# konto} other {# konti}}", "collections.accounts.empty_description": "Tilføj op til {count} konti, du følger", "collections.accounts.empty_title": "Denne samling er tom", + "collections.block_collection_owner": "Blokér konto", "collections.by_account": "af {account_handle}", "collections.collection_description": "Beskrivelse", "collections.collection_language": "Sprog", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Er du sikker på, at du vil fjerne denne konto fra denne samling?", "collections.content_warning": "Indholdsadvarsel", "collections.continue": "Fortsæt", + "collections.copy_link": "Kopiér link", + "collections.copy_link_confirmation": "Link til samling kopieret til udklipsholderen", "collections.create.accounts_subtitle": "Kun konti, du følger, og som har tilmeldt sig opdagelse, kan tilføjes.", "collections.create.accounts_title": "Hvem vil du fremhæve i denne samling?", "collections.create.basic_details_title": "Grundlæggende oplysninger", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Søg efter konti for at tilføje…", "collections.search_accounts_max_reached": "Du har tilføjet det maksimale antal konti", "collections.sensitive": "Sensitivt", + "collections.share_short": "Del", "collections.topic_hint": "Tilføj et hashtag, der hjælper andre med at forstå det overordnede emne for denne samling.", "collections.topic_special_chars_hint": "Specialtegn fjernes, når der gemmes", + "collections.unlisted_collections_description": "Disse vises ikke på din profil for andre. Alle, der har linket, kan finde dem.", + "collections.unlisted_collections_with_count": "Ikke-listede samlinger ({count})", "collections.view_collection": "Vis samling", "collections.view_other_collections_by_user": "Se andre samlinger af denne bruger", "collections.visibility_public": "Offentlig", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 1de89f237a..0e6cfe09b7 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# Konto} other {# Konten}}", "collections.accounts.empty_description": "Füge bis zu {count} Konten, denen du folgst, hinzu", "collections.accounts.empty_title": "Diese Sammlung ist leer", + "collections.block_collection_owner": "Konto blockieren", "collections.by_account": "von {account_handle}", "collections.collection_description": "Beschreibung", "collections.collection_language": "Sprache", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Möchtest du dieses Konto wirklich aus der Sammlung entfernen?", "collections.content_warning": "Inhaltswarnung", "collections.continue": "Fortfahren", + "collections.copy_link": "Link kopieren", + "collections.copy_link_confirmation": "Link zur Sammlung in die Zwischenablage kopiert", "collections.create.accounts_subtitle": "Du kannst nur Profile hinzufügen, denen du folgst und die das Hinzufügen gestatten.", "collections.create.accounts_title": "Wen möchtest du in dieser Sammlung präsentieren?", "collections.create.basic_details_title": "Allgemeine Informationen", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Suche nach Konten, um sie hinzuzufügen …", "collections.search_accounts_max_reached": "Du hast die Höchstzahl an Konten hinzugefügt", "collections.sensitive": "Inhaltswarnung", + "collections.share_short": "Teilen", "collections.topic_hint": "Ein Hashtag für diese Sammlung kann anderen dabei helfen, dein Anliegen besser einordnen zu können.", "collections.topic_special_chars_hint": "Sonderzeichen werden beim Speichern entfernt", + "collections.unlisted_collections_description": "Die Sammlungen sind für andere nicht auf deinem Profil sichtbar. Ein direkter Zugriff ist per Link möglich.", + "collections.unlisted_collections_with_count": "Nicht öffentliche Sammlungen ({count})", "collections.view_collection": "Sammlungen anzeigen", "collections.view_other_collections_by_user": "Andere Sammlungen dieses Kontos ansehen", "collections.visibility_public": "Öffentlich", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 254f2b69dc..a491854a0a 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# λογαριασμός} other {# λογαριασμοί}}", "collections.accounts.empty_description": "Προσθέστε μέχρι και {count} λογαριασμούς που ακολουθείτε", "collections.accounts.empty_title": "Αυτή η συλλογή είναι κενή", + "collections.block_collection_owner": "Αποκλεισμός λογαριασμού", "collections.by_account": "από {account_handle}", "collections.collection_description": "Περιγραφή", "collections.collection_language": "Γλώσσα", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Σίγουρα θέλετε να αφαιρέσετε αυτόν τον λογαριασμό από αυτή τη συλλογή;", "collections.content_warning": "Προειδοποίηση περιεχομένου", "collections.continue": "Συνέχεια", + "collections.copy_link": "Αντιγραφή συνδέσμου", + "collections.copy_link_confirmation": "Αντιγράφηκε ο σύνδεσμος συλλογής στο πρόχειρο", "collections.create.accounts_subtitle": "Μόνο οι λογαριασμοί που ακολουθείτε που έχουν επιλέξει ανακάλυψη μπορούν να προστεθούν.", "collections.create.accounts_title": "Ποιον θα αναδείξετε σε αυτήν τη συλλογή;", "collections.create.basic_details_title": "Βασικά στοιχεία", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Αναζήτηση λογαριασμών για προσθήκη…", "collections.search_accounts_max_reached": "Έχετε προσθέσει τον μέγιστο αριθμό λογαριασμών", "collections.sensitive": "Ευαίσθητο", + "collections.share_short": "Κοινοποίηση", "collections.topic_hint": "Προσθέστε μια ετικέτα που βοηθά άλλους να κατανοήσουν το κύριο θέμα αυτής της συλλογής.", "collections.topic_special_chars_hint": "Οι ειδικοί χαρακτήρες θα αφαιρεθούν κατά την αποθήκευση", + "collections.unlisted_collections_description": "Αυτές δεν εμφανίζονται στο προφίλ σας σε άλλους. Οποιοσδήποτε με τον σύνδεσμο μπορεί να τις ανακαλύψει.", + "collections.unlisted_collections_with_count": "Μη καταχωρισμένες συλλογές ({count})", "collections.view_collection": "Προβολή συλλογής", "collections.view_other_collections_by_user": "Δείτε άλλες συλλογές από αυτόν τον χρήστη", "collections.visibility_public": "Δημόσια", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index c74d77eb91..f3a868ec0e 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# hora} other {# horas}}", "collections.accounts.empty_description": "Agregá hasta {count} cuentas que seguís", "collections.accounts.empty_title": "Esta colección está vacía", + "collections.block_collection_owner": "Bloquear cuenta", "collections.by_account": "por {account_handle}", "collections.collection_description": "Descripción", "collections.collection_language": "Idioma", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "¿De verdad querés eliminar esta cuenta de esta colección?", "collections.content_warning": "Advertencia de contenido", "collections.continue": "Continuar", + "collections.copy_link": "Copiar enlace", + "collections.copy_link_confirmation": "Enlace a la colección copiado al portapapeles", "collections.create.accounts_subtitle": "Solo las cuentas que seguís —las cuales optaron por ser descubiertas— pueden ser agregadas.", "collections.create.accounts_title": "¿A quién vas a destacar en esta colección?", "collections.create.basic_details_title": "Detalles básicos", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Buscar cuentas para agregar…", "collections.search_accounts_max_reached": "Agregaste el número máximo de cuentas", "collections.sensitive": "Sensible", + "collections.share_short": "Compartir", "collections.topic_hint": "Agregá una etiqueta que ayude a otros usuarios a entender el tema principal de esta colección.", "collections.topic_special_chars_hint": "Los símbolos se eliminarán al guardar", + "collections.unlisted_collections_description": "Estas colecciones no aparecen en tu perfil para ser mostradas a otras cuentas. Cualquier persona que tenga el enlace puede descubrirlas.", + "collections.unlisted_collections_with_count": "Colecciones no listadas ({count})", "collections.view_collection": "Abrir colección", "collections.view_other_collections_by_user": "Ver otras colecciones de este usuario", "collections.visibility_public": "Pública", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 8003419ad1..ad9a0eaafd 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural,one {# cuenta} other {# cuentas}}", "collections.accounts.empty_description": "Añade hasta {count} cuentas que sigues", "collections.accounts.empty_title": "Esta colección está vacía", + "collections.block_collection_owner": "Bloquer cuenta", "collections.by_account": "de {account_handle}", "collections.collection_description": "Descripción", "collections.collection_language": "Idioma", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "¿Estás seguro/a de que quieres eliminar esta cuenta de esta colección?", "collections.content_warning": "Advertencia de contenido", "collections.continue": "Continuar", + "collections.copy_link": "Copiar enlace", + "collections.copy_link_confirmation": "Se ha copiado el enlace de la colección al portapapeles", "collections.create.accounts_subtitle": "Solo se pueden añadir cuentas que sigas y que hayan optado por aparecer en los resultados de búsqueda.", "collections.create.accounts_title": "¿A quién incluirás en esta colección?", "collections.create.basic_details_title": "Detalles básicos", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Buscar cuentas para añadir…", "collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas", "collections.sensitive": "Sensible", + "collections.share_short": "Compartir", "collections.topic_hint": "Añade una etiqueta que ayude a los demás a comprender el tema principal de esta colección.", "collections.topic_special_chars_hint": "Los caracteres especiales se eliminarán al guardar", + "collections.unlisted_collections_description": "No aparecen en tu perfil. Cualquier persona que tenga el enlace puede descubrirlas.", + "collections.unlisted_collections_with_count": "Colecciones no listadas ({count})", "collections.view_collection": "Ver colección", "collections.view_other_collections_by_user": "Ver otras colecciones de este usuario", "collections.visibility_public": "Pública", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index f8cb187f14..bea76a9996 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# cuenta} other {# cuentas}}", "collections.accounts.empty_description": "Añade hasta {count} cuentas que sigas", "collections.accounts.empty_title": "Esta colección está vacía", + "collections.block_collection_owner": "Bloquear cuenta", "collections.by_account": "de {account_handle}", "collections.collection_description": "Descripción", "collections.collection_language": "Idioma", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "¿Estás seguro de que quieres eliminar esta cuenta de la colección?", "collections.content_warning": "Advertencia de contenido", "collections.continue": "Continuar", + "collections.copy_link": "Copiar enlace", + "collections.copy_link_confirmation": "Enlace a la colección copiado al portapapeles", "collections.create.accounts_subtitle": "Solo pueden añadirse cuentas que sigues y que han activado el descubrimiento.", "collections.create.accounts_title": "¿A quién vas a destacar en esta colección?", "collections.create.basic_details_title": "Datos básicos", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Buscar cuentas para añadir…", "collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas", "collections.sensitive": "Sensible", + "collections.share_short": "Compartir", "collections.topic_hint": "Añadir una etiqueta que ayude a otros a entender el tema principal de esta colección.", "collections.topic_special_chars_hint": "Los caracteres especiales se eliminarán al guardar", + "collections.unlisted_collections_description": "No aparecen en tu perfil. Cualquiera que tenga un enlace puede descubrirlas.", + "collections.unlisted_collections_with_count": "Colecciones no listadas ({count})", "collections.view_collection": "Ver colección", "collections.view_other_collections_by_user": "Ver otras colecciones de este usuario", "collections.visibility_public": "Pública", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 4cfcccccc2..0d009f80c1 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -81,7 +81,7 @@ "account.menu.add_to_list": "Lisää listaan…", "account.menu.block": "Estä tili", "account.menu.block_domain": "Estä {domain}", - "account.menu.copied": "Kopioitu tilin linkki leikepöydälle", + "account.menu.copied": "Tlin linkki kopioitu leikepöydälle", "account.menu.copy": "Kopioi linkki", "account.menu.direct": "Mainitse yksityisesti", "account.menu.hide_reblogs": "Piilota tehostukset aikajanalta", @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# tili} other {# tiliä}}", "collections.accounts.empty_description": "Lisää enintään {count} seuraamaasi tiliä", "collections.accounts.empty_title": "Tämä kokoelma on tyhjä", + "collections.block_collection_owner": "Estä tili", "collections.by_account": "koonnut {account_handle}", "collections.collection_description": "Kuvaus", "collections.collection_language": "Kieli", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Haluatko varmasti poistaa tämän tilin tästä kokoelmasta?", "collections.content_warning": "Sisältövaroitus", "collections.continue": "Jatka", + "collections.copy_link": "Kopioi linkki", + "collections.copy_link_confirmation": "Kokoelman linkki kopioitu leikepöydälle", "collections.create.accounts_subtitle": "Lisätä voi vain tilejä, joita seuraat ja jotka ovat valinneet tulla löydetyiksi.", "collections.create.accounts_title": "Keitä esittelet tässä kokoelmassa?", "collections.create.basic_details_title": "Perustiedot", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Hae lisättäviä tilejä…", "collections.search_accounts_max_reached": "Olet lisännyt enimmäismäärän tilejä", "collections.sensitive": "Arkaluonteinen", + "collections.share_short": "Jaa", "collections.topic_hint": "Lisää aihetunniste, joka auttaa muita ymmärtämään tämän kokoelman pääaiheen.", "collections.topic_special_chars_hint": "Erikoismerkit poistetaan tallennettaessa", + "collections.unlisted_collections_description": "Nämä eivät näy muille profiilissasi. Kuka tahansa, jolla on linkki, voi löytää ne.", + "collections.unlisted_collections_with_count": "Listaamattomat kokoelmat ({count})", "collections.view_collection": "Näytä kokoelma", "collections.view_other_collections_by_user": "Näytä muut tämän käyttäjän kokoelmat", "collections.visibility_public": "Julkinen", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index e474a9597d..05cefacce2 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -106,6 +106,7 @@ "account.name.copy": "Copier l’identifiant", "account.name.help.domain": "{domain} est le serveur qui héberge le profil et les messages du compte.", "account.name.help.domain_self": "{domain} est le serveur qui héberge votre profil et vos messages.", + "account.name.help.footer": "Tout comme vous pouvez envoyer des courriels à des personnes utilisant différents fournisseurs de messagerie, vous pouvez interagir avec des personnes sur d'autres serveurs Mastodon, ainsi qu'avec n'importe qui utilisant une application sociale compatible avec Mastodon.", "account.name.help.header": "Un identifiant est comme une adresse de courriel", "account.name.help.username": "{username} est le nom d'utilisateur·ice de ce compte sur son serveur. Quelqu'un sur un autre serveur peut avoir le même nom.", "account.name.help.username_self": "{username} est votre nom d'utilisateur·ice sur ce serveur. Quelqu'un sur un autre serveur peut avoir le même nom.", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 7066d261a9..55fff4f338 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -106,6 +106,7 @@ "account.name.copy": "Copier l’identifiant", "account.name.help.domain": "{domain} est le serveur qui héberge le profil et les messages du compte.", "account.name.help.domain_self": "{domain} est le serveur qui héberge votre profil et vos messages.", + "account.name.help.footer": "Tout comme vous pouvez envoyer des courriels à des personnes utilisant différents fournisseurs de messagerie, vous pouvez interagir avec des personnes sur d'autres serveurs Mastodon, ainsi qu'avec n'importe qui utilisant une application sociale compatible avec Mastodon.", "account.name.help.header": "Un identifiant est comme une adresse de courriel", "account.name.help.username": "{username} est le nom d'utilisateur·ice de ce compte sur son serveur. Quelqu'un sur un autre serveur peut avoir le même nom.", "account.name.help.username_self": "{username} est votre nom d'utilisateur·ice sur ce serveur. Quelqu'un sur un autre serveur peut avoir le même nom.", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 80e713bbfe..cd307a897d 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -106,6 +106,7 @@ "account.name.copy": "Láimhseáil chóipeála", "account.name.help.domain": "Is é {domain} an freastalaí a óstálann próifíl agus poist an úsáideora.", "account.name.help.domain_self": "Is é {domain} an freastalaí a óstálann do phróifíl agus do phoist.", + "account.name.help.footer": "Díreach mar is féidir leat ríomhphoist a sheoladh chuig daoine ag baint úsáide as soláthraithe ríomhphoist éagsúla, is féidir leat idirghníomhú le daoine ar fhreastalaithe Mastodon eile, agus le duine ar bith ar aipeanna sóisialta eile atá comhoiriúnach le Mastodon.", "account.name.help.header": "Is cosúil le seoladh ríomhphoist é láimhseáil", "account.name.help.username": "Is é {username} ainm úsáideora an chuntais seo ar a bhfreastalaí. D’fhéadfadh an t-ainm úsáideora céanna a bheith ag duine éigin ar fhreastalaí eile.", "account.name.help.username_self": "Is é {username} d'ainm úsáideora ar an bhfreastalaí seo. D'fhéadfadh an t-ainm úsáideora céanna a bheith ag duine éigin ar fhreastalaí eile.", @@ -408,6 +409,8 @@ "collections.sensitive": "Íogair", "collections.topic_hint": "Cuir haischlib leis a chabhraíonn le daoine eile príomhábhar an bhailiúcháin seo a thuiscint.", "collections.topic_special_chars_hint": "Bainfear carachtair speisialta agus tú ag sábháil", + "collections.unlisted_collections_description": "Ní fheictear iad seo ar do phróifíl do dhaoine eile. Is féidir le duine ar bith a bhfuil an nasc aige iad a fháil amach.", + "collections.unlisted_collections_with_count": "Bailiúcháin neamhliostaithe ({count})", "collections.view_collection": "Féach ar bhailiúchán", "collections.view_other_collections_by_user": "Féach ar bhailiúcháin eile ón úsáideoir seo", "collections.visibility_public": "Poiblí", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 80550059b7..a0a2bc9f75 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -106,6 +106,7 @@ "account.name.copy": "Copiar identificador", "account.name.help.domain": "{domain} é o servidor que aloxa o perfil e as publicacións da usuaria.", "account.name.help.domain_self": "{domain} é o servidor que aloxa o teu perfil e publicacións.", + "account.name.help.footer": "Do mesmo xeito que podes enviar un correo electrónico a unha persoa que utiliza outro provedor, podes relacionarte con persoas doutros servidores Mastodon, e con calquera das outras aplicacións sociais compatibles con Mastodon.", "account.name.help.header": "Un alcume é o como o enderezo de correo", "account.name.help.username": "{username} é o nome de usuaria da conta no seu servidor. Alguén noutro servidor podería ter o mesmo nome de usuaria.", "account.name.help.username_self": "{username} é o teu nome de usuaria nete servidor. Alguén noutro sevidor podería ter o mesmo nome de usuaria.", @@ -360,6 +361,7 @@ "collections.account_count": "{count, plural, one {# conta} other {# contas}}", "collections.accounts.empty_description": "Engade ata {count} contas que segues", "collections.accounts.empty_title": "A colección está baleira", + "collections.block_collection_owner": "Bloquear conta", "collections.by_account": "de {account_handle}", "collections.collection_description": "Descrición", "collections.collection_language": "Idioma", @@ -369,6 +371,8 @@ "collections.confirm_account_removal": "Tes certeza de querer retirar esta conta desta colección?", "collections.content_warning": "Aviso sobre o contido", "collections.continue": "Continuar", + "collections.copy_link": "Copiar ligazón", + "collections.copy_link_confirmation": "Copiouse ao portapapeis a ligazón á colección", "collections.create.accounts_subtitle": "Só se poden engadir contas que segues e que optaron por ser incluídas en descubrir.", "collections.create.accounts_title": "A quen queres incluír nesta colección?", "collections.create.basic_details_title": "Detalles básicos", @@ -406,8 +410,11 @@ "collections.search_accounts_label": "Buscar contas para engadir…", "collections.search_accounts_max_reached": "Acadaches o máximo de contas permitidas", "collections.sensitive": "Sensible", + "collections.share_short": "Compartir", "collections.topic_hint": "Engadir un cancelo para que axudar a que outras persoas coñezan a temática desta colección.", "collections.topic_special_chars_hint": "Vanse eliminar os caracteres especiais ao gardar", + "collections.unlisted_collections_description": "Estas non se mostran no teu perfil, pero calquera que coñeza a ligazón pode velas.", + "collections.unlisted_collections_with_count": "Coleccións fóra do perfil ({count})", "collections.view_collection": "Ver colección", "collections.view_other_collections_by_user": "Ver outras coleccións desta usuaria", "collections.visibility_public": "Pública", @@ -1036,7 +1043,7 @@ "onboarding.profile.display_name": "Nome público", "onboarding.profile.display_name_hint": "O teu nome completo ou un nome divertido…", "onboarding.profile.finish": "Finalizar", - "onboarding.profile.note": "Acerca de ti", + "onboarding.profile.note": "Sobre ti", "onboarding.profile.note_hint": "Podes @mencionar a outras persoas ou usar #cancelos…", "onboarding.profile.title": "Configuración do perfil", "onboarding.profile.upload_avatar": "Subir imaxe do perfil", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index f3007fc25f..2150311462 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# aðgangur} other {# aðgangar}}", "collections.accounts.empty_description": "Bættu við allt að {count} aðgöngum sem þú fylgist með", "collections.accounts.empty_title": "Þetta safn er tómt", + "collections.block_collection_owner": "Útiloka notandaaðgang", "collections.by_account": "frá {account_handle}", "collections.collection_description": "Lýsing", "collections.collection_language": "Tungumál", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Ertu viss um að þú viljir fjarlægja þennan aðgang úr þessu safni?", "collections.content_warning": "Viðvörun vegna efnis", "collections.continue": "Halda áfram", + "collections.copy_link": "Afrita tengil", + "collections.copy_link_confirmation": "Afritaði tengil á safn á klippispjald", "collections.create.accounts_subtitle": "Einungis er hægt að bæta við notendum sem hafa samþykkt að vera með í opinberri birtingu.", "collections.create.accounts_title": "Hvern vilt þú gera áberandi í þessu safni?", "collections.create.basic_details_title": "Grunnupplýsingar", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Leita að aðgöngum til að bæta við…", "collections.search_accounts_max_reached": "Þú hefur þegar bætt við leyfilegum hámarksfjölda aðganga", "collections.sensitive": "Viðkvæmt", + "collections.share_short": "Deila", "collections.topic_hint": "Bættu við myllumerki sem hjálpar öðrum að skilja aðalefni þessa safns.", "collections.topic_special_chars_hint": "Sérstafir verða fjarlægðir við vistun", + "collections.unlisted_collections_description": "Þessi birtast ekki öðrum á notandasíðunni þinni. Þeir sem eru með tengil á þau geta séð þau.", + "collections.unlisted_collections_with_count": "Óskráð söfn ({count})", "collections.view_collection": "Skoða safn", "collections.view_other_collections_by_user": "Skoða önnur söfn frá þessum notanda", "collections.visibility_public": "Opinbert", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 5801715ac3..fda06fdb1a 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# account} other {# account}}", "collections.accounts.empty_description": "Aggiungi fino a {count} account che segui", "collections.accounts.empty_title": "Questa collezione è vuota", + "collections.block_collection_owner": "Blocca l'account", "collections.by_account": "di {account_handle}", "collections.collection_description": "Descrizione", "collections.collection_language": "Lingua", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Si è sicuri di voler rimuovere questo account da questa collezione?", "collections.content_warning": "Avviso sul contenuto", "collections.continue": "Continua", + "collections.copy_link": "Copia il сollegamento", + "collections.copy_link_confirmation": "Collegamento della collezione copiato negli appunti", "collections.create.accounts_subtitle": "Possono essere aggiunti solo gli account che segui e che hanno aderito alla funzione di scoperta.", "collections.create.accounts_title": "Chi includerai in questa collezione?", "collections.create.basic_details_title": "Dettagli di base", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Cerca account da aggiungere…", "collections.search_accounts_max_reached": "Hai aggiunto il numero massimo di account", "collections.sensitive": "Sensibile", + "collections.share_short": "Condividi", "collections.topic_hint": "Aggiungi un hashtag che aiuti gli altri a comprendere l'argomento principale di questa collezione.", "collections.topic_special_chars_hint": "I caratteri speciali verranno rimossi durante il salvataggio", + "collections.unlisted_collections_description": "Queste collezioni non sono visibili agli altri sul tuo profilo. Chiunque abbia i link può trovarle.", + "collections.unlisted_collections_with_count": "Collezioni non elencate ({count})", "collections.view_collection": "Visualizza la collezione", "collections.view_other_collections_by_user": "Visualizza altre collezioni da questo utente", "collections.visibility_public": "Pubblica", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 67256e2149..afdf82d6a1 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, one {# account} other {# accounts}}", "collections.accounts.empty_description": "Tot {count} accounts die je volgt toevoegen", "collections.accounts.empty_title": "Deze verzameling is leeg", + "collections.block_collection_owner": "Account blokkeren", "collections.by_account": "door {account_handle}", "collections.collection_description": "Omschrijving", "collections.collection_language": "Taal", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Weet je zeker dat je dit account uit deze verzameling wilt verwijderen?", "collections.content_warning": "Inhoudswaarschuwing", "collections.continue": "Doorgaan", + "collections.copy_link": "Link kopiëren", + "collections.copy_link_confirmation": "Verzamelinglink naar het klembord gekopieerd", "collections.create.accounts_subtitle": "Alleen accounts die je volgt en ontdekt willen worden, kunnen worden toegevoegd.", "collections.create.accounts_title": "Wie wil je aan deze verzameling toevoegen?", "collections.create.basic_details_title": "Basisgegevens", @@ -407,14 +410,17 @@ "collections.search_accounts_label": "Naar accounts zoeken om toe te voegen…", "collections.search_accounts_max_reached": "Je hebt het maximum aantal accounts toegevoegd", "collections.sensitive": "Gevoelig", + "collections.share_short": "Delen", "collections.topic_hint": "Voeg een hashtag toe die anderen helpt het hoofdonderwerp van deze verzameling te begrijpen.", "collections.topic_special_chars_hint": "Speciale tekens worden bij het opslaan verwijderd", + "collections.unlisted_collections_description": "Deze verschijnen niet voor anderen op je profiel. Iedereen met de link kan ze ontdekken.", + "collections.unlisted_collections_with_count": "Onzichtbare verzamelingen ({count})", "collections.view_collection": "Verzameling bekijken", "collections.view_other_collections_by_user": "Bekijk andere verzamelingen van deze gebruiker", "collections.visibility_public": "Openbaar", "collections.visibility_public_hint": "Te zien onder zoekresultaten en in andere gebieden waar aanbevelingen verschijnen.", "collections.visibility_title": "Zichtbaarheid", - "collections.visibility_unlisted": "Niet zichtbaar", + "collections.visibility_unlisted": "Onzichtbaar", "collections.visibility_unlisted_hint": "Zichtbaar voor iedereen met een link. Verborgen onder zoekresultaten en aanbevelingen.", "column.about": "Over", "column.blocks": "Geblokkeerde gebruikers", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index bdb382860c..ddf76ab27b 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -360,6 +360,7 @@ "collections.account_count": "{count, plural, one {# conta} other {# contas}}", "collections.accounts.empty_description": "Adicione até {count} contas que segue", "collections.accounts.empty_title": "Esta coleção está vazia", + "collections.block_collection_owner": "Bloquear conta", "collections.by_account": "por {account_handle}", "collections.collection_description": "Descrição", "collections.collection_language": "Idioma", @@ -369,6 +370,8 @@ "collections.confirm_account_removal": "Tem a certeza que quer remover esta conta desta coleção?", "collections.content_warning": "Aviso de conteúdo", "collections.continue": "Continuar", + "collections.copy_link": "Copiar hiperligação", + "collections.copy_link_confirmation": "A hiperligação da coleção foi copiada para a área de transferência", "collections.create.accounts_subtitle": "Apenas as contas que segue e que optaram por ser descobertas podem ser adicionadas.", "collections.create.accounts_title": "Quem vai destacar nesta coleção?", "collections.create.basic_details_title": "Informações básicas", @@ -404,6 +407,7 @@ "collections.search_accounts_label": "Procurar contas para adicionar…", "collections.search_accounts_max_reached": "Já adicionou o máximo de contas", "collections.sensitive": "Sensível", + "collections.share_short": "Partilhar", "collections.topic_hint": "Adicione uma etiqueta para ajudar outros a entender o tópico principal desta coleção.", "collections.topic_special_chars_hint": "Os carateres especiais serão removidos ao guardar", "collections.view_collection": "Ver coleções", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 33dfb83f46..afc6ca3d1a 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -404,6 +404,8 @@ "collections.sensitive": "Rezervat", "collections.topic_hint": "Shtoni një hashtag që ndihmon të tjerët të kuptojnë temën kryesore të këtij koleksion.", "collections.topic_special_chars_hint": "Shenjat e posaçme do të hiqen, kur ruhet", + "collections.unlisted_collections_description": "Këto nuk shfaqen në profilin tuaj për të tjerët. Cilido me lidhjen mund t’i zbulojë.", + "collections.unlisted_collections_with_count": "Koleksione të pashfaqur ({count})", "collections.view_collection": "Shiheni koleksionin", "collections.view_other_collections_by_user": "Shihni koleksione të tjera nga ky përdorues", "collections.visibility_public": "Publik", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 52a383a3a7..f7f8ececb9 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -359,6 +359,7 @@ "collections.account_count": "{count, plural, one {# konto} other {# konton}}", "collections.accounts.empty_description": "Lägg till upp till {count} konton som du följer", "collections.accounts.empty_title": "Denna samling är tom", + "collections.block_collection_owner": "Blockera konto", "collections.by_account": "av {account_handle}", "collections.collection_description": "Beskrivning", "collections.collection_language": "Språk", @@ -368,6 +369,7 @@ "collections.confirm_account_removal": "Är du säker på att du vill ta bort detta konto från denna samling?", "collections.content_warning": "Innehållsvarning", "collections.continue": "Fortsätt", + "collections.copy_link": "Kopiera länk", "collections.create.accounts_subtitle": "Endast konton som du följer som har valt att upptäcka kan läggas till.", "collections.create.accounts_title": "Vem kommer du att visa i den här samlingen?", "collections.create.basic_details_title": "Grundläggande detaljer", @@ -403,6 +405,7 @@ "collections.search_accounts_label": "Sök efter konton för att lägga till…", "collections.search_accounts_max_reached": "Du har lagt till maximalt antal konton", "collections.sensitive": "Känsligt", + "collections.share_short": "Dela", "collections.topic_hint": "Lägg till en hashtagg som hjälper andra att förstå huvudämnet i denna samling.", "collections.topic_special_chars_hint": "Specialtecken kommer att tas bort när du sparar", "collections.view_collection": "Visa samling", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index bba78d5d2b..f669d04515 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -409,6 +409,8 @@ "collections.sensitive": "Hassas", "collections.topic_hint": "Bu koleksiyonun ana konusunu başkalarının anlamasına yardımcı olacak bir etiket ekleyin.", "collections.topic_special_chars_hint": "Kaydederken özel karakterler silinecektir", + "collections.unlisted_collections_description": "Bunlar profilinizde başkalarına görünmez. Bağlantıya sahip olan herkes bunları görebilir.", + "collections.unlisted_collections_with_count": "Listelenmeyen koleksiyonlar ({count})", "collections.view_collection": "Koleksiyonu görüntüle", "collections.view_other_collections_by_user": "Bu kullanıcının diğer koleksiyonlarını görüntüle", "collections.visibility_public": "Herkese açık", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 38636f80da..6733c4d394 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, other {# tài khoản}}", "collections.accounts.empty_description": "Thêm tối đa {count} tài khoản mà bạn theo dõi", "collections.accounts.empty_title": "Gói khởi đầu này trống", + "collections.block_collection_owner": "Chặn tài khoản", "collections.by_account": "bởi {account_handle}", "collections.collection_description": "Mô tả", "collections.collection_language": "Ngôn ngữ", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "Bạn có chắc muốn gỡ tài khoản này khỏi gói khởi đầu?", "collections.content_warning": "Nội dung ẩn", "collections.continue": "Tiếp tục", + "collections.copy_link": "Sao chép liên kết", + "collections.copy_link_confirmation": "Sao chép liên kết tài khoản vào clipboard", "collections.create.accounts_subtitle": "Chỉ những tài khoản bạn theo dõi và đã chọn khám phá mới có thể được thêm vào.", "collections.create.accounts_title": "Bạn sẽ chọn ai để giới thiệu trong gói khởi đầu này?", "collections.create.basic_details_title": "Thông tin cơ bản", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "Tìm tài khoản để thêm…", "collections.search_accounts_max_reached": "Bạn đã đạt đến số lượng tài khoản tối đa", "collections.sensitive": "Nhạy cảm", + "collections.share_short": "Chia sẻ", "collections.topic_hint": "Thêm hashtag giúp người khác hiểu chủ đề chính của gói khởi đầu này.", "collections.topic_special_chars_hint": "Những ký tự đặc biệt sẽ bị xóa khi lưu", + "collections.unlisted_collections_description": "Những thông tin này sẽ không hiển thị trên hồ sơ của bạn. Bất cứ ai có liên kết đều có thể tìm thấy chúng.", + "collections.unlisted_collections_with_count": "Gói khởi đầu hạn chế ({count})", "collections.view_collection": "Xem gói khởi đầu", "collections.view_other_collections_by_user": "Xem những gói khởi đầu khác từ tài khoản này", "collections.visibility_public": "Công khai", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 58a15ef752..3498733f9b 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, other {# 个账号}}", "collections.accounts.empty_description": "添加你关注的账号,最多 {count} 个", "collections.accounts.empty_title": "收藏列表为空", + "collections.block_collection_owner": "屏蔽账户", "collections.by_account": "由 {account_handle}", "collections.collection_description": "说明", "collections.collection_language": "语言", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "你确定要将从收藏列表中移除此账号吗?", "collections.content_warning": "内容警告", "collections.continue": "继续", + "collections.copy_link": "复制链接", + "collections.copy_link_confirmation": "已复制收藏列表链接到剪贴板", "collections.create.accounts_subtitle": "只有你关注的且已经主动加入发现功能的账号可以添加。", "collections.create.accounts_title": "你想在收藏列表中添加哪些人?", "collections.create.basic_details_title": "基本信息", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "搜索要添加的账号…", "collections.search_accounts_max_reached": "你添加的账号数量已达上限", "collections.sensitive": "敏感内容", + "collections.share_short": "分享", "collections.topic_hint": "添加话题标签,帮助他人了解此收藏列表的主题。", "collections.topic_special_chars_hint": "保存时将自动移除特殊字符", + "collections.unlisted_collections_description": "这些收藏列表在他人查看时不会出现在你的个人资料上。任何知晓链接的人可以发现这些列表。", + "collections.unlisted_collections_with_count": "静默公开的收藏列表({count})", "collections.view_collection": "查看收藏列表", "collections.view_other_collections_by_user": "查看此用户的其他收藏列表", "collections.visibility_public": "公开", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 128129af53..d5e9b0d7c1 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -361,6 +361,7 @@ "collections.account_count": "{count, plural, other {# 個帳號}}", "collections.accounts.empty_description": "加入最多 {count} 個您跟隨之帳號", "collections.accounts.empty_title": "此收藏名單是空的", + "collections.block_collection_owner": "封鎖帳號", "collections.by_account": "來自 {account_handle}", "collections.collection_description": "說明", "collections.collection_language": "語言", @@ -370,6 +371,8 @@ "collections.confirm_account_removal": "您是否確定要自此收藏名單中移除此帳號?", "collections.content_warning": "內容警告", "collections.continue": "繼續", + "collections.copy_link": "複製連結", + "collections.copy_link_confirmation": "複製收藏名單連結至剪貼簿", "collections.create.accounts_subtitle": "僅能加入您跟隨並選擇加入探索功能之帳號。", "collections.create.accounts_title": "您想於此收藏名單推薦誰?", "collections.create.basic_details_title": "基本資料", @@ -407,8 +410,11 @@ "collections.search_accounts_label": "搜尋帳號以加入...", "collections.search_accounts_max_reached": "您新增之帳號數已達上限", "collections.sensitive": "敏感內容", + "collections.share_short": "分享", "collections.topic_hint": "新增主題標籤以協助其他人瞭解此收藏名單之主題。", "collections.topic_special_chars_hint": "特殊字元將於儲存時被移除", + "collections.unlisted_collections_description": "這些將不會於您的個人檔案對他人顯示。但任何擁有連結之人皆能發現其內容。", + "collections.unlisted_collections_with_count": "未公開收藏名單 ({count})", "collections.view_collection": "檢視收藏名單", "collections.view_other_collections_by_user": "檢視此使用者之其他收藏名單", "collections.visibility_public": "公開", diff --git a/config/locales/de.yml b/config/locales/de.yml index adf0596f46..353f262fbd 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -2226,7 +2226,7 @@ de: extra_instructions_html: Hinweis: Der Link auf deiner Website kann unsichtbar sein. Der wichtige Teil ist rel="me". Du kannst auch den Tag link im head (statt a im body) verwenden, jedoch muss die Internetseite ohne JavaScript abrufbar sein. here_is_how: So funktioniert’s hint_html: "Alle können ihre Identität auf Mastodon verifizieren. Basierend auf offenen Standards – jetzt und für immer kostenlos. Alles, was du brauchst, ist eine eigene Website. Wenn du von deinem Profil auf diese Website verlinkst, überprüfen wir, ob die Website zu deinem Profil zurückverlinkt, und zeigen einen visuellen Hinweis an." - instructions_html: Kopiere den unten stehenden Code und füge ihn in den HTML-Code deiner Website ein. Trage anschließend die Adresse deiner Website in ein Zusatzfeld auf deinem Profil ein und speichere die Änderungen. Die Zusatzfelder können „Profil bearbeiten“ verwaltet werden. + instructions_html: Kopiere den unten stehenden Code und füge ihn in den HTML-Code deiner Website ein. Trage anschließend die Adresse deiner Website in ein Zusatzfeld auf deinem Profil ein und speichere die Änderungen. Die Zusatzfelder können unter „Profil bearbeiten“ verwaltet werden. verification: Verifizierung verified_links: Deine verifizierten Links website_verification: Verifizierung einer Website diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 7e24896d8f..ce50173b5c 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -562,7 +562,7 @@ gl: confirm_purge: Tes a certeza de querer eliminar permanentemente os datos deste dominio? content_policies: comment: Nota interna - description_html: Podes definir políticas acerca do contido que serán aplicadas a tódalas contas deste dominio e tódolos seus subdominios. + description_html: Podes definir políticas sobre o contido que serán aplicadas a todas as contas de este dominio e todos os seus subdominios. limited_federation_mode_description_html: Podes escoller se permites ou non a federación con este dominio. policies: reject_media: Rexeitar multimedia diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 18f6dac9bc..e625ee5395 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -233,7 +233,7 @@ gl: locale: Idioma da interface max_uses: Número máximo de usos new_password: Novo contrasinal - note: Acerca de ti + note: Sobre ti otp_attempt: Código do Segundo Factor password: Contrasinal phrase: Palabra chave ou frase From 89611bf32c97d012d46f347d375aa43ea5d46b51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:45:58 +0200 Subject: [PATCH 06/37] Update dependency @rolldown/plugin-babel to v0.2.3 (#38661) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 475af06526..aa21c33fcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3824,10 +3824,10 @@ __metadata: linkType: hard "@rolldown/plugin-babel@npm:^0.2.2": - version: 0.2.2 - resolution: "@rolldown/plugin-babel@npm:0.2.2" + version: 0.2.3 + resolution: "@rolldown/plugin-babel@npm:0.2.3" dependencies: - picomatch: "npm:^4.0.3" + picomatch: "npm:^4.0.4" peerDependencies: "@babel/core": ^7.29.0 || ^8.0.0-rc.1 "@babel/plugin-transform-runtime": ^7.29.0 || ^8.0.0-rc.1 @@ -3841,7 +3841,7 @@ __metadata: optional: true vite: optional: true - checksum: 10c0/d00d6afd831c1efa5eac8bbe9eb4c78abfe731c744ffac99d2246e78a94ac8546ee26d0a304541143427e59a846c96d3d5728da540293d02c37bcbe3972428b3 + checksum: 10c0/84afe9854137b576f963f4fd11080dc0b8afe6243d565513d33ddfed9ea9149652110def881a3911b5e889f5fa505df036c6839f4d0fd01d4222297d7f2baa21 languageName: node linkType: hard From b17c544f1d34b5de958a839d76bf93540080e8e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:46:04 +0200 Subject: [PATCH 07/37] Update dependency postcss-preset-env to v11.2.1 (#38656) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 252 +++++++++++++++++++++++++++--------------------------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/yarn.lock b/yarn.lock index aa21c33fcc..b36d76d4ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1241,26 +1241,26 @@ __metadata: languageName: node linkType: hard -"@csstools/css-calc@npm:^3.1.1": - version: 3.1.1 - resolution: "@csstools/css-calc@npm:3.1.1" +"@csstools/css-calc@npm:^3.1.1, @csstools/css-calc@npm:^3.2.0": + version: 3.2.0 + resolution: "@csstools/css-calc@npm:3.2.0" peerDependencies: "@csstools/css-parser-algorithms": ^4.0.0 "@csstools/css-tokenizer": ^4.0.0 - checksum: 10c0/6efcc016d988edf66e54c7bad03e352d61752cbd1b56c7557fd013868aab23505052ded8f912cd4034e216943ea1e04c957d81012489e3eddc14a57b386510ef + checksum: 10c0/a4dffeef3cb8ec9e8c1e44fa68c7634033050be52ea0b56ba6ac3815b635b587c6f3a8f8cd7b8f53881c2dd0ab9ec0af77227c532ed81b8e24a05aa997d22337 languageName: node linkType: hard -"@csstools/css-color-parser@npm:^4.0.2": - version: 4.0.2 - resolution: "@csstools/css-color-parser@npm:4.0.2" +"@csstools/css-color-parser@npm:^4.0.2, @csstools/css-color-parser@npm:^4.1.0": + version: 4.1.0 + resolution: "@csstools/css-color-parser@npm:4.1.0" dependencies: "@csstools/color-helpers": "npm:^6.0.2" - "@csstools/css-calc": "npm:^3.1.1" + "@csstools/css-calc": "npm:^3.2.0" peerDependencies: "@csstools/css-parser-algorithms": ^4.0.0 "@csstools/css-tokenizer": ^4.0.0 - checksum: 10c0/487cf507ef4630f74bd67d84298294ed269900b206ade015a968d20047e07ff46f235b72e26fe0c6b949a03f8f9f00a22c363da49c1b06ca60b32d0188e546be + checksum: 10c0/b5b8a697b4c1b22dd535b4d93b2ffce338d38e587ac1ab20b781c08328bfa99e5f763a99d990983560df543862fa9bd578ee966c18f9d3381c8e33c641d32a0e languageName: node linkType: hard @@ -1302,18 +1302,18 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-alpha-function@npm:^2.0.3": - version: 2.0.3 - resolution: "@csstools/postcss-alpha-function@npm:2.0.3" +"@csstools/postcss-alpha-function@npm:^2.0.4": + version: 2.0.4 + resolution: "@csstools/postcss-alpha-function@npm:2.0.4" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/79d2b5c19b59cdf64377cae88c83e39411b82d4b87bd55a6f39bf85e0c2e4904ae73d329c2db713274dafd044dbd1cfcbcf5018536ccb16a3024555c3f9d0927 + checksum: 10c0/143e13dd1be8701736f5e4236634f3c1e64b03fa9ee56ba0195203fafad4fa52fa32801d1beb201ca6eb49265f9b1a4a54ca0ade62ac1745ab1827d7496cf267 languageName: node linkType: hard @@ -1329,63 +1329,63 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-color-function-display-p3-linear@npm:^2.0.2": - version: 2.0.2 - resolution: "@csstools/postcss-color-function-display-p3-linear@npm:2.0.2" +"@csstools/postcss-color-function-display-p3-linear@npm:^2.0.3": + version: 2.0.3 + resolution: "@csstools/postcss-color-function-display-p3-linear@npm:2.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/e20a66b4c186e66b47fa6b01d01b0d6ea061b520a06720a759d12c72c41a6286d97fdb4f5d23b28ffeff1b8d30ecc963e1b7266dae86a4bc63e14a3743a77bc4 + checksum: 10c0/263e7ce1a6ef9c4d9a070ac11168d94567121cebffdc001f2a7c97d5f171aaf69930076114c0132e8a26164572f4a9d863098f4a206cdb038b34bc7aa6e73b4b languageName: node linkType: hard -"@csstools/postcss-color-function@npm:^5.0.2": - version: 5.0.2 - resolution: "@csstools/postcss-color-function@npm:5.0.2" +"@csstools/postcss-color-function@npm:^5.0.3": + version: 5.0.3 + resolution: "@csstools/postcss-color-function@npm:5.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/ca255931d37d07f02e4401eef30f70bd3aa9c56ca6cac9d9beeca8e2f07c9909f31f5431347fc408522ebff892a51312afee0fcb9bdf6bc0e680e1585e0f9247 + checksum: 10c0/a43178f6dcffbbe4acdbcdf881a463ba6891eb6eff5ecfe46f14712fac2dddaa1dbb34dd0f067fdb09ee1f9902aef171bc75acc8ad35483a871700a9c2cc19b1 languageName: node linkType: hard -"@csstools/postcss-color-mix-function@npm:^4.0.2": - version: 4.0.2 - resolution: "@csstools/postcss-color-mix-function@npm:4.0.2" +"@csstools/postcss-color-mix-function@npm:^4.0.3": + version: 4.0.3 + resolution: "@csstools/postcss-color-mix-function@npm:4.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/c3411f91d1d1923fc1cd27f74dad4371466941236b30a25d97f2d50e255fd5fb37c3ec1f785958e13e4b5a851c098913da507e9f0ea2e176997daa16b6679c8c + checksum: 10c0/450f9f4eaf3beda9bf54b3245defd7dd4d10d29b9e61f2a238770f9d06f975a4164d8ac7554442b919cdffab7521d7d167f6691c152c53a1dea162dadac2f939 languageName: node linkType: hard -"@csstools/postcss-color-mix-variadic-function-arguments@npm:^2.0.2": - version: 2.0.2 - resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:2.0.2" +"@csstools/postcss-color-mix-variadic-function-arguments@npm:^2.0.3": + version: 2.0.3 + resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:2.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/3b658ca2e2c7f7224c01e4661178a0c2e69101cd815f86c65742d98cd5575b073232a8b5f7600113d929b4d0f9f5ada4364f58a74e26d26f73378a7e1fa8c7cb + checksum: 10c0/e154e98f529a2fd19fae26baedb55957041ec46aca2a7c40b240b592b0950ba8febba0970cfa2d22333fe6eaf0bf4e0a5e7e453c24475cd2a413365663a35195 languageName: node linkType: hard @@ -1403,31 +1403,31 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-contrast-color-function@npm:^3.0.2": - version: 3.0.2 - resolution: "@csstools/postcss-contrast-color-function@npm:3.0.2" +"@csstools/postcss-contrast-color-function@npm:^3.0.3": + version: 3.0.3 + resolution: "@csstools/postcss-contrast-color-function@npm:3.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/f8865c5a387a8c54c91a9dc25c2ba64311eabed2cea992843979ca672d3244089a1adf1d1ba1533c0d99f0e143d90ad5af2b30e841609bd3ceccb7f9920edfaf + checksum: 10c0/b3c218e627e757724eb208501be195d65c08d68749e54af8f8a7f0e2590aabb76ea5c8500b68d00af9b4e661a4506a6919fddb9897cbf6e2e29ac2f3e084d3dd languageName: node linkType: hard -"@csstools/postcss-exponential-functions@npm:^3.0.1": - version: 3.0.1 - resolution: "@csstools/postcss-exponential-functions@npm:3.0.1" +"@csstools/postcss-exponential-functions@npm:^3.0.2": + version: 3.0.2 + resolution: "@csstools/postcss-exponential-functions@npm:3.0.2" dependencies: - "@csstools/css-calc": "npm:^3.1.1" + "@csstools/css-calc": "npm:^3.2.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/3cffb90647640bae836e91b7909b4c92fdb71fd4ca5498c49868e70a924a17307206623f5f66129ece2feec0b3abb54d3259653364d1073935af4b0c8afe40a5 + checksum: 10c0/70be8c3cd42b72d3955ab122cf89a70a61674341947452f33dfdc3b2371f969bbd366e5824b4ad62ec4227067b830346783b5deb9170cc8c23ccca3c2fb04cc5 languageName: node linkType: hard @@ -1454,46 +1454,46 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-gamut-mapping@npm:^3.0.2": - version: 3.0.2 - resolution: "@csstools/postcss-gamut-mapping@npm:3.0.2" +"@csstools/postcss-gamut-mapping@npm:^3.0.3": + version: 3.0.3 + resolution: "@csstools/postcss-gamut-mapping@npm:3.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/af4eea62e651be5d1470c425feea29a832f092008d4d3a88a0ee2b3b703c2cd1707b36456611806bfacdbd05754e007f0e0d9f604b3e0aadf09a94e808735439 + checksum: 10c0/161a8485b91fd1d9228978619e2037936353ac60cf7e7a829c300efc308598b376d734e50582a6c44de51c124486685e217ab549c8a97ee1a7427ba15debb242 languageName: node linkType: hard -"@csstools/postcss-gradients-interpolation-method@npm:^6.0.2": - version: 6.0.2 - resolution: "@csstools/postcss-gradients-interpolation-method@npm:6.0.2" +"@csstools/postcss-gradients-interpolation-method@npm:^6.0.3": + version: 6.0.3 + resolution: "@csstools/postcss-gradients-interpolation-method@npm:6.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/85f472ed7609dd608a3bce267b9bae9f06a6241e52df0901f1a51cb5432cb642fadaa24812abbe12b14fd9c49a4333ea191f8d11815c7f7e9c904b127ed9f6e0 + checksum: 10c0/8bf699dc7e5c071512ebba84b1b9f0367bf43d96b55a19d34032865883f7f33adb9cf21c824915dd451b7eebb24102e61cf5e38343301290594d7dae3e1ac610 languageName: node linkType: hard -"@csstools/postcss-hwb-function@npm:^5.0.2": - version: 5.0.2 - resolution: "@csstools/postcss-hwb-function@npm:5.0.2" +"@csstools/postcss-hwb-function@npm:^5.0.3": + version: 5.0.3 + resolution: "@csstools/postcss-hwb-function@npm:5.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/376c3e0a22856afb3304cc4a24e27d502e580637d9495426195f269d9d8348e42754f0d8a2aaac6d8fb5a64e82954f90e3ee358ca7af520ca295edd9fc7ddd20 + checksum: 10c0/16ba746bcd992bba0c34c22f84ed64a67ff03274266832bf32aa0a553fe96c32a803ae7484c248652db0ddab9f3ad942dbe684d33bbb247551b2be9cfc918959 languageName: node linkType: hard @@ -1595,17 +1595,17 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-media-minmax@npm:^3.0.1": - version: 3.0.1 - resolution: "@csstools/postcss-media-minmax@npm:3.0.1" +"@csstools/postcss-media-minmax@npm:^3.0.2": + version: 3.0.2 + resolution: "@csstools/postcss-media-minmax@npm:3.0.2" dependencies: - "@csstools/css-calc": "npm:^3.1.1" + "@csstools/css-calc": "npm:^3.2.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/media-query-list-parser": "npm:^5.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/d5675f4b3b07c41763d28f0ab7f89548c4c5e1633fa09c94a8289ea099bcc4a9c123bed7f2c12af3c2adb32f6afb2b3b107eeb071f9eff4da2676b778ca602af + checksum: 10c0/aefdab19cd1c839bbb455536b80b308f6e16e3302deac944ed826f3b6333c78ba7b5f2df39259f0cefa19c01c6569cdbbd9f52914de0e781a2f039031fdf931d languageName: node linkType: hard @@ -1657,18 +1657,18 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-oklab-function@npm:^5.0.2": - version: 5.0.2 - resolution: "@csstools/postcss-oklab-function@npm:5.0.2" +"@csstools/postcss-oklab-function@npm:^5.0.3": + version: 5.0.3 + resolution: "@csstools/postcss-oklab-function@npm:5.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/675c43880ebc92ecdbf14e9b614f8c7ca3ecbb4c58d68b615ac1056eeafc017eaeff360c65b271836268e7ab26983a0c6a3bbdaa974986409a2516288a0efe40 + checksum: 10c0/5995f9589295e9e60ab0c39f200b5dc3254cf33d8e264f21fcd804387eabc64c989e8d97a950c99c2b4c9ecb6957a93a0a50e18fa643726b0e364c41f63c9c84 languageName: node linkType: hard @@ -1704,31 +1704,31 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-random-function@npm:^3.0.1": - version: 3.0.1 - resolution: "@csstools/postcss-random-function@npm:3.0.1" +"@csstools/postcss-random-function@npm:^3.0.2": + version: 3.0.2 + resolution: "@csstools/postcss-random-function@npm:3.0.2" dependencies: - "@csstools/css-calc": "npm:^3.1.1" + "@csstools/css-calc": "npm:^3.2.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/de495b71abec80bbe80d16dbd7c74edf2f1e4dcb8ef6061ee027713416a69a8840accb2db8a52f93750f2d2e1bc27b9548fd11ba349f1ed25f995fb96201418d + checksum: 10c0/724ae46ee669c8e17315c3354ebf0e3ed7cc3cfb1633017685cfbbeb8870fe671574dee03d4470f50f68089d141f24dc507c586cac16ab2aca0a7db7b241943a languageName: node linkType: hard -"@csstools/postcss-relative-color-syntax@npm:^4.0.2": - version: 4.0.2 - resolution: "@csstools/postcss-relative-color-syntax@npm:4.0.2" +"@csstools/postcss-relative-color-syntax@npm:^4.0.3": + version: 4.0.3 + resolution: "@csstools/postcss-relative-color-syntax@npm:4.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/d4ad2d3a5b30a52749db95e1b987d0d3fa31a4f589620201514725707978de870a74702d7010eaef662c9a13e675c5877eb77a84bd47f3e1d853d10e93de5a06 + checksum: 10c0/e4f99ec2a54438f399ae4011d856395806dba614b893cdc86476cc35ecab6c322bd8b5f28dc689452681bc7fe7f86100aa36427a6e8a90f84ef3d777eb856fa4 languageName: node linkType: hard @@ -1743,29 +1743,29 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-sign-functions@npm:^2.0.1": - version: 2.0.1 - resolution: "@csstools/postcss-sign-functions@npm:2.0.1" +"@csstools/postcss-sign-functions@npm:^2.0.2": + version: 2.0.2 + resolution: "@csstools/postcss-sign-functions@npm:2.0.2" dependencies: - "@csstools/css-calc": "npm:^3.1.1" + "@csstools/css-calc": "npm:^3.2.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/c521746dc96b755a42d05defb21b8f3835eca46881c38f1919375711950b6138991a3b39ecb0198224804626cb55e69bca9906d9b0806b451e2e7fb452896e37 + checksum: 10c0/dfb4714fe49793b8e5466ae1498109e35c843eda513ed20827fd32469f6cafd633f4d2d616c9f7e9df9512a3cda8daef9c56e3ce4bc1436dddb7892a46a8cf39 languageName: node linkType: hard -"@csstools/postcss-stepped-value-functions@npm:^5.0.1": - version: 5.0.1 - resolution: "@csstools/postcss-stepped-value-functions@npm:5.0.1" +"@csstools/postcss-stepped-value-functions@npm:^5.0.2": + version: 5.0.2 + resolution: "@csstools/postcss-stepped-value-functions@npm:5.0.2" dependencies: - "@csstools/css-calc": "npm:^3.1.1" + "@csstools/css-calc": "npm:^3.2.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/f5f90977919bec2aabdfddfebe4cf9e4278cb5c53b2e2d16b23162011f4c112048dc365614b25c429e0fa793c95e1cd77e2697a255d5c03d73e25a614f27deab + checksum: 10c0/c8e06be85cbfee602cfb429695484ca9420a0f6cb1e9d5627cc35d3a03b09a8e059aa9b20f7bbd91b356dfd79c53c06043a962a611c5b36253826904fbc17f67 languageName: node linkType: hard @@ -1804,16 +1804,16 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-trigonometric-functions@npm:^5.0.1": - version: 5.0.1 - resolution: "@csstools/postcss-trigonometric-functions@npm:5.0.1" +"@csstools/postcss-trigonometric-functions@npm:^5.0.2": + version: 5.0.2 + resolution: "@csstools/postcss-trigonometric-functions@npm:5.0.2" dependencies: - "@csstools/css-calc": "npm:^3.1.1" + "@csstools/css-calc": "npm:^3.2.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/3a941fab1bfdeb5ce1ca388824a9606e3b973078b3a153aaf6bad5edf6401fd421ff7fd7cc8acc41b4e05cd67d2f02cfac3b2015dd3e9f5fc0135b15ef3d3d2c + checksum: 10c0/95a25be56a6e1bd390a025b5c72fb6bf7db329711717a663454d8c13e70971d92126c33c683047f283cd44dd99ac4101e37f9f56a44afbee8161cf95aeaec482 languageName: node linkType: hard @@ -11245,18 +11245,18 @@ __metadata: languageName: node linkType: hard -"postcss-color-functional-notation@npm:^8.0.2": - version: 8.0.2 - resolution: "postcss-color-functional-notation@npm:8.0.2" +"postcss-color-functional-notation@npm:^8.0.3": + version: 8.0.3 + resolution: "postcss-color-functional-notation@npm:8.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/62322c62db70098112f00d4827465d07407e3394471b197f938a7c47d087025021281d36a882a9631b4840bef5c3413f61dca0afcf59c5e4a0f51c53b5edc3c8 + checksum: 10c0/e7e40e4173734b19fe714ccbc907460743a46a93a59c0fb045496a070f98b5e309f2b5a5e773510af445d4ecbe8dda25dff2228106948764423e9de609d03414 languageName: node linkType: hard @@ -11403,18 +11403,18 @@ __metadata: languageName: node linkType: hard -"postcss-lab-function@npm:^8.0.2": - version: 8.0.2 - resolution: "postcss-lab-function@npm:8.0.2" +"postcss-lab-function@npm:^8.0.3": + version: 8.0.3 + resolution: "postcss-lab-function@npm:8.0.3" dependencies: - "@csstools/css-color-parser": "npm:^4.0.2" + "@csstools/css-color-parser": "npm:^4.1.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" "@csstools/css-tokenizer": "npm:^4.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/eb1410dd93492e83a9a2af91e616b75f1346a4427e130b60d3b7497d28d3e90819235eff5d8d703d4a01864f9d75b679430b59226fc65307cc14d7f7ab359501 + checksum: 10c0/f2d1246d3f92333e0ad5cbae9060146080ea65b48b9f57779bf5ed8cca73f6bc1ff35217a10688cdfcd3427c37e14b1453485b6e535de55fe5c8945dbbb2201c languageName: node linkType: hard @@ -11541,23 +11541,23 @@ __metadata: linkType: hard "postcss-preset-env@npm:^11.0.0": - version: 11.2.0 - resolution: "postcss-preset-env@npm:11.2.0" + version: 11.2.1 + resolution: "postcss-preset-env@npm:11.2.1" dependencies: - "@csstools/postcss-alpha-function": "npm:^2.0.3" + "@csstools/postcss-alpha-function": "npm:^2.0.4" "@csstools/postcss-cascade-layers": "npm:^6.0.0" - "@csstools/postcss-color-function": "npm:^5.0.2" - "@csstools/postcss-color-function-display-p3-linear": "npm:^2.0.2" - "@csstools/postcss-color-mix-function": "npm:^4.0.2" - "@csstools/postcss-color-mix-variadic-function-arguments": "npm:^2.0.2" + "@csstools/postcss-color-function": "npm:^5.0.3" + "@csstools/postcss-color-function-display-p3-linear": "npm:^2.0.3" + "@csstools/postcss-color-mix-function": "npm:^4.0.3" + "@csstools/postcss-color-mix-variadic-function-arguments": "npm:^2.0.3" "@csstools/postcss-content-alt-text": "npm:^3.0.0" - "@csstools/postcss-contrast-color-function": "npm:^3.0.2" - "@csstools/postcss-exponential-functions": "npm:^3.0.1" + "@csstools/postcss-contrast-color-function": "npm:^3.0.3" + "@csstools/postcss-exponential-functions": "npm:^3.0.2" "@csstools/postcss-font-format-keywords": "npm:^5.0.0" "@csstools/postcss-font-width-property": "npm:^1.0.0" - "@csstools/postcss-gamut-mapping": "npm:^3.0.2" - "@csstools/postcss-gradients-interpolation-method": "npm:^6.0.2" - "@csstools/postcss-hwb-function": "npm:^5.0.2" + "@csstools/postcss-gamut-mapping": "npm:^3.0.3" + "@csstools/postcss-gradients-interpolation-method": "npm:^6.0.3" + "@csstools/postcss-hwb-function": "npm:^5.0.3" "@csstools/postcss-ic-unit": "npm:^5.0.0" "@csstools/postcss-initial": "npm:^3.0.0" "@csstools/postcss-is-pseudo-class": "npm:^6.0.0" @@ -11567,24 +11567,24 @@ __metadata: "@csstools/postcss-logical-overscroll-behavior": "npm:^3.0.0" "@csstools/postcss-logical-resize": "npm:^4.0.0" "@csstools/postcss-logical-viewport-units": "npm:^4.0.0" - "@csstools/postcss-media-minmax": "npm:^3.0.1" + "@csstools/postcss-media-minmax": "npm:^3.0.2" "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^4.0.0" "@csstools/postcss-mixins": "npm:^1.0.0" "@csstools/postcss-nested-calc": "npm:^5.0.0" "@csstools/postcss-normalize-display-values": "npm:^5.0.1" - "@csstools/postcss-oklab-function": "npm:^5.0.2" + "@csstools/postcss-oklab-function": "npm:^5.0.3" "@csstools/postcss-position-area-property": "npm:^2.0.0" "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" "@csstools/postcss-property-rule-prelude-list": "npm:^2.0.0" - "@csstools/postcss-random-function": "npm:^3.0.1" - "@csstools/postcss-relative-color-syntax": "npm:^4.0.2" + "@csstools/postcss-random-function": "npm:^3.0.2" + "@csstools/postcss-relative-color-syntax": "npm:^4.0.3" "@csstools/postcss-scope-pseudo-class": "npm:^5.0.0" - "@csstools/postcss-sign-functions": "npm:^2.0.1" - "@csstools/postcss-stepped-value-functions": "npm:^5.0.1" + "@csstools/postcss-sign-functions": "npm:^2.0.2" + "@csstools/postcss-stepped-value-functions": "npm:^5.0.2" "@csstools/postcss-syntax-descriptor-syntax-production": "npm:^2.0.0" "@csstools/postcss-system-ui-font-family": "npm:^2.0.0" "@csstools/postcss-text-decoration-shorthand": "npm:^5.0.3" - "@csstools/postcss-trigonometric-functions": "npm:^5.0.1" + "@csstools/postcss-trigonometric-functions": "npm:^5.0.2" "@csstools/postcss-unset-value": "npm:^5.0.0" autoprefixer: "npm:^10.4.24" browserslist: "npm:^4.28.1" @@ -11594,7 +11594,7 @@ __metadata: cssdb: "npm:^8.8.0" postcss-attribute-case-insensitive: "npm:^8.0.0" postcss-clamp: "npm:^4.1.0" - postcss-color-functional-notation: "npm:^8.0.2" + postcss-color-functional-notation: "npm:^8.0.3" postcss-color-hex-alpha: "npm:^11.0.0" postcss-color-rebeccapurple: "npm:^11.0.0" postcss-custom-media: "npm:^12.0.1" @@ -11607,7 +11607,7 @@ __metadata: postcss-font-variant: "npm:^5.0.0" postcss-gap-properties: "npm:^7.0.0" postcss-image-set-function: "npm:^8.0.0" - postcss-lab-function: "npm:^8.0.2" + postcss-lab-function: "npm:^8.0.3" postcss-logical: "npm:^9.0.0" postcss-nesting: "npm:^14.0.0" postcss-opacity-percentage: "npm:^3.0.0" @@ -11619,7 +11619,7 @@ __metadata: postcss-selector-not: "npm:^9.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/df4254d95efcb06825b393bb7b65ea908614e3fa8c21accfa232984123cd33ffdebadbea542f925f48a7e501a4051ed97624b7012e5b31a479055eb71af96208 + checksum: 10c0/fe1a295feac4cab319a3ed6ddb375e1ee5543683d8121eb20959d325f6b1fdd40d4e38c1719e3c76f56579c48a2f45910b4a20b4519c09d5690e46a5b95c6392 languageName: node linkType: hard From e05ac2ec0464bf334a77f38b1cb8085628df2156 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:46:12 +0200 Subject: [PATCH 08/37] Update dependency dotenv to v17.4.2 (#38655) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b36d76d4ca..8e6a1c3ea6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6888,9 +6888,9 @@ __metadata: linkType: hard "dotenv@npm:^17.0.0": - version: 17.4.1 - resolution: "dotenv@npm:17.4.1" - checksum: 10c0/eb6ae592ee1e8f6b7f62e0f0c2827c4f27bbe9e00abe9d7910e06456e63dccd42f011de94da9450a63c07ccc52c318f527c1b56ad8e2c81a1a16314b53d4fd99 + version: 17.4.2 + resolution: "dotenv@npm:17.4.2" + checksum: 10c0/164f8e77a646c8446867d5b588d26ea6005c8ea7c5eb41cf926f6113d23f2191355f6e0cfd95ea9bab98394a5b0a3f1e51a8399711b666fe55cc7b0bd745f942 languageName: node linkType: hard From 961acaf202981efc286c4eca9ed5a15e98a426bc Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 16 Apr 2026 12:09:39 +0200 Subject: [PATCH 09/37] Include collection url in API responses (#38708) --- app/serializers/rest/collection_serializer.rb | 12 +++++++++++- spec/serializers/rest/collection_serializer_spec.rb | 9 ++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/serializers/rest/collection_serializer.rb b/app/serializers/rest/collection_serializer.rb index 8354699710..98dbabb949 100644 --- a/app/serializers/rest/collection_serializer.rb +++ b/app/serializers/rest/collection_serializer.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true class REST::CollectionSerializer < ActiveModel::Serializer + include RoutingHelper + attributes :id, :uri, :name, :description, :language, :account_id, - :local, :sensitive, :discoverable, :item_count, + :local, :sensitive, :discoverable, :url, :item_count, :created_at, :updated_at belongs_to :tag, serializer: REST::ShallowTagSerializer @@ -17,6 +19,10 @@ class REST::CollectionSerializer < ActiveModel::Serializer ActivityPub::TagManager.instance.uri_for(object) end + def url + object.local? ? account_collection_url(object.account, object) : object.url + end + def description return object.description if object.local? return if object.description_html.nil? @@ -31,4 +37,8 @@ class REST::CollectionSerializer < ActiveModel::Serializer def account_id object.account_id.to_s end + + def tag + object.tag + end end diff --git a/spec/serializers/rest/collection_serializer_spec.rb b/spec/serializers/rest/collection_serializer_spec.rb index ded12f229e..0ff98c4cb1 100644 --- a/spec/serializers/rest/collection_serializer_spec.rb +++ b/spec/serializers/rest/collection_serializer_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' RSpec.describe REST::CollectionSerializer do + include RoutingHelper + subject do serialized_record_json(collection, described_class, options: { scope: current_user, @@ -37,6 +39,7 @@ RSpec.describe REST::CollectionSerializer do 'local' => true, 'sensitive' => true, 'discoverable' => false, + 'url' => account_collection_url(collection.account, collection), 'tag' => a_hash_including('name' => 'discovery'), 'created_at' => match_api_datetime_format, 'updated_at' => match_api_datetime_format, @@ -46,7 +49,11 @@ RSpec.describe REST::CollectionSerializer do end context 'when the collection is remote' do - let(:collection) { Fabricate(:remote_collection, description_html: '

remote

') } + let(:collection) { Fabricate(:remote_collection, description_html: '

remote

', url: 'https://example.com/c/1') } + + it 'includes the uri' do + expect(subject).to include('url' => 'https://example.com/c/1') + end it 'includes the html description' do expect(subject) From 0ef00be494d5db088d884c59280f686a3751732f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Apr 2026 07:46:13 -0400 Subject: [PATCH 10/37] Use bundler version 4.0.10 (#38671) --- Gemfile.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0eb4bc5ffd..e2b64a9f06 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -99,7 +99,7 @@ GEM ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.4.0) - aws-partitions (1.1236.0) + aws-partitions (1.1238.0) aws-sdk-core (3.244.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -156,7 +156,7 @@ GEM playwright-ruby-client (>= 1.16.0) case_transform (0.2) activesupport - cbor (0.5.10.1) + cbor (0.5.10.2) cgi (0.5.1) charlock_holmes (0.7.9) chewy (8.0.1) @@ -178,7 +178,7 @@ GEM bigdecimal rexml crass (1.0.6) - css_parser (1.21.1) + css_parser (2.0.0) addressable csv (3.3.5) database_cleaner-active_record (2.2.2) @@ -214,7 +214,7 @@ GEM dotenv (3.2.0) drb (2.2.3) dry-cli (1.4.1) - elastic-transport (8.4.1) + elastic-transport (8.5.1) faraday (< 3) multi_json elasticsearch (8.19.3) @@ -226,11 +226,11 @@ GEM elasticsearch-dsl (0.1.10) email_validator (2.2.4) activemodel - erb (6.0.2) + erb (6.0.3) erubi (1.13.1) et-orbi (1.4.0) tzinfo - excon (1.4.0) + excon (1.4.2) logger fabrication (3.0.0) faker (3.7.1) @@ -247,7 +247,7 @@ GEM net-http (~> 0.5) fast_blank (1.0.1) fastimage (2.4.1) - ffi (1.17.3) + ffi (1.17.4) ffi-compiler (1.3.2) ffi (>= 1.15.5) rake @@ -292,9 +292,9 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.72.0) + haml_lint (0.73.0) haml (>= 5.0) - parallel (~> 1.10) + parallel (>= 1.10) rainbow rubocop (>= 1.0) sysexits (~> 1.1) @@ -314,7 +314,7 @@ GEM http-cookie (~> 1.0) http-form_data (~> 2.2) llhttp-ffi (~> 0.5.0) - http-cookie (1.1.0) + http-cookie (1.1.4) domain_name (~> 0.5) http-form_data (2.3.0) http_accept_language (2.1.1) @@ -447,14 +447,14 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2026.0407) + mime-types-data (3.2026.0414) mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (6.0.3) + minitest (6.0.4) drb (~> 2.0) prism (~> 1.5) msgpack (1.8.0) - multi_json (1.19.1) + multi_json (1.20.1) mutex_m (0.3.0) net-http (0.6.0) uri @@ -589,8 +589,8 @@ GEM ostruct (0.6.3) ox (2.14.23) bigdecimal (>= 3.0) - parallel (1.27.0) - parser (3.3.10.2) + parallel (1.28.0) + parser (3.3.11.1) ast (~> 2.4.1) racc parslet (2.0.0) @@ -605,7 +605,7 @@ GEM mime-types (>= 3.0) pp (0.6.3) prettyprint - premailer (1.27.0) + premailer (1.29.0) addressable css_parser (>= 1.19.0) htmlentities (>= 4.0.0) @@ -691,7 +691,7 @@ GEM tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.1) + rake (13.4.1) rdf (3.3.4) bcp47_spec (~> 0.2) bigdecimal (~> 3.1, >= 3.1.5) @@ -712,7 +712,7 @@ GEM redis-client (>= 0.22.0) redis-client (0.28.0) connection_pool - regexp_parser (2.11.3) + regexp_parser (2.12.0) reline (0.6.3) io-console (~> 0.5) request_store (1.7.0) @@ -773,7 +773,7 @@ GEM rubocop-capybara (2.22.1) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) - rubocop-i18n (3.2.3) + rubocop-i18n (3.3.0) lint_roller (~> 1.1) rubocop (>= 1.72.1) rubocop-performance (1.26.1) @@ -905,7 +905,7 @@ GEM vite_rails (3.0.20) railties (>= 5.1, < 9) vite_ruby (~> 3.0, >= 3.2.2) - vite_ruby (3.9.3) + vite_ruby (3.10.2) dry-cli (>= 0.7, < 2) logger (~> 1.6) mutex_m @@ -1100,4 +1100,4 @@ RUBY VERSION ruby 4.0.2 BUNDLED WITH - 4.0.8 + 4.0.10 From 5a38246ee8dffad6789fb4a3a7723477d2d97b4c Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 16 Apr 2026 15:59:38 +0200 Subject: [PATCH 11/37] Update design of collection accounts editor (#38712) --- .../empty_state/empty_state.module.scss | 9 + .../empty_state/empty_state.stories.tsx | 20 +- .../mastodon/components/empty_state/index.tsx | 18 +- .../components/empty_message.module.scss | 8 - .../components/empty_message.tsx | 8 +- .../features/collections/editor/accounts.tsx | 347 ++++++++---------- .../features/collections/editor/details.tsx | 4 +- .../features/collections/editor/index.tsx | 4 +- .../collections/editor/styles.module.scss | 78 ++-- .../collections/editor/wizard_step_header.tsx | 23 -- .../collections/editor/wizard_step_title.tsx | 21 ++ .../mastodon/hooks/useSearchAccounts.ts | 7 +- app/javascript/mastodon/locales/en.json | 11 +- 13 files changed, 240 insertions(+), 318 deletions(-) delete mode 100644 app/javascript/mastodon/features/account_featured/components/empty_message.module.scss delete mode 100644 app/javascript/mastodon/features/collections/editor/wizard_step_header.tsx create mode 100644 app/javascript/mastodon/features/collections/editor/wizard_step_title.tsx diff --git a/app/javascript/mastodon/components/empty_state/empty_state.module.scss b/app/javascript/mastodon/components/empty_state/empty_state.module.scss index e9602f2e41..b58c565ac5 100644 --- a/app/javascript/mastodon/components/empty_state/empty_state.module.scss +++ b/app/javascript/mastodon/components/empty_state/empty_state.module.scss @@ -35,3 +35,12 @@ text-wrap: pretty; } } + +[data-color-scheme='dark'] .defaultImage { + --color-skin-1: #3a3a50; + --color-skin-2: #67678e; + --color-skin-3: #44445f; + --color-outline: #21212c; + --color-shadow: #181820; + --color-highlight: #b2b1c8; +} diff --git a/app/javascript/mastodon/components/empty_state/empty_state.stories.tsx b/app/javascript/mastodon/components/empty_state/empty_state.stories.tsx index 8515a6ea1a..83fce03468 100644 --- a/app/javascript/mastodon/components/empty_state/empty_state.stories.tsx +++ b/app/javascript/mastodon/components/empty_state/empty_state.stories.tsx @@ -29,12 +29,6 @@ export const Default: Story = { }, }; -export const WithoutMessage: Story = { - args: { - message: undefined, - }, -}; - export const WithAction: Story = { args: { ...Default.args, @@ -42,3 +36,17 @@ export const WithAction: Story = { children: , }, }; + +export const WithoutImage: Story = { + args: { + ...Default.args, + image: null, + }, +}; + +export const WithoutMessage: Story = { + args: { + ...Default.args, + message: undefined, + }, +}; diff --git a/app/javascript/mastodon/components/empty_state/index.tsx b/app/javascript/mastodon/components/empty_state/index.tsx index 59210bbc87..0ef0d67995 100644 --- a/app/javascript/mastodon/components/empty_state/index.tsx +++ b/app/javascript/mastodon/components/empty_state/index.tsx @@ -1,31 +1,39 @@ import { FormattedMessage } from 'react-intl'; +import ElephantImage from '@/images/elephant_ui.svg?react'; + import classes from './empty_state.module.scss'; +const images = { + default: , +}; + /** * Simple empty state component with a neutral default title and customisable message. * - * Action buttons can be passed as `children` + * Action buttons can be passed as `children`. */ export const EmptyState: React.FC<{ - image?: React.ReactNode; + image?: keyof typeof images | React.ReactElement | null; title?: React.ReactNode; message?: React.ReactNode; children?: React.ReactNode; }> = ({ - image, + image = 'default', title = ( ), message, children, }) => { + const imageToRender = typeof image === 'string' ? images[image] : image; + return (
- {(title || message || image) && ( + {(title || message || imageToRender) && (
- {image} + {imageToRender} {!!title &&

{title}

} {!!message &&

{message}

}
diff --git a/app/javascript/mastodon/features/account_featured/components/empty_message.module.scss b/app/javascript/mastodon/features/account_featured/components/empty_message.module.scss deleted file mode 100644 index 25dcf19433..0000000000 --- a/app/javascript/mastodon/features/account_featured/components/empty_message.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -[data-color-scheme='dark'] .image { - --color-skin-1: #3a3a50; - --color-skin-2: #67678e; - --color-skin-3: #44445f; - --color-outline: #21212c; - --color-shadow: #181820; - --color-highlight: #b2b1c8; -} diff --git a/app/javascript/mastodon/features/account_featured/components/empty_message.tsx b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx index 35b67cd641..e1de024a69 100644 --- a/app/javascript/mastodon/features/account_featured/components/empty_message.tsx +++ b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx @@ -5,7 +5,6 @@ import { FormattedMessage } from 'react-intl'; import { useParams } from 'react-router'; import { Link } from 'react-router-dom'; -import ElephantImage from '@/images/elephant_ui.svg?react'; import { openModal } from '@/mastodon/actions/modal'; import { Button } from '@/mastodon/components/button'; import { EmptyState } from '@/mastodon/components/empty_state'; @@ -14,8 +13,6 @@ import { areCollectionsEnabled } from '@/mastodon/features/collections/utils'; import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId'; import { useAppDispatch } from '@/mastodon/store'; -import classes from './empty_message.module.scss'; - interface EmptyMessageProps { suspended: boolean; hidden: boolean; @@ -54,14 +51,11 @@ export const EmptyMessage: React.FC = ({ const hasCollections = areCollectionsEnabled(); - const image = ; - if (me === accountId) { if (hasCollections) { // Return only here to insert the "Create a collection" button as the action for the empty state. return ( = ({ } } - return ; + return ; }; diff --git a/app/javascript/mastodon/features/collections/editor/accounts.tsx b/app/javascript/mastodon/features/collections/editor/accounts.tsx index 654f6265e6..0abca399d5 100644 --- a/app/javascript/mastodon/features/collections/editor/accounts.tsx +++ b/app/javascript/mastodon/features/collections/editor/accounts.tsx @@ -4,22 +4,16 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; -import CancelIcon from '@/material-icons/400-24px/cancel.svg?react'; -import CheckIcon from '@/material-icons/400-24px/check.svg?react'; -import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import { showAlertForError } from 'mastodon/actions/alerts'; import { openModal } from 'mastodon/actions/modal'; import { apiFollowAccount } from 'mastodon/api/accounts'; import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; import { Account } from 'mastodon/components/account'; import { Avatar } from 'mastodon/components/avatar'; -import { Badge } from 'mastodon/components/badge'; import { Button } from 'mastodon/components/button'; import { DisplayName } from 'mastodon/components/display_name'; import { EmptyState } from 'mastodon/components/empty_state'; -import { FormStack, Combobox } from 'mastodon/components/form_fields'; -import { Icon } from 'mastodon/components/icon'; -import { IconButton } from 'mastodon/components/icon_button'; +import { FormStack, ComboboxField } from 'mastodon/components/form_fields'; import { Article, ItemList, @@ -37,75 +31,35 @@ import { import { store, useAppDispatch, useAppSelector } from 'mastodon/store'; import classes from './styles.module.scss'; -import { WizardStepHeader } from './wizard_step_header'; +import { WizardStepTitle } from './wizard_step_title'; -const MAX_ACCOUNT_COUNT = 25; - -function isOlderThanAWeek(date?: string): boolean { - if (!date) return false; - - const targetDate = new Date(date); - const sevenDaysAgo = new Date(); - sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); - return targetDate < sevenDaysAgo; -} +const MAX_ACCOUNT_COUNT = 3; const AddedAccountItem: React.FC<{ accountId: string; onRemove: (id: string) => void; }> = ({ accountId, onRemove }) => { - const intl = useIntl(); - const account = useAccount(accountId); - const handleRemoveAccount = useCallback(() => { onRemove(accountId); }, [accountId, onRemove]); - const lastStatusAt = account?.last_status_at; - - const lastPostHint = useMemo( - () => - (!lastStatusAt || isOlderThanAWeek(lastStatusAt)) && ( - - } - icon={} - className={classes.accountBadge} - /> - ), - [lastStatusAt], - ); - return ( - - + + ); }; interface SuggestionItem { id: string; - isSelected: boolean; } -const SuggestedAccountItem: React.FC = ({ id, isSelected }) => { +const SuggestedAccountItem: React.FC = ({ id }) => { const account = useAccount(id); if (!account) return null; @@ -114,23 +68,15 @@ const SuggestedAccountItem: React.FC = ({ id, isSelected }) => { <> - {isSelected && ( - - )} ); }; const renderAccountItem = (item: SuggestionItem) => ( - + ); const getItemId = (item: SuggestionItem) => item.id; -const getIsItemSelected = (item: SuggestionItem) => item.isSelected; export const CollectionAccounts: React.FC<{ collection?: ApiCollectionJSON | null; @@ -139,9 +85,8 @@ export const CollectionAccounts: React.FC<{ const dispatch = useAppDispatch(); const history = useHistory(); - const { id, items } = collection ?? {}; + const { id, items: collectionItems } = collection ?? {}; const isEditMode = !!id; - const collectionItems = items; const addedAccountIds = useAppSelector( (state) => state.collections.editor.accountIds, @@ -157,22 +102,25 @@ export const CollectionAccounts: React.FC<{ const [searchValue, setSearchValue] = useState(''); + const hasAccounts = accountIds.length > 0; const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT; const { accountIds: suggestedAccountIds, isLoading: isLoadingSuggestions, searchAccounts, + resetAccounts, } = useSearchAccounts({ withRelationships: true, filterResults: (account) => + !accountIds.includes(account.id) && // Only suggest accounts who allow being featured/recommended account.feature_approval.current_user === 'automatic', }); const suggestedItems = suggestedAccountIds.map((id) => ({ id, - isSelected: accountIds.includes(id), + isDisabled: accountIds.includes(id), })); const handleSearchValueChange = useCallback( @@ -242,12 +190,12 @@ export const CollectionAccounts: React.FC<{ ); const addAccountItem = useCallback( - (accountId: string) => { - confirmFollowStatus(accountId, () => { + (item: SuggestionItem) => { + confirmFollowStatus(item.id, () => { dispatch( updateCollectionEditorField({ field: 'accountIds', - value: [...accountIds, accountId], + value: [...accountIds, item.id], }), ); }); @@ -255,17 +203,6 @@ export const CollectionAccounts: React.FC<{ [accountIds, confirmFollowStatus, dispatch], ); - const toggleAccountItem = useCallback( - (item: SuggestionItem) => { - if (accountIds.includes(item.id)) { - removeAccountItem(item.id); - } else { - addAccountItem(item.id); - } - }, - [accountIds, addAccountItem, removeAccountItem], - ); - const instantRemoveAccountItem = useCallback( (accountId: string) => { const itemId = collectionItems?.find( @@ -289,23 +226,16 @@ export const CollectionAccounts: React.FC<{ ); const instantAddAccountItem = useCallback( - (collectionId: string, accountId: string) => { - confirmFollowStatus(accountId, () => { - void dispatch(addCollectionItem({ collectionId, accountId })); + (item: SuggestionItem) => { + confirmFollowStatus(item.id, () => { + if (id) { + void dispatch( + addCollectionItem({ collectionId: id, accountId: item.id }), + ); + } }); }, - [confirmFollowStatus, dispatch], - ); - - const instantToggleAccountItem = useCallback( - (item: SuggestionItem) => { - if (accountIds.includes(item.id)) { - instantRemoveAccountItem(item.id); - } else if (id) { - instantAddAccountItem(id, item.id); - } - }, - [accountIds, id, instantAddAccountItem, instantRemoveAccountItem], + [confirmFollowStatus, dispatch, id], ); const handleRemoveAccountItem = useCallback( @@ -319,6 +249,20 @@ export const CollectionAccounts: React.FC<{ [isEditMode, instantRemoveAccountItem, removeAccountItem], ); + const handleSelectItem = useCallback( + (item: SuggestionItem) => { + if (isEditMode) { + instantAddAccountItem(item); + } else { + addAccountItem(item); + } + + setSearchValue(''); + resetAccounts(); + }, + [addAccountItem, instantAddAccountItem, isEditMode, resetAccounts], + ); + const handleSubmit = useCallback( (e: React.FormEvent) => { e.preventDefault(); @@ -333,117 +277,114 @@ export const CollectionAccounts: React.FC<{ ); const inputId = useId(); - const inputLabel = intl.formatMessage({ - id: 'collections.search_accounts_label', - defaultMessage: 'Search for accounts to add…', - }); + const AccountsHeadingElement = id ? 'h2' : 'h3'; return (
- {!id && ( - - } - description={ - - } - /> - )} - - - {hasMaxAccounts && ( - - )} - - - - } - message={ - - } - /> - } - > - {accountIds.map((accountId, index) => ( -
- -
- ))} -
-
-
- {!isEditMode && ( -
-
- - {(text) =>
{text}
} -
- -
+ } + /> + )} + +
+ +
+ {hasAccounts && ( + + + + )} + + + + } + message={ + + } + /> + } + > + {accountIds.map((accountId, index) => ( +
+ +
+ ))} +
+
+
+ + {!isEditMode && hasAccounts && ( +
+
)} diff --git a/app/javascript/mastodon/features/collections/editor/details.tsx b/app/javascript/mastodon/features/collections/editor/details.tsx index 04a6ad1428..f4e561e6e3 100644 --- a/app/javascript/mastodon/features/collections/editor/details.tsx +++ b/app/javascript/mastodon/features/collections/editor/details.tsx @@ -36,7 +36,7 @@ import { import { useAppDispatch, useAppSelector } from 'mastodon/store'; import classes from './styles.module.scss'; -import { WizardStepHeader } from './wizard_step_header'; +import { WizardStepTitle } from './wizard_step_title'; export const CollectionDetails: React.FC = () => { const dispatch = useAppDispatch(); @@ -152,7 +152,7 @@ export const CollectionDetails: React.FC = () => {
{!id && ( - = ({ multiColumn }) => { const intl = useIntl(); const dispatch = useAppDispatch(); - const { id } = useParams<{ id?: string }>(); + const { id = null } = useParams<{ id?: string }>(); const { path } = useRouteMatch(); const collection = useAppSelector((state) => id ? state.collections.collections[id] : undefined, diff --git a/app/javascript/mastodon/features/collections/editor/styles.module.scss b/app/javascript/mastodon/features/collections/editor/styles.module.scss index 1991aa4211..577a6d7d12 100644 --- a/app/javascript/mastodon/features/collections/editor/styles.module.scss +++ b/app/javascript/mastodon/features/collections/editor/styles.module.scss @@ -1,10 +1,17 @@ +.header { + display: flex; + flex-direction: column; + gap: 12px; +} + .step { font-size: 13px; color: var(--color-text-secondary); } .title { - font-size: 22px; + font-size: 17px; + font-weight: 500; line-height: 1.2; margin-top: 4px; } @@ -14,79 +21,40 @@ margin-top: 8px; } -/* Make form stretch full height of the column */ +.listHeading { + margin-bottom: 8px; + font-size: 15px; + font-weight: 500; + line-height: 1.2; +} + +/* Ensure sticky footer isn't covered by mobile bottom nav */ .form { --bottom-spacing: 0; - box-sizing: border-box; - display: flex; - flex-direction: column; - min-height: 100%; - padding-bottom: var(--bottom-spacing); + padding-bottom: 0; @media (width < 760px) { --bottom-spacing: var(--mobile-bottom-nav-height); } } -.selectedSuggestionIcon { - box-sizing: border-box; - width: 18px; - height: 18px; - margin-left: auto; - padding: 2px; - border-radius: 100%; - color: var(--color-text-on-brand-base); - background: var(--color-bg-brand-base); - - [data-highlighted='true'] & { - color: var(--color-bg-brand-base); - background: var(--color-text-on-brand-base); - } -} - -.formFieldStack { - flex-grow: 1; -} - -.scrollableWrapper, -.scrollableInner { - margin-inline: -8px; -} - -.submitDisabledCallout { - align-self: center; -} - .stickyFooter { position: sticky; bottom: var(--bottom-spacing); + margin-top: -16px; padding: 16px; background-image: linear-gradient( to bottom, transparent, - var(--color-bg-primary) 32px + var(--color-bg-primary) 24px ); } -.itemCountReadout { - text-align: center; +.formFieldStack { + gap: 16px; } -.actionWrapper { - display: flex; - flex-direction: column; - width: min-content; - min-width: 240px; - margin-inline: auto; - gap: 8px; -} - -.accountBadge { - margin-inline-start: 56px; - - @container (width < 360px) { - margin-top: 4px; - margin-inline-start: 46px; - } +.scrollableWrapper { + margin-inline: -16px; } diff --git a/app/javascript/mastodon/features/collections/editor/wizard_step_header.tsx b/app/javascript/mastodon/features/collections/editor/wizard_step_header.tsx deleted file mode 100644 index 3bc9a4d7ef..0000000000 --- a/app/javascript/mastodon/features/collections/editor/wizard_step_header.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FormattedMessage } from 'react-intl'; - -import classes from './styles.module.scss'; - -export const WizardStepHeader: React.FC<{ - step: number; - title: React.ReactElement; - description?: React.ReactElement; -}> = ({ step, title, description }) => { - return ( -
- - {(content) =>

{content}

} -
-

{title}

- {!!description &&

{description}

} -
- ); -}; diff --git a/app/javascript/mastodon/features/collections/editor/wizard_step_title.tsx b/app/javascript/mastodon/features/collections/editor/wizard_step_title.tsx new file mode 100644 index 0000000000..4d328521e7 --- /dev/null +++ b/app/javascript/mastodon/features/collections/editor/wizard_step_title.tsx @@ -0,0 +1,21 @@ +import { FormattedMessage } from 'react-intl'; + +import classes from './styles.module.scss'; + +export const WizardStepTitle: React.FC<{ + step: number; + title: React.ReactElement; +}> = ({ step, title }) => { + return ( +
+

+ +

+

{title}

+
+ ); +}; diff --git a/app/javascript/mastodon/hooks/useSearchAccounts.ts b/app/javascript/mastodon/hooks/useSearchAccounts.ts index 1fc71f54ef..358632296f 100644 --- a/app/javascript/mastodon/hooks/useSearchAccounts.ts +++ b/app/javascript/mastodon/hooks/useSearchAccounts.ts @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { useDebouncedCallback } from 'use-debounce'; @@ -73,8 +73,13 @@ export function useSearchAccounts({ { leading: true, trailing: true }, ); + const resetAccounts = useCallback(() => { + setAccountIds([]); + }, []); + return { searchAccounts, + resetAccounts, accountIds, isLoading: loadingState === 'loading', isError: loadingState === 'error', diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 3e2ce2c696..93f395d560 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Check out this cool collection: {link}", "collection.share_template_own": "Check out my new collection: {link}", "collections.account_count": "{count, plural, one {# account} other {# accounts}}", - "collections.accounts.empty_description": "Add up to {count} accounts you follow", + "collections.accounts.empty_description": "Add up to {count} accounts", + "collections.accounts.empty_editor_title": "No one is in this collection yet", "collections.accounts.empty_title": "This collection is empty", "collections.block_collection_owner": "Block account", "collections.by_account": "by {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Continue", "collections.copy_link": "Copy link", "collections.copy_link_confirmation": "Copied collection link to clipboard", - "collections.create.accounts_subtitle": "Only accounts you follow who have opted into discovery can be added.", "collections.create.accounts_title": "Who will you feature in this collection?", "collections.create.basic_details_title": "Basic details", "collections.create.steps": "Step {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "There was an error when trying to load your collections.", "collections.hidden_accounts_description": "You’ve blocked or muted {count, plural, one {this user} other {these users}}", "collections.hidden_accounts_link": "{count, plural, one {# hidden account} other {# hidden accounts}}", - "collections.hints.accounts_counter": "{count} / {max} accounts", + "collections.hints.accounts_counter": "{count}/{max} accounts", "collections.last_updated_at": "Last updated: {date}", "collections.manage_accounts": "Manage accounts", "collections.mark_as_sensitive": "Mark as sensitive", @@ -401,13 +401,12 @@ "collections.name_length_hint": "40 characters limit", "collections.new_collection": "New collection", "collections.no_collections_yet": "No collections yet.", - "collections.old_last_post_note": "Last posted over a week ago", - "collections.remove_account": "Remove this account", + "collections.remove_account": "Remove", "collections.report_collection": "Report this collection", "collections.revoke_collection_inclusion": "Remove myself from this collection", "collections.revoke_inclusion.confirmation": "You've been removed from \"{collection}\"", "collections.revoke_inclusion.error": "There was an error, please try again later.", - "collections.search_accounts_label": "Search for accounts to add…", + "collections.search_accounts_label": "Search for an account to add", "collections.search_accounts_max_reached": "You have added the maximum number of accounts", "collections.sensitive": "Sensitive", "collections.share_short": "Share", From e711f9d492b2a08086e81e849912e87d48cde0e1 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 16 Apr 2026 17:44:50 +0200 Subject: [PATCH 12/37] Federate featured item creation date (#38713) --- app/serializers/activitypub/featured_item_serializer.rb | 6 +++++- app/serializers/rest/collection_item_serializer.rb | 2 +- app/services/activitypub/process_featured_item_service.rb | 4 +++- .../activitypub/featured_collection_serializer_spec.rb | 2 ++ .../activitypub/featured_item_serializer_spec.rb | 3 ++- spec/serializers/rest/collection_item_serializer_spec.rb | 3 ++- .../activitypub/process_featured_item_service_spec.rb | 2 ++ 7 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/serializers/activitypub/featured_item_serializer.rb b/app/serializers/activitypub/featured_item_serializer.rb index 56c0b4390f..529bfd797f 100644 --- a/app/serializers/activitypub/featured_item_serializer.rb +++ b/app/serializers/activitypub/featured_item_serializer.rb @@ -4,7 +4,7 @@ class ActivityPub::FeaturedItemSerializer < ActivityPub::Serializer include RoutingHelper attributes :id, :type, :featured_object, :featured_object_type, - :feature_authorization + :feature_authorization, :published def id ActivityPub::TagManager.instance.uri_for(object) @@ -29,4 +29,8 @@ class ActivityPub::FeaturedItemSerializer < ActivityPub::Serializer object.approval_uri end end + + def published + object.created_at.iso8601 + end end diff --git a/app/serializers/rest/collection_item_serializer.rb b/app/serializers/rest/collection_item_serializer.rb index 8628960403..49cd8c7a0e 100644 --- a/app/serializers/rest/collection_item_serializer.rb +++ b/app/serializers/rest/collection_item_serializer.rb @@ -3,7 +3,7 @@ class REST::CollectionItemSerializer < ActiveModel::Serializer delegate :accepted?, to: :object - attributes :id, :state + attributes :id, :state, :created_at attribute :account_id, if: :accepted? diff --git a/app/services/activitypub/process_featured_item_service.rb b/app/services/activitypub/process_featured_item_service.rb index 718a7d1f23..a4323b41c6 100644 --- a/app/services/activitypub/process_featured_item_service.rb +++ b/app/services/activitypub/process_featured_item_service.rb @@ -43,7 +43,9 @@ class ActivityPub::ProcessFeaturedItemService end def new_item - @collection.collection_items.new + @collection.collection_items.new( + created_at: @item_json['published'] + ) end def verify_authorization! diff --git a/spec/serializers/activitypub/featured_collection_serializer_spec.rb b/spec/serializers/activitypub/featured_collection_serializer_spec.rb index 64b2fdebbe..ad62e7f8ad 100644 --- a/spec/serializers/activitypub/featured_collection_serializer_spec.rb +++ b/spec/serializers/activitypub/featured_collection_serializer_spec.rb @@ -39,6 +39,7 @@ RSpec.describe ActivityPub::FeaturedCollectionSerializer do 'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_items.first.account), 'featuredObjectType' => 'Person', 'featureAuthorization' => ap_account_feature_authorization_url(collection_items.first.account_id, collection_items.first), + 'published' => match_api_datetime_format, }, { 'id' => ActivityPub::TagManager.instance.uri_for(collection_items.last), @@ -46,6 +47,7 @@ RSpec.describe ActivityPub::FeaturedCollectionSerializer do 'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_items.last.account), 'featuredObjectType' => 'Person', 'featureAuthorization' => ap_account_feature_authorization_url(collection_items.last.account_id, collection_items.last), + 'published' => match_api_datetime_format, }, ], 'published' => match_api_datetime_format, diff --git a/spec/serializers/activitypub/featured_item_serializer_spec.rb b/spec/serializers/activitypub/featured_item_serializer_spec.rb index 7aca086192..18afdd10fe 100644 --- a/spec/serializers/activitypub/featured_item_serializer_spec.rb +++ b/spec/serializers/activitypub/featured_item_serializer_spec.rb @@ -7,7 +7,7 @@ RSpec.describe ActivityPub::FeaturedItemSerializer do subject { serialized_record_json(collection_item, described_class, adapter: ActivityPub::Adapter) } - let(:collection_item) { Fabricate(:collection_item) } + let(:collection_item) { Fabricate(:collection_item, created_at: Time.utc(2026, 4, 16, 1)) } context 'when a local account is featured' do it 'serializes to the expected structure' do @@ -17,6 +17,7 @@ RSpec.describe ActivityPub::FeaturedItemSerializer do 'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_item.account), 'featuredObjectType' => 'Person', 'featureAuthorization' => ap_account_feature_authorization_url(collection_item.account_id, collection_item), + 'published' => '2026-04-16T01:00:00Z', }) end end diff --git a/spec/serializers/rest/collection_item_serializer_spec.rb b/spec/serializers/rest/collection_item_serializer_spec.rb index 348107fb91..a16af9bce0 100644 --- a/spec/serializers/rest/collection_item_serializer_spec.rb +++ b/spec/serializers/rest/collection_item_serializer_spec.rb @@ -19,7 +19,8 @@ RSpec.describe REST::CollectionItemSerializer do .to include( 'id' => '2342', 'account_id' => collection_item.account_id.to_s, - 'state' => 'accepted' + 'state' => 'accepted', + 'created_at' => match_api_datetime_format ) end end diff --git a/spec/services/activitypub/process_featured_item_service_spec.rb b/spec/services/activitypub/process_featured_item_service_spec.rb index bfcea2a5da..14147b3784 100644 --- a/spec/services/activitypub/process_featured_item_service_spec.rb +++ b/spec/services/activitypub/process_featured_item_service_spec.rb @@ -19,6 +19,7 @@ RSpec.describe ActivityPub::ProcessFeaturedItemService do 'featuredObject' => featured_object_uri, 'featuredObjectType' => 'Person', 'featureAuthorization' => feature_authorization_uri, + 'published' => '2026-04-16T01:00:00Z', } end let(:stubbed_service) do @@ -56,6 +57,7 @@ RSpec.describe ActivityPub::ProcessFeaturedItemService do new_item = collection.collection_items.last expect(new_item.object_uri).to eq 'https://example.com/actor/1' expect(new_item.approval_uri).to be_nil + expect(new_item.created_at).to eq Time.utc(2026, 4, 16, 1) expect(new_item.position).to eq 3 end end From 0e4ee62dfc05803b1e98cb67e2c1db9afb5bddab Mon Sep 17 00:00:00 2001 From: Shlee Date: Fri, 17 Apr 2026 01:29:48 +0930 Subject: [PATCH 13/37] Fix typo in block_spec.rb (#38714) --- spec/models/block_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/block_spec.rb b/spec/models/block_spec.rb index 62d7e40e28..e5dfc909b7 100644 --- a/spec/models/block_spec.rb +++ b/spec/models/block_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Block do end context 'when URI is blank' do - subject { Fabricate.build :follow, uri: nil } + subject { Fabricate.build :block, uri: nil } it 'populates the value' do expect { subject.save } From fc1ba93cdc01538c9862810273a82e112a66ec1c Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 16 Apr 2026 18:00:13 +0200 Subject: [PATCH 14/37] Refactor featured collections URL code (#38709) --- app/lib/activitypub/tag_manager.rb | 2 ++ .../activitypub/featured_collection_serializer.rb | 5 +---- app/serializers/rest/collection_serializer.rb | 8 +------- spec/serializers/rest/collection_serializer_spec.rb | 4 +--- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 9036811217..37c45d68f1 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -31,6 +31,8 @@ class ActivityPub::TagManager short_account_status_url(target.account, target) when :flag target.uri + when :featured_collection + account_collection_url(target.account, target) end end diff --git a/app/serializers/activitypub/featured_collection_serializer.rb b/app/serializers/activitypub/featured_collection_serializer.rb index 60b4d8459c..b83e0bf9ea 100644 --- a/app/serializers/activitypub/featured_collection_serializer.rb +++ b/app/serializers/activitypub/featured_collection_serializer.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer - # include Rails.application.routes.url_helpers - include RoutingHelper - attributes :id, :type, :total_items, :name, :attributed_to, :url, :sensitive, :discoverable, :published, :updated @@ -35,7 +32,7 @@ class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer end def url - account_collection_url(object.account, object) + ActivityPub::TagManager.instance.url_for(object) end def total_items diff --git a/app/serializers/rest/collection_serializer.rb b/app/serializers/rest/collection_serializer.rb index 98dbabb949..c9696b9aef 100644 --- a/app/serializers/rest/collection_serializer.rb +++ b/app/serializers/rest/collection_serializer.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class REST::CollectionSerializer < ActiveModel::Serializer - include RoutingHelper - attributes :id, :uri, :name, :description, :language, :account_id, :local, :sensitive, :discoverable, :url, :item_count, :created_at, :updated_at @@ -20,7 +18,7 @@ class REST::CollectionSerializer < ActiveModel::Serializer end def url - object.local? ? account_collection_url(object.account, object) : object.url + ActivityPub::TagManager.instance.url_for(object) end def description @@ -37,8 +35,4 @@ class REST::CollectionSerializer < ActiveModel::Serializer def account_id object.account_id.to_s end - - def tag - object.tag - end end diff --git a/spec/serializers/rest/collection_serializer_spec.rb b/spec/serializers/rest/collection_serializer_spec.rb index 0ff98c4cb1..dbb9803753 100644 --- a/spec/serializers/rest/collection_serializer_spec.rb +++ b/spec/serializers/rest/collection_serializer_spec.rb @@ -3,8 +3,6 @@ require 'rails_helper' RSpec.describe REST::CollectionSerializer do - include RoutingHelper - subject do serialized_record_json(collection, described_class, options: { scope: current_user, @@ -39,7 +37,7 @@ RSpec.describe REST::CollectionSerializer do 'local' => true, 'sensitive' => true, 'discoverable' => false, - 'url' => account_collection_url(collection.account, collection), + 'url' => ActivityPub::TagManager.instance.url_for(collection), 'tag' => a_hash_including('name' => 'discovery'), 'created_at' => match_api_datetime_format, 'updated_at' => match_api_datetime_format, From 0e6180a5afdecca880ec94077b7086dcbcaa29ab Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 16 Apr 2026 20:05:04 +0200 Subject: [PATCH 15/37] Fix `Bundle` being used with incorrect prop types by using type-dependent `key` (#38721) --- app/javascript/mastodon/components/media_attachments.jsx | 6 +++--- app/javascript/mastodon/components/status.jsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/components/media_attachments.jsx b/app/javascript/mastodon/components/media_attachments.jsx index 63fe3e67f9..a8d4b654a4 100644 --- a/app/javascript/mastodon/components/media_attachments.jsx +++ b/app/javascript/mastodon/components/media_attachments.jsx @@ -65,7 +65,7 @@ export default class MediaAttachments extends ImmutablePureComponent { const description = audio.getIn(['translation', 'description']) || audio.get('description'); return ( - + {Component => ( + {Component => ( + {Component => ( 1) { media = ( - + {Component => ( + {Component => ( + {Component => ( Date: Thu, 16 Apr 2026 20:05:36 +0200 Subject: [PATCH 16/37] Implement new Collection inclusion rules in Collection accounts editor (#38719) --- .../features/collections/editor/accounts.tsx | 104 ++++++------------ .../mastodon/features/lists/members.tsx | 5 +- .../follow_to_collection.tsx | 43 -------- .../components/confirmation_modals/index.ts | 1 - .../features/ui/components/modal_root.jsx | 2 - .../mastodon/hooks/useSearchAccounts.ts | 10 +- app/javascript/mastodon/locales/en.json | 3 - 7 files changed, 41 insertions(+), 127 deletions(-) delete mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_collection.tsx diff --git a/app/javascript/mastodon/features/collections/editor/accounts.tsx b/app/javascript/mastodon/features/collections/editor/accounts.tsx index 0abca399d5..3fc26f4cfd 100644 --- a/app/javascript/mastodon/features/collections/editor/accounts.tsx +++ b/app/javascript/mastodon/features/collections/editor/accounts.tsx @@ -4,11 +4,8 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; -import { showAlertForError } from 'mastodon/actions/alerts'; -import { openModal } from 'mastodon/actions/modal'; -import { apiFollowAccount } from 'mastodon/api/accounts'; import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; -import { Account } from 'mastodon/components/account'; +import { AccountListItem } from 'mastodon/components/account_list_item'; import { Avatar } from 'mastodon/components/avatar'; import { Button } from 'mastodon/components/button'; import { DisplayName } from 'mastodon/components/display_name'; @@ -21,19 +18,18 @@ import { } from 'mastodon/components/scrollable_list/components'; import { useAccount } from 'mastodon/hooks/useAccount'; import { useSearchAccounts } from 'mastodon/hooks/useSearchAccounts'; -import { me } from 'mastodon/initial_state'; import { addCollectionItem, getCollectionItemIds, removeCollectionItem, updateCollectionEditorField, } from 'mastodon/reducers/slices/collections'; -import { store, useAppDispatch, useAppSelector } from 'mastodon/store'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; import classes from './styles.module.scss'; import { WizardStepTitle } from './wizard_step_title'; -const MAX_ACCOUNT_COUNT = 3; +const MAX_ACCOUNT_COUNT = 25; const AddedAccountItem: React.FC<{ accountId: string; @@ -43,20 +39,24 @@ const AddedAccountItem: React.FC<{ onRemove(accountId); }, [accountId, onRemove]); - return ( - + const renderButton = useCallback( + () => ( - + ), + [handleRemoveAccount], ); + + return ; }; interface SuggestionItem { id: string; + isDisabled?: boolean; } const SuggestedAccountItem: React.FC = ({ id }) => { @@ -77,6 +77,7 @@ const renderAccountItem = (item: SuggestionItem) => ( ); const getItemId = (item: SuggestionItem) => item.id; +const getIsItemDisabled = (item: SuggestionItem) => item.isDisabled ?? false; export const CollectionAccounts: React.FC<{ collection?: ApiCollectionJSON | null; @@ -106,21 +107,22 @@ export const CollectionAccounts: React.FC<{ const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT; const { - accountIds: suggestedAccountIds, + accounts: suggestedAccounts, isLoading: isLoadingSuggestions, searchAccounts, resetAccounts, } = useSearchAccounts({ withRelationships: true, - filterResults: (account) => - !accountIds.includes(account.id) && - // Only suggest accounts who allow being featured/recommended - account.feature_approval.current_user === 'automatic', + // Don't suggest accounts that were already added + filterResults: (account) => !accountIds.includes(account.id), }); - const suggestedItems = suggestedAccountIds.map((id) => ({ + const suggestedItems = suggestedAccounts.map(({ id, feature_approval }) => ({ id, - isDisabled: accountIds.includes(id), + // Disable accounts who can't be added to a collection + isDisabled: !['automatic', 'manual'].includes( + feature_approval.current_user, + ), })); const handleSearchValueChange = useCallback( @@ -140,43 +142,6 @@ export const CollectionAccounts: React.FC<{ [], ); - const relationships = useAppSelector((state) => state.relationships); - - const confirmFollowStatus = useCallback( - (accountId: string, onFollowing: () => void) => { - const relationship = relationships.get(accountId); - - if (!relationship) { - return; - } - - if ( - accountId === me || - relationship.following || - relationship.requested - ) { - onFollowing(); - } else { - dispatch( - openModal({ - modalType: 'CONFIRM_FOLLOW_TO_COLLECTION', - modalProps: { - accountId, - onConfirm: () => { - apiFollowAccount(accountId) - .then(onFollowing) - .catch((err: unknown) => { - store.dispatch(showAlertForError(err)); - }); - }, - }, - }), - ); - } - }, - [dispatch, relationships], - ); - const removeAccountItem = useCallback( (accountId: string) => { dispatch( @@ -191,16 +156,14 @@ export const CollectionAccounts: React.FC<{ const addAccountItem = useCallback( (item: SuggestionItem) => { - confirmFollowStatus(item.id, () => { - dispatch( - updateCollectionEditorField({ - field: 'accountIds', - value: [...accountIds, item.id], - }), - ); - }); + dispatch( + updateCollectionEditorField({ + field: 'accountIds', + value: [...accountIds, item.id], + }), + ); }, - [accountIds, confirmFollowStatus, dispatch], + [accountIds, dispatch], ); const instantRemoveAccountItem = useCallback( @@ -227,15 +190,13 @@ export const CollectionAccounts: React.FC<{ const instantAddAccountItem = useCallback( (item: SuggestionItem) => { - confirmFollowStatus(item.id, () => { - if (id) { - void dispatch( - addCollectionItem({ collectionId: id, accountId: item.id }), - ); - } - }); + if (id) { + void dispatch( + addCollectionItem({ collectionId: id, accountId: item.id }), + ); + } }, - [confirmFollowStatus, dispatch, id], + [dispatch, id], ); const handleRemoveAccountItem = useCallback( @@ -307,6 +268,7 @@ export const CollectionAccounts: React.FC<{ isLoading={isLoadingSuggestions} items={suggestedItems} getItemId={getItemId} + getIsItemDisabled={getIsItemDisabled} renderItem={renderAccountItem} onSelectItem={handleSelectItem} status={ diff --git a/app/javascript/mastodon/features/lists/members.tsx b/app/javascript/mastodon/features/lists/members.tsx index dd4cf3171d..c4bd99d9b4 100644 --- a/app/javascript/mastodon/features/lists/members.tsx +++ b/app/javascript/mastodon/features/lists/members.tsx @@ -164,7 +164,7 @@ const ListMembers: React.FC<{ const [mode, setMode] = useState('remove'); const { - accountIds: searchAccountIds, + accounts: accountsFromSearch, isLoading: loadingSearchResults, searchAccounts: handleSearch, } = useSearchAccounts({ @@ -177,6 +177,7 @@ const ListMembers: React.FC<{ } }, }); + const accountIdsFromSearch = accountsFromSearch.map((item) => item.id); useEffect(() => { if (id) { @@ -220,7 +221,7 @@ const ListMembers: React.FC<{ let displayedAccountIds: string[]; if (mode === 'add' && searching) { - displayedAccountIds = searchAccountIds; + displayedAccountIds = accountIdsFromSearch; } else { displayedAccountIds = accountIds; } diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_collection.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_collection.tsx deleted file mode 100644 index c4700a1938..0000000000 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_collection.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; - -import { useAccount } from 'mastodon/hooks/useAccount'; - -import type { BaseConfirmationModalProps } from './confirmation_modal'; -import { ConfirmationModal } from './confirmation_modal'; - -const messages = defineMessages({ - title: { - id: 'confirmations.follow_to_collection.title', - defaultMessage: 'Follow account?', - }, - confirm: { - id: 'confirmations.follow_to_collection.confirm', - defaultMessage: 'Follow and add to collection', - }, -}); - -export const ConfirmFollowToCollectionModal: React.FC< - { - accountId: string; - onConfirm: () => void; - } & BaseConfirmationModalProps -> = ({ accountId, onConfirm, onClose }) => { - const intl = useIntl(); - const account = useAccount(accountId); - - return ( - @{account?.acct} }} - /> - } - confirm={intl.formatMessage(messages.confirm)} - onConfirm={onConfirm} - onClose={onClose} - /> - ); -}; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts index 4011b4db06..c27597fb52 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts @@ -13,7 +13,6 @@ export { ConfirmUnblockModal } from './unblock'; export { ConfirmClearNotificationsModal } from './clear_notifications'; export { ConfirmLogOutModal } from './log_out'; export { ConfirmFollowToListModal } from './follow_to_list'; -export { ConfirmFollowToCollectionModal } from './follow_to_collection'; export { ConfirmMissingAltTextModal } from './missing_alt_text'; export { ConfirmRevokeQuoteModal } from './revoke_quote'; export { QuietPostQuoteInfoModal } from './quiet_post_quote_info'; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index f32234d467..ee59e3f8cf 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -39,7 +39,6 @@ import { ConfirmClearNotificationsModal, ConfirmLogOutModal, ConfirmFollowToListModal, - ConfirmFollowToCollectionModal, ConfirmMissingAltTextModal, ConfirmRevokeQuoteModal, QuietPostQuoteInfoModal, @@ -69,7 +68,6 @@ export const MODAL_COMPONENTS = { 'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), 'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), 'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }), - 'CONFIRM_FOLLOW_TO_COLLECTION': () => Promise.resolve({ default: ConfirmFollowToCollectionModal }), 'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }), 'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }), 'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }), diff --git a/app/javascript/mastodon/hooks/useSearchAccounts.ts b/app/javascript/mastodon/hooks/useSearchAccounts.ts index 358632296f..c19f08e734 100644 --- a/app/javascript/mastodon/hooks/useSearchAccounts.ts +++ b/app/javascript/mastodon/hooks/useSearchAccounts.ts @@ -21,7 +21,7 @@ export function useSearchAccounts({ } = {}) { const dispatch = useAppDispatch(); - const [accountIds, setAccountIds] = useState([]); + const [accounts, setAccounts] = useState([]); const [loadingState, setLoadingState] = useState< 'idle' | 'loading' | 'error' >('idle'); @@ -37,7 +37,7 @@ export function useSearchAccounts({ if (value.trim().length === 0) { onSettled?.(''); if (resetOnInputClear) { - setAccountIds([]); + setAccounts([]); } return; } @@ -60,7 +60,7 @@ export function useSearchAccounts({ if (withRelationships) { dispatch(fetchRelationships(accountIds)); } - setAccountIds(accountIds); + setAccounts(accounts); setLoadingState('idle'); onSettled?.(value); }) @@ -74,13 +74,13 @@ export function useSearchAccounts({ ); const resetAccounts = useCallback(() => { - setAccountIds([]); + setAccounts([]); }, []); return { searchAccounts, resetAccounts, - accountIds, + accounts, isLoading: loadingState === 'loading', isError: loadingState === 'error', }; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 93f395d560..51d91c2115 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -505,9 +505,6 @@ "confirmations.discard_draft.post.title": "Discard your draft post?", "confirmations.discard_edit_media.confirm": "Discard", "confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?", - "confirmations.follow_to_collection.confirm": "Follow and add to collection", - "confirmations.follow_to_collection.message": "You need to be following {name} to add them to a collection.", - "confirmations.follow_to_collection.title": "Follow account?", "confirmations.follow_to_list.confirm": "Follow and add to list", "confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.", "confirmations.follow_to_list.title": "Follow user?", From 58f0a80ae9123c307c3a64340998edc12a2a361f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 09:48:15 +0200 Subject: [PATCH 17/37] Update Node.js to 24.15 (#38707) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index fd655f8a35..a2e33f6e2c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.14 +24.15 From 3c88310f37b6ae70930f6a85b6d13f1263a73827 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:08:49 +0200 Subject: [PATCH 18/37] New Crowdin Translations (automated) (#38726) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 18 +++++++------- app/javascript/mastodon/locales/cs.json | 2 -- app/javascript/mastodon/locales/cy.json | 9 ------- app/javascript/mastodon/locales/da.json | 14 ++++------- app/javascript/mastodon/locales/de.json | 12 +++------ app/javascript/mastodon/locales/el.json | 14 ++++------- app/javascript/mastodon/locales/en-GB.json | 9 ------- app/javascript/mastodon/locales/es-AR.json | 14 ++++------- app/javascript/mastodon/locales/es-MX.json | 14 ++++------- app/javascript/mastodon/locales/es.json | 14 ++++------- app/javascript/mastodon/locales/et.json | 27 ++++++++++++++------- app/javascript/mastodon/locales/fi.json | 14 ++++------- app/javascript/mastodon/locales/fo.json | 9 ------- app/javascript/mastodon/locales/fr-CA.json | 15 +++++------- app/javascript/mastodon/locales/fr.json | 15 +++++------- app/javascript/mastodon/locales/ga.json | 13 +++------- app/javascript/mastodon/locales/gd.json | 9 ------- app/javascript/mastodon/locales/gl.json | 14 ++++------- app/javascript/mastodon/locales/he.json | 15 +++++------- app/javascript/mastodon/locales/hu.json | 9 ------- app/javascript/mastodon/locales/is.json | 14 ++++------- app/javascript/mastodon/locales/it.json | 9 ------- app/javascript/mastodon/locales/ja.json | 5 ---- app/javascript/mastodon/locales/kab.json | 5 ---- app/javascript/mastodon/locales/nan-TW.json | 9 ------- app/javascript/mastodon/locales/nl.json | 14 ++++------- app/javascript/mastodon/locales/nn.json | 9 ------- app/javascript/mastodon/locales/no.json | 1 - app/javascript/mastodon/locales/pt-BR.json | 9 ------- app/javascript/mastodon/locales/pt-PT.json | 9 ------- app/javascript/mastodon/locales/ru.json | 6 ----- app/javascript/mastodon/locales/sq.json | 18 +++++++------- app/javascript/mastodon/locales/sv.json | 9 ------- app/javascript/mastodon/locales/th.json | 4 --- app/javascript/mastodon/locales/tr.json | 13 +++------- app/javascript/mastodon/locales/uk.json | 1 - app/javascript/mastodon/locales/vi.json | 14 ++++------- app/javascript/mastodon/locales/zh-CN.json | 14 ++++------- app/javascript/mastodon/locales/zh-TW.json | 14 ++++------- 39 files changed, 126 insertions(+), 311 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index d8b0d73049..2756471b30 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -359,8 +359,10 @@ "collection.share_template_other": "Глядзі, якая класная калекцыя: {link}", "collection.share_template_own": "Глядзі, у мяне новая калекцыя: {link}", "collections.account_count": "{count, plural,one {# уліковы запіс} few {# уліковыя запісы} other {# уліковых запісаў}}", - "collections.accounts.empty_description": "Дадайце да {count} уліковых запісаў, на якія Вы падпісаныя", + "collections.accounts.empty_description": "Дадайце да {count} уліковых запісаў", + "collections.accounts.empty_editor_title": "У гэтай калекцыі пакуль нікога няма", "collections.accounts.empty_title": "Гэтая калекцыя пустая", + "collections.block_collection_owner": "Заблакіраваць профіль", "collections.by_account": "ад {account_handle}", "collections.collection_description": "Апісанне", "collections.collection_language": "Мова", @@ -370,7 +372,8 @@ "collections.confirm_account_removal": "Упэўненыя, што хочаце прыбраць гэты ўліковы запіс з гэтай калекцыі?", "collections.content_warning": "Папярэджанне аб змесціве", "collections.continue": "Працягнуць", - "collections.create.accounts_subtitle": "Можна дадаць толькі ўліковыя запісы, на якія Вы падпісаныя і якія далі дазвол на тое, каб іх можна было знайсці.", + "collections.copy_link": "Капіяваць спасылку", + "collections.copy_link_confirmation": "Калекцыя скапіяваная ў буфер абмену", "collections.create.accounts_title": "Каго Вы ўключыце ў гэтую калекцыю?", "collections.create.basic_details_title": "Асноўныя звесткі", "collections.create.steps": "Крок {step}/{total}", @@ -390,7 +393,7 @@ "collections.error_loading_collections": "Адбылася памылка падчас загрузкі Вашых калекцый.", "collections.hidden_accounts_description": "Вы заблакіравалі або ігнаруеце {count, plural, one {гэтага карыстальніка} other {гэтых карыстальнікаў}}", "collections.hidden_accounts_link": "{count, plural, one {# схаваны ўліковы запіс} few {# схаваныя ўліковыя запісы} other {# схаваных уліковых запісаў}}", - "collections.hints.accounts_counter": "{count} / {max} уліковых запісаў", + "collections.hints.accounts_counter": "{count}/{max} уліковых запісаў", "collections.last_updated_at": "Апошняе абнаўленне: {date}", "collections.manage_accounts": "Кіраванне ўліковымі запісамі", "collections.mark_as_sensitive": "Пазначыць як адчувальную", @@ -398,15 +401,15 @@ "collections.name_length_hint": "Максімум 40 сімвалаў", "collections.new_collection": "Новая калекцыя", "collections.no_collections_yet": "Пакуль няма калекцый.", - "collections.old_last_post_note": "Апошні допіс быў больш за тыдзень таму", - "collections.remove_account": "Прыбраць гэты ўліковы запіс", + "collections.remove_account": "Выдаліць", "collections.report_collection": "Паскардзіцца на гэту калекцыю", "collections.revoke_collection_inclusion": "Прыбраць сябе з гэтай калекцыі", "collections.revoke_inclusion.confirmation": "Вас прыбралі з \"{collection}\"", "collections.revoke_inclusion.error": "Адбылася памылка, калі ласка, спрабуйце яшчэ раз пазней.", - "collections.search_accounts_label": "Шукайце ўліковыя запісы, каб дадаць іх сюды…", + "collections.search_accounts_label": "Адшукайце ўліковы запіс, каб дадаць яго сюды", "collections.search_accounts_max_reached": "Вы дадалі максімальную колькасць уліковых запісаў", "collections.sensitive": "Адчувальная", + "collections.share_short": "Абагуліць", "collections.topic_hint": "Дадайце хэштэг, які дапаможа іншым зразумець галоўную тэму гэтай калекцыі.", "collections.topic_special_chars_hint": "Спецыяльныя сімвалы будуць прыбраныя пры захаванні", "collections.unlisted_collections_description": "Яны не паказваюцца ў Вашым профілі іншым карыстальнікам. Іх можа пабачыць любы, хто мае спасылку на іх.", @@ -502,9 +505,6 @@ "confirmations.discard_draft.post.title": "Скасаваць чарнавік?", "confirmations.discard_edit_media.confirm": "Адмяніць", "confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?", - "confirmations.follow_to_collection.confirm": "Падпісацца і дадаць у калекцыю", - "confirmations.follow_to_collection.message": "Вам трэба падпісацца на {name}, каб дадаць яго/яе ў калекцыю.", - "confirmations.follow_to_collection.title": "Падпісацца на ўліковы запіс?", "confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс", "confirmations.follow_to_list.message": "Вам трэба падпісацца на {name}, каб дадаць яго/яе ў спіс.", "confirmations.follow_to_list.title": "Падпісацца на карыстальніка?", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 1f0dddbf5a..f1251fae17 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -252,8 +252,6 @@ "collections.name_length_hint": "Limit 40 znaků", "collections.new_collection": "Nová sbírka", "collections.no_collections_yet": "Ještě nemáte žádné sbírky.", - "collections.remove_account": "Odstranit tento účet", - "collections.search_accounts_label": "Hledat účty pro přidání…", "collections.search_accounts_max_reached": "Přidali jste maximální počet účtů", "collections.sensitive": "Citlivé", "collections.topic_hint": "Přidat štítek, který pomůže ostatním pochopit hlavní téma této kolekce.", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 7b1fca1ca5..18d3043a8a 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -329,7 +329,6 @@ "collection.share_template_other": "Edrychwch ar y casgliad trawiadol hwn: {link}", "collection.share_template_own": "Edrychwch ar fy nghasgliad newydd: {link}", "collections.account_count": "{count, plural, one {# cyfrif} other {# cyfrif}}", - "collections.accounts.empty_description": "Ychwanegwch hyd at {count} cyfrif rydych chi'n eu dilyn", "collections.accounts.empty_title": "Mae'r casgliad hwn yn wag", "collections.collection_description": "Disgrifiad", "collections.collection_name": "Enw", @@ -337,7 +336,6 @@ "collections.confirm_account_removal": "Ydych chi'n siŵr eich bod chi eisiau tynnu'r cyfrif hwn o'r casgliad hwn?", "collections.content_warning": "Rhybudd cynnwys", "collections.continue": "Parhau", - "collections.create.accounts_subtitle": "Dim ond cyfrifon rydych chi'n eu dilyn sydd wedi dewis cael eu darganfod y mae modd eu hychwanegu.", "collections.create.accounts_title": "Pwy fyddwch chi'n ei gynnwys yn y casgliad hwn?", "collections.create.basic_details_title": "Manylion sylfaenol", "collections.create.steps": "Cam {step}/{total}", @@ -352,7 +350,6 @@ "collections.detail.share": "Rhannu'r casgliad hwn", "collections.edit_details": "Golygu manylion", "collections.error_loading_collections": "Bu gwall wrth geisio llwytho eich casgliadau.", - "collections.hints.accounts_counter": "{count} / {max} cyfrif", "collections.last_updated_at": "Diweddarwyd ddiwethaf: {date}", "collections.manage_accounts": "Rheoli cyfrifon", "collections.mark_as_sensitive": "Marcio fel sensitif", @@ -360,13 +357,10 @@ "collections.name_length_hint": "Terfyn o 40 nod", "collections.new_collection": "Casgliad newydd", "collections.no_collections_yet": "Dim casgliadau eto.", - "collections.old_last_post_note": "Postiwyd ddiwethaf dros wythnos yn ôl", - "collections.remove_account": "Dileu'r cyfrif hwn", "collections.report_collection": "Adroddwch am y casgliad hwn", "collections.revoke_collection_inclusion": "Tynnu fy hun o'r casgliad hwn", "collections.revoke_inclusion.confirmation": "Rydych chi wedi cael eich tynnu o \"{collection}\"", "collections.revoke_inclusion.error": "Bu gwall, ceisiwch eto yn nes ymlaen.", - "collections.search_accounts_label": "Chwiliwch am gyfrifon i'w hychwanegu…", "collections.search_accounts_max_reached": "Rydych chi wedi ychwanegu'r nifer mwyaf o gyfrifon", "collections.sensitive": "Sensitif", "collections.topic_hint": "Ychwanegwch hashnod sy'n helpu eraill i ddeall prif bwnc y casgliad hwn.", @@ -460,9 +454,6 @@ "confirmations.discard_draft.post.title": "Dileu drafft eich postiad?", "confirmations.discard_edit_media.confirm": "Dileu", "confirmations.discard_edit_media.message": "Mae gennych newidiadau heb eu cadw i'r disgrifiad cyfryngau neu'r rhagolwg - eu dileu beth bynnag?", - "confirmations.follow_to_collection.confirm": "Dilyn ac ychwanegu at y casgliad", - "confirmations.follow_to_collection.message": "Mae angen i chi fod yn dilyn {name} i'w hychwanegu at gasgliad.", - "confirmations.follow_to_collection.title": "Dilyn cyfrif?", "confirmations.follow_to_list.confirm": "Dilyn ac ychwanegu at y rhestr", "confirmations.follow_to_list.message": "Mae angen i chi fod yn dilyn {name} i'w ychwanegu at restr.", "confirmations.follow_to_list.title": "Dilyn defnyddiwr?", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index b0906c357d..e8f708ac11 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Tjek denne seje samling: {link}", "collection.share_template_own": "Tjek min nye samling: {link}", "collections.account_count": "{count, plural, one {# konto} other {# konti}}", - "collections.accounts.empty_description": "Tilføj op til {count} konti, du følger", + "collections.accounts.empty_description": "Tilføj op til {count} konti", + "collections.accounts.empty_editor_title": "Ingen er i denne samling endnu", "collections.accounts.empty_title": "Denne samling er tom", "collections.block_collection_owner": "Blokér konto", "collections.by_account": "af {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Fortsæt", "collections.copy_link": "Kopiér link", "collections.copy_link_confirmation": "Link til samling kopieret til udklipsholderen", - "collections.create.accounts_subtitle": "Kun konti, du følger, og som har tilmeldt sig opdagelse, kan tilføjes.", "collections.create.accounts_title": "Hvem vil du fremhæve i denne samling?", "collections.create.basic_details_title": "Grundlæggende oplysninger", "collections.create.steps": "Trin {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Der opstod en fejl under indlæsning af dine samlinger.", "collections.hidden_accounts_description": "Du har blokeret eller skjult {count, plural, one {denne bruger} other {disse brugere}}", "collections.hidden_accounts_link": "{count, plural, one {# skjult konto} other {# skjulte konti}}", - "collections.hints.accounts_counter": "{count} / {max} konti", + "collections.hints.accounts_counter": "{count}/{max} konti", "collections.last_updated_at": "Senest opdateret: {date}", "collections.manage_accounts": "Administrer konti", "collections.mark_as_sensitive": "Markér som sensitiv", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Begrænset til 40 tegn", "collections.new_collection": "Ny samling", "collections.no_collections_yet": "Ingen samlinger endnu.", - "collections.old_last_post_note": "Seneste indlæg er fra over en uge siden", - "collections.remove_account": "Fjern denne konto", + "collections.remove_account": "Fjern", "collections.report_collection": "Anmeld denne samling", "collections.revoke_collection_inclusion": "Fjern mig selv fra denne samling", "collections.revoke_inclusion.confirmation": "Du er blevet fjernet fra \"{collection}\"", "collections.revoke_inclusion.error": "Der opstod en fejl, prøv igen senere.", - "collections.search_accounts_label": "Søg efter konti for at tilføje…", + "collections.search_accounts_label": "Søg efter en konto at tilføje", "collections.search_accounts_max_reached": "Du har tilføjet det maksimale antal konti", "collections.sensitive": "Sensitivt", "collections.share_short": "Del", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "Kassér dit indlægsudkast?", "confirmations.discard_edit_media.confirm": "Kassér", "confirmations.discard_edit_media.message": "Der er ugemte ændringer i mediebeskrivelsen eller forhåndsvisningen, kassér dem alligevel?", - "confirmations.follow_to_collection.confirm": "Følg og føj til samling", - "confirmations.follow_to_collection.message": "Du skal følge {name} for at føje vedkommende til en samling.", - "confirmations.follow_to_collection.title": "Følg konto?", "confirmations.follow_to_list.confirm": "Følg og føj til liste", "confirmations.follow_to_list.message": "Du skal følge {name} for at føje vedkommende til en liste.", "confirmations.follow_to_list.title": "Følg bruger?", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0e6cfe09b7..ad7d273914 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Seht euch diese coole Sammlung an: {link}", "collection.share_template_own": "Seht euch meine neue Sammlung an: {link}", "collections.account_count": "{count, plural, one {# Konto} other {# Konten}}", - "collections.accounts.empty_description": "Füge bis zu {count} Konten, denen du folgst, hinzu", + "collections.accounts.empty_description": "Füge bis zu {count} Konten hinzu", + "collections.accounts.empty_editor_title": "Noch befindet sich niemand in dieser Sammlung", "collections.accounts.empty_title": "Diese Sammlung ist leer", "collections.block_collection_owner": "Konto blockieren", "collections.by_account": "von {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Fortfahren", "collections.copy_link": "Link kopieren", "collections.copy_link_confirmation": "Link zur Sammlung in die Zwischenablage kopiert", - "collections.create.accounts_subtitle": "Du kannst nur Profile hinzufügen, denen du folgst und die das Hinzufügen gestatten.", "collections.create.accounts_title": "Wen möchtest du in dieser Sammlung präsentieren?", "collections.create.basic_details_title": "Allgemeine Informationen", "collections.create.steps": "Schritt {step}/{total}", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Maximal 40 Zeichen", "collections.new_collection": "Neue Sammlung", "collections.no_collections_yet": "Bisher keine Sammlungen vorhanden.", - "collections.old_last_post_note": "Neuester Beitrag mehr als eine Woche alt", - "collections.remove_account": "Dieses Konto entfernen", + "collections.remove_account": "Entfernen", "collections.report_collection": "Sammlung melden", "collections.revoke_collection_inclusion": "Mich aus dieser Sammlung entfernen", "collections.revoke_inclusion.confirmation": "Du wurdest aus „{collection}“ entfernt", "collections.revoke_inclusion.error": "Es ist ein Fehler aufgetreten. Bitte versuche es später erneut.", - "collections.search_accounts_label": "Suche nach Konten, um sie hinzuzufügen …", + "collections.search_accounts_label": "Suche nach einem Konto, um es hinzuzufügen", "collections.search_accounts_max_reached": "Du hast die Höchstzahl an Konten hinzugefügt", "collections.sensitive": "Inhaltswarnung", "collections.share_short": "Teilen", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "Entwurf verwerfen?", "confirmations.discard_edit_media.confirm": "Verwerfen", "confirmations.discard_edit_media.message": "Du hast Änderungen an der Medienbeschreibung oder -vorschau vorgenommen, die noch nicht gespeichert sind. Trotzdem verwerfen?", - "confirmations.follow_to_collection.confirm": "Folgen und zur Sammlung hinzufügen", - "confirmations.follow_to_collection.message": "Du musst {name} folgen, um das Profil zu einer Sammlung hinzufügen zu können.", - "confirmations.follow_to_collection.title": "Konto folgen?", "confirmations.follow_to_list.confirm": "Folgen und zur Liste hinzufügen", "confirmations.follow_to_list.message": "Du musst {name} folgen, um das Profil zu einer Liste hinzufügen zu können.", "confirmations.follow_to_list.title": "Profil folgen?", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index a491854a0a..fd5a84cdfd 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Δείτε αυτή την ωραία συλλογή: {link}", "collection.share_template_own": "Δείτε τη νέα μου συλλογή: {link}", "collections.account_count": "{count, plural, one {# λογαριασμός} other {# λογαριασμοί}}", - "collections.accounts.empty_description": "Προσθέστε μέχρι και {count} λογαριασμούς που ακολουθείτε", + "collections.accounts.empty_description": "Προσθέστε μέχρι και {count} λογαριασμούς", + "collections.accounts.empty_editor_title": "Κανείς δεν είναι ακόμη σε αυτήν τη συλλογή", "collections.accounts.empty_title": "Αυτή η συλλογή είναι κενή", "collections.block_collection_owner": "Αποκλεισμός λογαριασμού", "collections.by_account": "από {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Συνέχεια", "collections.copy_link": "Αντιγραφή συνδέσμου", "collections.copy_link_confirmation": "Αντιγράφηκε ο σύνδεσμος συλλογής στο πρόχειρο", - "collections.create.accounts_subtitle": "Μόνο οι λογαριασμοί που ακολουθείτε που έχουν επιλέξει ανακάλυψη μπορούν να προστεθούν.", "collections.create.accounts_title": "Ποιον θα αναδείξετε σε αυτήν τη συλλογή;", "collections.create.basic_details_title": "Βασικά στοιχεία", "collections.create.steps": "Βήμα {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια φόρτωσης των συλλογών σας.", "collections.hidden_accounts_description": "Έχετε αποκλείσει ή κάνει σίγαση {count, plural, one {αυτόν τον χρήστη} other {αυτούς τους χρήστες}}", "collections.hidden_accounts_link": "{count, plural, one {# κρυμμένος λογαριασμός} other {# κρυμμένοι λογαριασμοί}}", - "collections.hints.accounts_counter": "{count} / {max} λογαριασμοί", + "collections.hints.accounts_counter": "{count}/{max} λογαριασμοί", "collections.last_updated_at": "Τελευταία ενημέρωση: {date}", "collections.manage_accounts": "Διαχείριση λογαριασμών", "collections.mark_as_sensitive": "Σήμανση ως ευαίσθητο", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Όριο 40 χαρακτήρων", "collections.new_collection": "Νέα συλλογή", "collections.no_collections_yet": "Καμία συλλογή ακόμη.", - "collections.old_last_post_note": "Τελευταία ανάρτηση πριν από μια εβδομάδα", - "collections.remove_account": "Αφαίρεση λογαριασμού", + "collections.remove_account": "Αφαίρεση", "collections.report_collection": "Αναφορά αυτής της συλλογής", "collections.revoke_collection_inclusion": "Αφαίρεσε τον εαυτό μου από αυτήν τη συλλογή", "collections.revoke_inclusion.confirmation": "Έχεις αφαιρεθεί από τη συλλογή \"{collection}\"", "collections.revoke_inclusion.error": "Υπήρξε ένα σφάλμα, παρακαλούμε προσπαθήστε ξανά αργότερα.", - "collections.search_accounts_label": "Αναζήτηση λογαριασμών για προσθήκη…", + "collections.search_accounts_label": "Αναζήτηση λογαριασμού για προσθήκη", "collections.search_accounts_max_reached": "Έχετε προσθέσει τον μέγιστο αριθμό λογαριασμών", "collections.sensitive": "Ευαίσθητο", "collections.share_short": "Κοινοποίηση", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "Απόρριψη της πρόχειρης ανάρτησης σας;", "confirmations.discard_edit_media.confirm": "Απόρριψη", "confirmations.discard_edit_media.message": "Έχεις μη αποθηκευμένες αλλαγές στην περιγραφή πολυμέσων ή στην προεπισκόπηση, απόρριψη ούτως ή άλλως;", - "confirmations.follow_to_collection.confirm": "Ακολουθήστε και προσθέστε στη συλλογή", - "confirmations.follow_to_collection.message": "Πρέπει να ακολουθήσετε τον χρήστη {name} για να τους προσθέσετε σε μια συλλογή.", - "confirmations.follow_to_collection.title": "Ακολουθήστε τον λογαριασμό;", "confirmations.follow_to_list.confirm": "Ακολούθησε και πρόσθεσε στη λίστα", "confirmations.follow_to_list.message": "Πρέπει να ακολουθήσεις τον χρήστη {name} για να τον προσθέσεις σε μια λίστα.", "confirmations.follow_to_list.title": "Ακολούθηση χρήστη;", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index d048b6254d..74f9bdd7cc 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -357,7 +357,6 @@ "collection.share_template_other": "Check out this cool collection: {link}", "collection.share_template_own": "Check out my new collection: {link}", "collections.account_count": "{count, plural, one {# account} other {# accounts}}", - "collections.accounts.empty_description": "Add up to {count} accounts you follow", "collections.accounts.empty_title": "This collection is empty", "collections.by_account": "Delete", "collections.collection_description": "Description", @@ -368,7 +367,6 @@ "collections.confirm_account_removal": "Are you sure you want to remove this account from this collection?", "collections.content_warning": "Content warning", "collections.continue": "Continue", - "collections.create.accounts_subtitle": "Only accounts you follow who have opted into discovery can be added.", "collections.create.accounts_title": "Who will you feature in this collection?", "collections.create.basic_details_title": "Basic details", "collections.create.steps": "Step {step}/{total}", @@ -386,7 +384,6 @@ "collections.detail.you_are_in_this_collection": "You're featured in this collection", "collections.edit_details": "Edit details", "collections.error_loading_collections": "There was an error when trying to load your collections.", - "collections.hints.accounts_counter": "{count} / {max} accounts", "collections.last_updated_at": "Last updated: {date}", "collections.manage_accounts": "Manage accounts", "collections.mark_as_sensitive": "Mark as sensitive", @@ -394,13 +391,10 @@ "collections.name_length_hint": "40 characters limit", "collections.new_collection": "New collection", "collections.no_collections_yet": "No collections yet.", - "collections.old_last_post_note": "Last posted over a week ago", - "collections.remove_account": "Remove this account", "collections.report_collection": "Report this collection", "collections.revoke_collection_inclusion": "Remove myself from this collection", "collections.revoke_inclusion.confirmation": "You've been removed from \"{collection}\"", "collections.revoke_inclusion.error": "There was an error, please try again later.", - "collections.search_accounts_label": "Search for accounts to add…", "collections.search_accounts_max_reached": "You have added the maximum number of accounts", "collections.sensitive": "Sensitive", "collections.topic_hint": "Add a hashtag that helps others understand the main topic of this collection.", @@ -496,9 +490,6 @@ "confirmations.discard_draft.post.title": "Discard your draft post?", "confirmations.discard_edit_media.confirm": "Discard", "confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?", - "confirmations.follow_to_collection.confirm": "Follow and add to collection", - "confirmations.follow_to_collection.message": "You need to be following {name} to add them to a collection.", - "confirmations.follow_to_collection.title": "Follow account?", "confirmations.follow_to_list.confirm": "Follow and add to list", "confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.", "confirmations.follow_to_list.title": "Follow user?", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index f3a868ec0e..a238950969 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -359,7 +359,8 @@ "collection.share_template_other": "¡Mirá qué copada está esta colección! {link}", "collection.share_template_own": "Mirá mi nueva colección: {link}", "collections.account_count": "{count, plural, one {# hora} other {# horas}}", - "collections.accounts.empty_description": "Agregá hasta {count} cuentas que seguís", + "collections.accounts.empty_description": "Agregá hasta {count} cuentas", + "collections.accounts.empty_editor_title": "Todavía no hay nadie en esta colección", "collections.accounts.empty_title": "Esta colección está vacía", "collections.block_collection_owner": "Bloquear cuenta", "collections.by_account": "por {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Continuar", "collections.copy_link": "Copiar enlace", "collections.copy_link_confirmation": "Enlace a la colección copiado al portapapeles", - "collections.create.accounts_subtitle": "Solo las cuentas que seguís —las cuales optaron por ser descubiertas— pueden ser agregadas.", "collections.create.accounts_title": "¿A quién vas a destacar en esta colección?", "collections.create.basic_details_title": "Detalles básicos", "collections.create.steps": "Paso {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Hubo un error al intentar cargar tus colecciones.", "collections.hidden_accounts_description": "Bloqueaste o silenciaste a {count, plural, one {este usuario} other {estos usuarios}}", "collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}", - "collections.hints.accounts_counter": "{count} / {max} cuentas", + "collections.hints.accounts_counter": "{count}/{max} cuentas", "collections.last_updated_at": "Última actualización: {date}", "collections.manage_accounts": "Administrar cuentas", "collections.mark_as_sensitive": "Marcar como sensible", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Límite de 40 caracteres", "collections.new_collection": "Nueva colección", "collections.no_collections_yet": "No hay colecciones aún.", - "collections.old_last_post_note": "Último mensaje hace más de una semana", - "collections.remove_account": "Eliminar esta cuenta", + "collections.remove_account": "Quitar", "collections.report_collection": "Denunciar esta colección", "collections.revoke_collection_inclusion": "Quitarme de esta colección", "collections.revoke_inclusion.confirmation": "Saliste de «{collection}»", "collections.revoke_inclusion.error": "Hubo un error; por favor, intentalo de nuevo más tarde.", - "collections.search_accounts_label": "Buscar cuentas para agregar…", + "collections.search_accounts_label": "Buscar una cuenta para agregar", "collections.search_accounts_max_reached": "Agregaste el número máximo de cuentas", "collections.sensitive": "Sensible", "collections.share_short": "Compartir", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "¿Descartar tu borrador?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Tenés cambios sin guardar en la descripción de medios o en la vista previa, ¿querés descartarlos de todos modos?", - "confirmations.follow_to_collection.confirm": "Seguir y agregar a la colección", - "confirmations.follow_to_collection.message": "Necesitás seguir a {name} para agregarle a una colección.", - "confirmations.follow_to_collection.title": "¿Seguir cuenta?", "confirmations.follow_to_list.confirm": "Seguir y agregar a la lista", "confirmations.follow_to_list.message": "Necesitás seguir a {name} para agregarle a una lista.", "confirmations.follow_to_list.title": "¿Querés seguirle?", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index ad9a0eaafd..7941096cbc 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Echa un vistazo a esta increíble colección: {link}", "collection.share_template_own": "Echa un vistazo a mi nueva colección: {link}", "collections.account_count": "{count, plural,one {# cuenta} other {# cuentas}}", - "collections.accounts.empty_description": "Añade hasta {count} cuentas que sigues", + "collections.accounts.empty_description": "Añade hasta {count} cuentas", + "collections.accounts.empty_editor_title": "No hay nadie en esta colección todavía", "collections.accounts.empty_title": "Esta colección está vacía", "collections.block_collection_owner": "Bloquer cuenta", "collections.by_account": "de {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Continuar", "collections.copy_link": "Copiar enlace", "collections.copy_link_confirmation": "Se ha copiado el enlace de la colección al portapapeles", - "collections.create.accounts_subtitle": "Solo se pueden añadir cuentas que sigas y que hayan optado por aparecer en los resultados de búsqueda.", "collections.create.accounts_title": "¿A quién incluirás en esta colección?", "collections.create.basic_details_title": "Detalles básicos", "collections.create.steps": "Paso {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Se produjo un error al intentar cargar tus colecciones.", "collections.hidden_accounts_description": "Has bloqueado o silenciado {count, plural, one {a este usuario} other {a estos usuarios}}", "collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}", - "collections.hints.accounts_counter": "{count} / {max} cuentas", + "collections.hints.accounts_counter": "{count}/{max} cuentas", "collections.last_updated_at": "Última actualización: {date}", "collections.manage_accounts": "Administrar cuentas", "collections.mark_as_sensitive": "Marcar como sensible", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Limitado a 40 caracteres", "collections.new_collection": "Nueva colección", "collections.no_collections_yet": "No hay colecciones todavía.", - "collections.old_last_post_note": "Última publicación hace más de una semana", - "collections.remove_account": "Eliminar esta cuenta", + "collections.remove_account": "Eliminar", "collections.report_collection": "Reportar esta colección", "collections.revoke_collection_inclusion": "Excluirme de esta colección", "collections.revoke_inclusion.confirmation": "Has sido excluido de \"{collection}\"", "collections.revoke_inclusion.error": "Se ha producido un error, por favor, inténtalo de nuevo más tarde.", - "collections.search_accounts_label": "Buscar cuentas para añadir…", + "collections.search_accounts_label": "Buscar una cuenta para añadir", "collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas", "collections.sensitive": "Sensible", "collections.share_short": "Compartir", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "¿Deseas descartar tu borrador?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Tienes cambios sin guardar en la descripción o vista previa del archivo, ¿deseas descartarlos de cualquier manera?", - "confirmations.follow_to_collection.confirm": "Seguir y añadir a la colección", - "confirmations.follow_to_collection.message": "Debes seguir a {name} para añadirlo a una colección.", - "confirmations.follow_to_collection.title": "¿Seguir cuenta?", "confirmations.follow_to_list.confirm": "Seguir y agregar a la lista", "confirmations.follow_to_list.message": "Tienes que seguir a {name} para añadirlo a una lista.", "confirmations.follow_to_list.title": "¿Seguir a usuario?", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index bea76a9996..2e41b99125 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Echa un vistazo a esta fantástica colección: {link}", "collection.share_template_own": "Echa un vistazo a mi nueva colección: {link}", "collections.account_count": "{count, plural, one {# cuenta} other {# cuentas}}", - "collections.accounts.empty_description": "Añade hasta {count} cuentas que sigas", + "collections.accounts.empty_description": "Añade hasta {count} cuentas", + "collections.accounts.empty_editor_title": "No hay nadie en esta colección todavía", "collections.accounts.empty_title": "Esta colección está vacía", "collections.block_collection_owner": "Bloquear cuenta", "collections.by_account": "de {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Continuar", "collections.copy_link": "Copiar enlace", "collections.copy_link_confirmation": "Enlace a la colección copiado al portapapeles", - "collections.create.accounts_subtitle": "Solo pueden añadirse cuentas que sigues y que han activado el descubrimiento.", "collections.create.accounts_title": "¿A quién vas a destacar en esta colección?", "collections.create.basic_details_title": "Datos básicos", "collections.create.steps": "Paso {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Se ha producido un error al intentar cargar tus colecciones.", "collections.hidden_accounts_description": "Has bloqueado o silenciado {count, plural,one {a este usuario}other {a estos usuarios}}", "collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}", - "collections.hints.accounts_counter": "{count} / {max} cuentas", + "collections.hints.accounts_counter": "{count}/{max} cuentas", "collections.last_updated_at": "Última actualización: {date}", "collections.manage_accounts": "Administrar cuentas", "collections.mark_as_sensitive": "Marcar como sensible", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Límite de 40 caracteres", "collections.new_collection": "Nueva colección", "collections.no_collections_yet": "Aún no hay colecciones.", - "collections.old_last_post_note": "Última publicación hace más de una semana", - "collections.remove_account": "Quitar esta cuenta", + "collections.remove_account": "Eliminar", "collections.report_collection": "Informar de esta colección", "collections.revoke_collection_inclusion": "Sácame de esta colección", "collections.revoke_inclusion.confirmation": "Has salido de \"{collection}\"", "collections.revoke_inclusion.error": "Se ha producido un error, inténtalo de nuevo más tarde.", - "collections.search_accounts_label": "Buscar cuentas para añadir…", + "collections.search_accounts_label": "Buscar una cuenta para añadir", "collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas", "collections.sensitive": "Sensible", "collections.share_short": "Compartir", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "¿Descartar tu borrador?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Tienes cambios sin guardar en la descripción o vista previa del archivo audiovisual, ¿descartarlos de todos modos?", - "confirmations.follow_to_collection.confirm": "Seguir y añadir a la colección", - "confirmations.follow_to_collection.message": "Debes seguir a {name} para añadirlo a una colección.", - "confirmations.follow_to_collection.title": "¿Seguir cuenta?", "confirmations.follow_to_list.confirm": "Seguir y añadir a la lista", "confirmations.follow_to_list.message": "Necesitas seguir a {name} para agregarlo a una lista.", "confirmations.follow_to_list.title": "¿Seguir usuario?", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 6f060f28df..1f73933dea 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -359,8 +359,10 @@ "collection.share_template_other": "Vaata seda lahedat kogumikku: {link}", "collection.share_template_own": "Vaata mu uut kogumikku: {link}", "collections.account_count": "{count, plural, one {# kasutajakonto} other {# kasutajakontot}}", - "collections.accounts.empty_description": "Lisa kuni {count} kontot, mida jälgid", + "collections.accounts.empty_description": "Lisa kuni {count} kontot", + "collections.accounts.empty_editor_title": "Selles kogus pole veel kedagi", "collections.accounts.empty_title": "See kogumik on tühi", + "collections.block_collection_owner": "Konto blokeerimine", "collections.by_account": "{account_handle} poolt", "collections.collection_description": "Kirjeldus", "collections.collection_language": "Keel", @@ -370,7 +372,8 @@ "collections.confirm_account_removal": "Kas oled kindel, et soovid selle konto kogumikust eemaldada?", "collections.content_warning": "Sisuhoiatus", "collections.continue": "Jätka", - "collections.create.accounts_subtitle": "Lisada saad vaid kasutajakontosid, keda sa jälgid ja kes on sellise leiatvuse lubanud.", + "collections.copy_link": "Kopeeri link", + "collections.copy_link_confirmation": "Kogu link kopeeriti lõikelauale", "collections.create.accounts_title": "Kes saavad olema selles kogumikus?", "collections.create.basic_details_title": "Põhiandmed", "collections.create.steps": "Samm {step}/{total}", @@ -390,7 +393,7 @@ "collections.error_loading_collections": "Sinu kogumike laadimisel tekkis viga.", "collections.hidden_accounts_description": "Oled blokeerinud või vaigistanud {count, plural, one {selle kasutaja} other {need kasutajad}}", "collections.hidden_accounts_link": "{count, plural, one {# peidetud konto} other {# peidetud kontot}}", - "collections.hints.accounts_counter": "{count} / {max} kontot", + "collections.hints.accounts_counter": "{count}/{max} kontot", "collections.last_updated_at": "Viimati uuendatud: {date}", "collections.manage_accounts": "Halda kasutajakontosid", "collections.mark_as_sensitive": "Märgi delikaatseks", @@ -398,17 +401,19 @@ "collections.name_length_hint": "40 märgi piir", "collections.new_collection": "Uus kogumik", "collections.no_collections_yet": "Kogumikke veel pole.", - "collections.old_last_post_note": "Viimati postitanud üle nädala tagasi", - "collections.remove_account": "Eemalda see konto", + "collections.remove_account": "Eemalda", "collections.report_collection": "Raporteeri sellest kogumikust", "collections.revoke_collection_inclusion": "Eemalda mind sellest kogumikust", "collections.revoke_inclusion.confirmation": "Oled eemaldatud \"{collection}\"-st", "collections.revoke_inclusion.error": "Oli viga, palun proovi hiljem uuesti.", - "collections.search_accounts_label": "Lisatavate kontode otsimine…", + "collections.search_accounts_label": "Otsi kontot, mida lisada", "collections.search_accounts_max_reached": "Oled lisanud maksimumarv kontosid", "collections.sensitive": "Tundlik", + "collections.share_short": "Jaga", "collections.topic_hint": "Lisa teemaviide, mis aitab teistel kasutajatel mõista selle kogumiku põhisisu.", "collections.topic_special_chars_hint": "Erimärgid eemaldatakse salvestamisel", + "collections.unlisted_collections_description": "Need pole sinu profiilis teiste kasutajate jaoks nähtavad. Neid saavad vaadata kõik, kellel on link.", + "collections.unlisted_collections_with_count": "Loetlemata kogud ({count})", "collections.view_collection": "Vaata kogumikku", "collections.view_other_collections_by_user": "Vaata selle kasuta ja teisi kogumikke", "collections.visibility_public": "Avalik", @@ -500,9 +505,6 @@ "confirmations.discard_draft.post.title": "Kas loobud postituse kavandist?", "confirmations.discard_edit_media.confirm": "Hülga", "confirmations.discard_edit_media.message": "Sul on salvestamata muudatusi meediakirjelduses või eelvaates, kas hülgad need?", - "confirmations.follow_to_collection.confirm": "Jälgi ja lisa kogumikku", - "confirmations.follow_to_collection.message": "Pead jälgima {name}, enne kui neid kogumikku lisada saad.", - "confirmations.follow_to_collection.title": "Jälgida kontot?", "confirmations.follow_to_list.confirm": "Jälgi ja lisa loetellu", "confirmations.follow_to_list.message": "Pead jälgima kasutajat {name}, et lisada teda loetellu.", "confirmations.follow_to_list.title": "Jälgida kasutajat?", @@ -1029,12 +1031,14 @@ "notifications_permission_banner.title": "Ära jää millestki ilma", "onboarding.follows.back": "Tagasi", "onboarding.follows.empty": "Kahjuks ei saa hetkel tulemusi näidata. Proovi kasutada otsingut või lehitse uurimise lehte, et leida inimesi, keda jälgida, või proovi hiljem uuesti.", + "onboarding.follows.next": "Järgmine: Seadista oma profiil", "onboarding.follows.search": "Otsi", "onboarding.follows.title": "Jälgi inimesi, et alustada", "onboarding.profile.discoverable": "Muuda mu profiil avastatavaks", "onboarding.profile.discoverable_hint": "Kui nõustud enda avastamisega Mastodonis, võivad sinu postitused ilmuda otsingutulemustes ja trendides ning sinu profiili võidakse soovitada sinuga sarnaste huvidega inimestele.", "onboarding.profile.display_name": "Näidatav nimi", "onboarding.profile.display_name_hint": "Su täisnimi või naljanimi…", + "onboarding.profile.finish": "Valmis", "onboarding.profile.note": "Elulugu", "onboarding.profile.note_hint": "Saad @mainida teisi kasutajaid või lisada #teemaviiteid…", "onboarding.profile.title": "Profiili seadistamine", @@ -1106,6 +1110,7 @@ "report.category.title_account": "kontoga", "report.category.title_status": "postitusega", "report.close": "Valmis", + "report.collection_comment": "Miks soovid sellest kogust raporteerida?", "report.comment.title": "Kas arvad, et on veel midagi, mida me peaks teadma?", "report.forward": "Edasta ka {target} domeeni", "report.forward_hint": "See kasutaja on teisest serverist. Kas saadan anonümiseeritud koopia sellest teatest sinna ka?", @@ -1127,6 +1132,8 @@ "report.rules.title": "Milliseid reegleid rikutakse?", "report.statuses.subtitle": "Vali kõik, mis sobivad", "report.statuses.title": "Kas on olemas postitusi, mis on sellele teavitusele tõenduseks?", + "report.submission_error": "Raportit ei saanud saata", + "report.submission_error_details": "Palun kontrolli internetiühendust ja proovi hiljem uuesti.", "report.submit": "Esita", "report.target": "Teatamine {target} kohta", "report.thanks.take_action": "Need on su võimalused määrata, mida Mastodonis näed:", @@ -1180,6 +1187,8 @@ "sign_in_banner.mastodon_is": "Mastodon on parim viis olemaks kursis sellega, mis toimub.", "sign_in_banner.sign_in": "Logi sisse", "sign_in_banner.sso_redirect": "Sisene või registreeru", + "skip_links.hotkey": "Kiirklahv {hotkey}", + "skip_links.skip_to_content": "Hüppa põhisisuni", "status.admin_account": "Ava @{name} moderaatorivaates", "status.admin_domain": "Ava {domain} modeereerimisliides", "status.admin_status": "Ava postitus moderaatorivaates", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 0d009f80c1..980fcc83d2 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Katso tämä siisti kokoelma: {link}", "collection.share_template_own": "Katso uusi kokoelmani: {link}", "collections.account_count": "{count, plural, one {# tili} other {# tiliä}}", - "collections.accounts.empty_description": "Lisää enintään {count} seuraamaasi tiliä", + "collections.accounts.empty_description": "Lisää enintään {count} tiliä", + "collections.accounts.empty_editor_title": "Kukaan ei ole vielä tässä kokoelmassa", "collections.accounts.empty_title": "Tämä kokoelma on tyhjä", "collections.block_collection_owner": "Estä tili", "collections.by_account": "koonnut {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Jatka", "collections.copy_link": "Kopioi linkki", "collections.copy_link_confirmation": "Kokoelman linkki kopioitu leikepöydälle", - "collections.create.accounts_subtitle": "Lisätä voi vain tilejä, joita seuraat ja jotka ovat valinneet tulla löydetyiksi.", "collections.create.accounts_title": "Keitä esittelet tässä kokoelmassa?", "collections.create.basic_details_title": "Perustiedot", "collections.create.steps": "Vaihe {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Kokoelmien latauksessa tapahtui virhe.", "collections.hidden_accounts_description": "Olet estänyt tai mykistänyt {count, plural, one {tämän käyttäjän} other {nämä käyttäjät}}", "collections.hidden_accounts_link": "{count, plural, one {# piilotettu tili} other {# piilotettua tiliä}}", - "collections.hints.accounts_counter": "{count} / {max} tiliä", + "collections.hints.accounts_counter": "{count}/{max} tiliä", "collections.last_updated_at": "Päivitetty viimeksi {date}", "collections.manage_accounts": "Hallitse tilejä", "collections.mark_as_sensitive": "Merkitse arkaluonteiseksi", @@ -401,13 +401,12 @@ "collections.name_length_hint": "40 merkin rajoitus", "collections.new_collection": "Uusi kokoelma", "collections.no_collections_yet": "Ei vielä kokoelmia.", - "collections.old_last_post_note": "Julkaissut viimeksi yli viikko sitten", - "collections.remove_account": "Poista tämä tili", + "collections.remove_account": "Poista", "collections.report_collection": "Raportoi tämä kokoelma", "collections.revoke_collection_inclusion": "Poista itseni tästä kokoelmasta", "collections.revoke_inclusion.confirmation": "Sinut on poistettu kokoelmasta ”{collection}”", "collections.revoke_inclusion.error": "Tapahtui virhe – yritä myöhemmin uudelleen.", - "collections.search_accounts_label": "Hae lisättäviä tilejä…", + "collections.search_accounts_label": "Hae lisättävää tiliä", "collections.search_accounts_max_reached": "Olet lisännyt enimmäismäärän tilejä", "collections.sensitive": "Arkaluonteinen", "collections.share_short": "Jaa", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "Hylätäänkö luonnosjulkaisusi?", "confirmations.discard_edit_media.confirm": "Hylkää", "confirmations.discard_edit_media.message": "Sinulla on tallentamattomia muutoksia median kuvaukseen tai esikatseluun. Hylätäänkö ne silti?", - "confirmations.follow_to_collection.confirm": "Seuraa ja lisää kokoelmaan", - "confirmations.follow_to_collection.message": "Sinun on seurattava tiliä {name}, jotta voit lisätä sen kokoelmaan.", - "confirmations.follow_to_collection.title": "Seurataanko tiliä?", "confirmations.follow_to_list.confirm": "Seuraa ja lisää listaan", "confirmations.follow_to_list.message": "Sinun on seurattava käyttäjää {name}, jotta voit lisätä hänet listaan.", "confirmations.follow_to_list.title": "Seurataanko käyttäjää?", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index f32a934a18..542e68dee2 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -278,7 +278,6 @@ "collection.share_template_other": "Hygg at hesum kula savninum: {link}", "collection.share_template_own": "Hygg at mínum nýggja savni: {link}", "collections.account_count": "{count, plural, one {# konta} other {# kontur}}", - "collections.accounts.empty_description": "Legg afturat upp til {count} kontur, sum tú fylgir", "collections.accounts.empty_title": "Hetta savnið er tómt", "collections.collection_description": "Lýsing", "collections.collection_name": "Navn", @@ -286,7 +285,6 @@ "collections.confirm_account_removal": "Er tú vís/ur í, at tú vilt strika hesa kontuna frá hesum savninum?", "collections.content_warning": "Innihaldsávaring", "collections.continue": "Halt fram", - "collections.create.accounts_subtitle": "Einans kontur, sum tú fylgir og sum hava játtað at blíva uppdagaðar, kunnu leggjast afturat.", "collections.create.accounts_title": "Hvønn vil tú framhevja í hesum savninum?", "collections.create.basic_details_title": "Grundleggjandi smálutir", "collections.create.steps": "Stig {step}/{total}", @@ -299,7 +297,6 @@ "collections.detail.share": "Deil hetta savnið", "collections.edit_details": "Rætta smálutir", "collections.error_loading_collections": "Ein feilur hendi, tá tú royndi at finna fram søvnini hjá tær.", - "collections.hints.accounts_counter": "{count} / {max} kontur", "collections.last_updated_at": "Seinast dagført: {date}", "collections.manage_accounts": "Umsit kontur", "collections.mark_as_sensitive": "Merk sum viðkvæmt", @@ -307,10 +304,7 @@ "collections.name_length_hint": "Í mesta lagi 40 tekn", "collections.new_collection": "Nýtt savn", "collections.no_collections_yet": "Eingi søvn enn.", - "collections.old_last_post_note": "Postaði seinast fyri meira enn einari viku síðani", - "collections.remove_account": "Strika hesa kontuna", "collections.report_collection": "Melda hetta savnið", - "collections.search_accounts_label": "Leita eftir kontum at leggja afturat…", "collections.search_accounts_max_reached": "Tú hevur lagt afturat mesta talið av kontum", "collections.sensitive": "Viðkvæmt", "collections.topic_hint": "Legg afturat eitt frámerki, sum hjálpir øðrum at skilja høvuðevnið í hesum savninum.", @@ -403,9 +397,6 @@ "confirmations.discard_draft.post.title": "Vraka kladdupostin?", "confirmations.discard_edit_media.confirm": "Vraka", "confirmations.discard_edit_media.message": "Tú hevur broytingar í miðlalýsingini ella undansýningini, sum ikki eru goymdar. Vilt tú kortini vraka?", - "confirmations.follow_to_collection.confirm": "Fylg og legg afturat savni", - "confirmations.follow_to_collection.message": "Tú mást fylgja {name} fyri at leggja tey afturat einum savni.", - "confirmations.follow_to_collection.title": "Fylg kontu?", "confirmations.follow_to_list.confirm": "Fylg og legg afturat lista", "confirmations.follow_to_list.message": "Tú mást fylgja {name} fyri at leggja tey afturat einum lista.", "confirmations.follow_to_list.title": "Fylg brúkara?", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 05cefacce2..d06b2fc1ed 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -359,8 +359,8 @@ "collection.share_template_other": "Découvrez cette collection incroyable : {link}", "collection.share_template_own": "Découvrez ma nouvelle collection : {link}", "collections.account_count": "{count, plural, one {# compte} other {# comptes}}", - "collections.accounts.empty_description": "Ajouter jusqu'à {count} comptes que vous suivez", "collections.accounts.empty_title": "Cette collection est vide", + "collections.block_collection_owner": "Bloquer le compte", "collections.by_account": "par {account_handle}", "collections.collection_description": "Description", "collections.collection_language": "Langue", @@ -370,7 +370,8 @@ "collections.confirm_account_removal": "Voulez-vous vraiment supprimer ce compte de la collection ?", "collections.content_warning": "Avertissement au public", "collections.continue": "Continuer", - "collections.create.accounts_subtitle": "Seuls les comptes que vous suivez et qui ont autorisé leur découverte peuvent être ajoutés.", + "collections.copy_link": "Copier le lien", + "collections.copy_link_confirmation": "Lien de la collection copié dans le presse-papiers", "collections.create.accounts_title": "Qui voulez-vous mettre en avant dans cette collection ?", "collections.create.basic_details_title": "Informations générales", "collections.create.steps": "Étape {step}/{total}", @@ -390,7 +391,6 @@ "collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.", "collections.hidden_accounts_description": "Vous avez bloqué ou masqué {count, plural, one {ce compte} other {ces comptes}}", "collections.hidden_accounts_link": "{count, plural, one {# compte caché} other {# comptes cachés}}", - "collections.hints.accounts_counter": "{count} / {max} comptes", "collections.last_updated_at": "Dernière mise à jour : {date}", "collections.manage_accounts": "Gérer les comptes", "collections.mark_as_sensitive": "Marquer comme sensible", @@ -398,17 +398,17 @@ "collections.name_length_hint": "Maximum 40 caractères", "collections.new_collection": "Nouvelle collection", "collections.no_collections_yet": "Aucune collection pour le moment.", - "collections.old_last_post_note": "Dernier message il y a plus d'une semaine", - "collections.remove_account": "Supprimer ce compte", "collections.report_collection": "Signaler cette collection", "collections.revoke_collection_inclusion": "Me retirer de cette collection", "collections.revoke_inclusion.confirmation": "Vous avez été retiré·e de « {collection} »", "collections.revoke_inclusion.error": "Une erreur s'est produite, veuillez réessayer plus tard.", - "collections.search_accounts_label": "Chercher des comptes à ajouter…", "collections.search_accounts_max_reached": "Vous avez ajouté le nombre maximum de comptes", "collections.sensitive": "Sensible", + "collections.share_short": "Partager", "collections.topic_hint": "Ajouter un hashtag pour aider les autres personnes à comprendre le sujet de la collection.", "collections.topic_special_chars_hint": "Les caractères spéciaux seront supprimés lors de l'enregistrement", + "collections.unlisted_collections_description": "Celles-ci n'apparaissent pas sur votre profil. N'importe qui ayant leur lien peut les découvrir.", + "collections.unlisted_collections_with_count": "Collections non listées ({count})", "collections.view_collection": "Voir la collection", "collections.view_other_collections_by_user": "Voir les autres collections par ce compte", "collections.visibility_public": "Publique", @@ -500,9 +500,6 @@ "confirmations.discard_draft.post.title": "Abandonner votre brouillon ?", "confirmations.discard_edit_media.confirm": "Rejeter", "confirmations.discard_edit_media.message": "Vous avez des modifications non enregistrées de la description ou de l'aperçu du média, voulez-vous quand même les supprimer?", - "confirmations.follow_to_collection.confirm": "Suivre et ajouter à la collection", - "confirmations.follow_to_collection.message": "Vous devez suivre {name} pour l'ajouter à une collection.", - "confirmations.follow_to_collection.title": "Suivre le compte ?", "confirmations.follow_to_list.confirm": "Suivre et ajouter à la liste", "confirmations.follow_to_list.message": "Vous devez suivre {name} pour l'ajouter à une liste.", "confirmations.follow_to_list.title": "Suivre l'utilisateur·rice ?", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 55fff4f338..e6e6c8145e 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -359,8 +359,8 @@ "collection.share_template_other": "Découvrez cette collection incroyable : {link}", "collection.share_template_own": "Découvrez ma nouvelle collection : {link}", "collections.account_count": "{count, plural, one {# compte} other {# comptes}}", - "collections.accounts.empty_description": "Ajouter jusqu'à {count} comptes que vous suivez", "collections.accounts.empty_title": "Cette collection est vide", + "collections.block_collection_owner": "Bloquer le compte", "collections.by_account": "par {account_handle}", "collections.collection_description": "Description", "collections.collection_language": "Langue", @@ -370,7 +370,8 @@ "collections.confirm_account_removal": "Voulez-vous vraiment supprimer ce compte de la collection ?", "collections.content_warning": "Avertissement au public", "collections.continue": "Continuer", - "collections.create.accounts_subtitle": "Seuls les comptes que vous suivez et qui ont autorisé leur découverte peuvent être ajoutés.", + "collections.copy_link": "Copier le lien", + "collections.copy_link_confirmation": "Lien de la collection copié dans le presse-papiers", "collections.create.accounts_title": "Qui voulez-vous mettre en avant dans cette collection ?", "collections.create.basic_details_title": "Informations générales", "collections.create.steps": "Étape {step}/{total}", @@ -390,7 +391,6 @@ "collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.", "collections.hidden_accounts_description": "Vous avez bloqué ou masqué {count, plural, one {ce compte} other {ces comptes}}", "collections.hidden_accounts_link": "{count, plural, one {# compte caché} other {# comptes cachés}}", - "collections.hints.accounts_counter": "{count} / {max} comptes", "collections.last_updated_at": "Dernière mise à jour : {date}", "collections.manage_accounts": "Gérer les comptes", "collections.mark_as_sensitive": "Marquer comme sensible", @@ -398,17 +398,17 @@ "collections.name_length_hint": "Maximum 40 caractères", "collections.new_collection": "Nouvelle collection", "collections.no_collections_yet": "Aucune collection pour le moment.", - "collections.old_last_post_note": "Dernier message il y a plus d'une semaine", - "collections.remove_account": "Supprimer ce compte", "collections.report_collection": "Signaler cette collection", "collections.revoke_collection_inclusion": "Me retirer de cette collection", "collections.revoke_inclusion.confirmation": "Vous avez été retiré·e de « {collection} »", "collections.revoke_inclusion.error": "Une erreur s'est produite, veuillez réessayer plus tard.", - "collections.search_accounts_label": "Chercher des comptes à ajouter…", "collections.search_accounts_max_reached": "Vous avez ajouté le nombre maximum de comptes", "collections.sensitive": "Sensible", + "collections.share_short": "Partager", "collections.topic_hint": "Ajouter un hashtag pour aider les autres personnes à comprendre le sujet de la collection.", "collections.topic_special_chars_hint": "Les caractères spéciaux seront supprimés lors de l'enregistrement", + "collections.unlisted_collections_description": "Celles-ci n'apparaissent pas sur votre profil. N'importe qui ayant leur lien peut les découvrir.", + "collections.unlisted_collections_with_count": "Collections non listées ({count})", "collections.view_collection": "Voir la collection", "collections.view_other_collections_by_user": "Voir les autres collections par ce compte", "collections.visibility_public": "Publique", @@ -500,9 +500,6 @@ "confirmations.discard_draft.post.title": "Abandonner votre brouillon ?", "confirmations.discard_edit_media.confirm": "Supprimer", "confirmations.discard_edit_media.message": "Vous avez des modifications non enregistrées de la description ou de l'aperçu du média. Voulez-vous les supprimer ?", - "confirmations.follow_to_collection.confirm": "Suivre et ajouter à la collection", - "confirmations.follow_to_collection.message": "Vous devez suivre {name} pour l'ajouter à une collection.", - "confirmations.follow_to_collection.title": "Suivre le compte ?", "confirmations.follow_to_list.confirm": "Suivre et ajouter à la liste", "confirmations.follow_to_list.message": "Vous devez suivre {name} pour l'ajouter à une liste.", "confirmations.follow_to_list.title": "Suivre l'utilisateur·rice ?", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index cd307a897d..3a7382f704 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -359,8 +359,8 @@ "collection.share_template_other": "Féach ar an mbailiúchán fionnuar seo: {link}", "collection.share_template_own": "Féach ar mo bhailiúchán nua: {link}", "collections.account_count": "{count, plural, one {# cuntas} two {# cuntais} few {# cuntais} many {# cuntais} other {# cuntais}}", - "collections.accounts.empty_description": "Cuir suas le {count} cuntas leis a leanann tú", "collections.accounts.empty_title": "Tá an bailiúchán seo folamh", + "collections.block_collection_owner": "Cuntas blocáilte", "collections.by_account": "le {account_handle}", "collections.collection_description": "Cur síos", "collections.collection_language": "Teanga", @@ -370,7 +370,8 @@ "collections.confirm_account_removal": "An bhfuil tú cinnte gur mian leat an cuntas seo a bhaint den bhailiúchán seo?", "collections.content_warning": "Rabhadh ábhair", "collections.continue": "Lean ar aghaidh", - "collections.create.accounts_subtitle": "Ní féidir ach cuntais a leanann tú atá roghnaithe le fionnachtain a chur leis.", + "collections.copy_link": "Cóipeáil nasc", + "collections.copy_link_confirmation": "Cóipeáladh nasc an bhailiúcháin chuig an ghearrthaisce", "collections.create.accounts_title": "Cé a bheidh le feiceáil agat sa bhailiúchán seo?", "collections.create.basic_details_title": "Sonraí bunúsacha", "collections.create.steps": "Céim {step}/{total}", @@ -390,7 +391,6 @@ "collections.error_loading_collections": "Tharla earráid agus iarracht á déanamh do bhailiúcháin a luchtú.", "collections.hidden_accounts_description": "Tá bac nó múchadh déanta agat ar {count, plural, one {an t-úsáideoir seo} two {na húsáideoirí seo} few {na húsáideoirí seo} many {na húsáideoirí seo} other {na húsáideoirí seo}}", "collections.hidden_accounts_link": "{count, plural, one {# cuntas folaithe} two {# cuntais folaithe} few {# cuntais folaithe} many {# cuntais folaithe} other {# cuntais folaithe}}", - "collections.hints.accounts_counter": "{count} / {max} cuntais", "collections.last_updated_at": "Nuashonraithe go deireanach: {date}", "collections.manage_accounts": "Bainistigh cuntais", "collections.mark_as_sensitive": "Marcáil mar íogair", @@ -398,15 +398,13 @@ "collections.name_length_hint": "Teorainn 40 carachtar", "collections.new_collection": "Bailiúchán nua", "collections.no_collections_yet": "Gan aon bhailiúcháin fós.", - "collections.old_last_post_note": "Postáilte go deireanach breis agus seachtain ó shin", - "collections.remove_account": "Bain an cuntas seo", "collections.report_collection": "Tuairiscigh an bailiúchán seo", "collections.revoke_collection_inclusion": "Bain mé féin as an mbailiúchán seo", "collections.revoke_inclusion.confirmation": "Baineadh as \"{collection}\" thú", "collections.revoke_inclusion.error": "Tharla earráid, déan iarracht arís ar ball.", - "collections.search_accounts_label": "Cuardaigh cuntais le cur leis…", "collections.search_accounts_max_reached": "Tá an líon uasta cuntas curtha leis agat", "collections.sensitive": "Íogair", + "collections.share_short": "Comhroinn", "collections.topic_hint": "Cuir haischlib leis a chabhraíonn le daoine eile príomhábhar an bhailiúcháin seo a thuiscint.", "collections.topic_special_chars_hint": "Bainfear carachtair speisialta agus tú ag sábháil", "collections.unlisted_collections_description": "Ní fheictear iad seo ar do phróifíl do dhaoine eile. Is féidir le duine ar bith a bhfuil an nasc aige iad a fháil amach.", @@ -502,9 +500,6 @@ "confirmations.discard_draft.post.title": "An bhfuil tú ag iarraidh do dhréachtphost a chaitheamh amach?", "confirmations.discard_edit_media.confirm": "Faigh réidh de", "confirmations.discard_edit_media.message": "Tá athruithe neamhshlánaithe don tuarascáil gné nó réamhamharc agat, faigh réidh dóibh ar aon nós?", - "confirmations.follow_to_collection.confirm": "Lean agus cuir leis an mbailiúchán", - "confirmations.follow_to_collection.message": "Ní mór duit a bheith ag leanúint {name} le go gcuirfidh tú iad le bailiúchán.", - "confirmations.follow_to_collection.title": "Lean an cuntas?", "confirmations.follow_to_list.confirm": "Lean agus cuir leis an liosta", "confirmations.follow_to_list.message": "Ní mór duit {name} a leanúint chun iad a chur le liosta.", "confirmations.follow_to_list.title": "Lean an t-úsáideoir?", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index ffc0fc7d3f..a7a789898b 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -356,7 +356,6 @@ "collection.share_template_other": "Thoir sùil air an deagh-chruinneachadh seo: {link}", "collection.share_template_own": "Thoir sùil air a’ chruinneachadh ùr agam: {link}", "collections.account_count": "{count, plural, one {# chunntas} two {# chunntas} few {# cunntasan} other {# cunntas}}", - "collections.accounts.empty_description": "Cuir ris suas ri {count} cunntas(an) a tha thu a’ leantainn", "collections.accounts.empty_title": "Tha an an cruinneachadh seo falamh", "collections.by_account": "le {account_handle}", "collections.collection_description": "Tuairisgeul", @@ -367,7 +366,6 @@ "collections.confirm_account_removal": "A bheil thu cinnteach gu bheil thu airson an cunntas seo a thoirt air falbh on chruinneachadh seo?", "collections.content_warning": "Rabhadh susbainte", "collections.continue": "Lean air adhart", - "collections.create.accounts_subtitle": "Chan urrainn dhut cur ris ach cunntasan a leanas tu ’s a ghabh ri rùrachadh.", "collections.create.accounts_title": "Cò bhrosnaicheas tu sa chruinneachadh seo?", "collections.create.basic_details_title": "Bun-fhiosrachadh", "collections.create.steps": "Ceum {step}/{total}", @@ -385,7 +383,6 @@ "collections.detail.you_are_in_this_collection": "Thathar do bhrosnachadh sa chruinneachadh seo", "collections.edit_details": "Deasaich am fiosrachadh", "collections.error_loading_collections": "Thachair mearachd nuair a dh’fheuch sinn ris a’ chruinneachaidhean agad a luchdadh.", - "collections.hints.accounts_counter": "{count} / {max} cunntas(an)", "collections.last_updated_at": "An tùrachadh mu dheireadh: {date}", "collections.manage_accounts": "Stiùirich na cunntasan", "collections.mark_as_sensitive": "Cuir comharra gu bheil e frionasach", @@ -393,13 +390,10 @@ "collections.name_length_hint": "Crìoch de 40 caractar", "collections.new_collection": "Cruinneachadh ùr", "collections.no_collections_yet": "Chan eil cruinneachadh agad fhathast.", - "collections.old_last_post_note": "Tha am post mu dheireadh còrr is seachdain air ais", - "collections.remove_account": "Thoir air falbh an cunntas seo", "collections.report_collection": "Dèan gearan mun chruinneachadh seo", "collections.revoke_collection_inclusion": "Thoir mi fhìn air falbh on chruinneachadh seo", "collections.revoke_inclusion.confirmation": "Chaidh do thoirt air falbh o “{collection}”", "collections.revoke_inclusion.error": "Thachair mearachd. Feuch ris a-rithist an ceann greis.", - "collections.search_accounts_label": "Lorg cunntasan gus an cur ris…", "collections.search_accounts_max_reached": "Chuir thu na tha ceadaichte de chunntasan ris", "collections.sensitive": "Frionasach", "collections.topic_hint": "Cuir taga hais ris a chuidicheas càch le tuigse prìomh-chuspair a’ chruinneachaidh seo.", @@ -493,9 +487,6 @@ "confirmations.discard_draft.post.title": "A bheil thu airson dreachd a’ phuist agad a thilgeil air falbh?", "confirmations.discard_edit_media.confirm": "Tilg air falbh", "confirmations.discard_edit_media.message": "Tha atharraichean gun sàbhaladh agad ann an tuairisgeul no ro-shealladh a’ mheadhain, a bheil thu airson an tilgeil air falbh co-dhiù?", - "confirmations.follow_to_collection.confirm": "Lean ’s cuir ri cruinneachadh", - "confirmations.follow_to_collection.message": "Feumaidh tu {name} a leantainn mus cuir thu ri cruinneachadh iad.", - "confirmations.follow_to_collection.title": "A bheil thu airson an cunntas a leantainn?", "confirmations.follow_to_list.confirm": "Lean ’s cuir ris an liosta", "confirmations.follow_to_list.message": "Feumaidh tu {name} a leantainn ron chur ri liosta.", "confirmations.follow_to_list.title": "A bheil thu airson an cleachdaiche a leantainn?", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index a0a2bc9f75..a8adb8953f 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Mira que colección máis boa: {link}", "collection.share_template_own": "Mira a miña nova colección: {link}", "collections.account_count": "{count, plural, one {# conta} other {# contas}}", - "collections.accounts.empty_description": "Engade ata {count} contas que segues", + "collections.accounts.empty_description": "Engade ate {count} contas", + "collections.accounts.empty_editor_title": "Aínda non hai ninguén nesta colección", "collections.accounts.empty_title": "A colección está baleira", "collections.block_collection_owner": "Bloquear conta", "collections.by_account": "de {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Continuar", "collections.copy_link": "Copiar ligazón", "collections.copy_link_confirmation": "Copiouse ao portapapeis a ligazón á colección", - "collections.create.accounts_subtitle": "Só se poden engadir contas que segues e que optaron por ser incluídas en descubrir.", "collections.create.accounts_title": "A quen queres incluír nesta colección?", "collections.create.basic_details_title": "Detalles básicos", "collections.create.steps": "Paso {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Houbo un erro ao intentar cargar as túas coleccións.", "collections.hidden_accounts_description": "Bloqueaches ou silenciaches {count, plural, one {esta conta} other {estas contas}}", "collections.hidden_accounts_link": "{count, plural, one {# conta oculta} other {# contas ocultas}}", - "collections.hints.accounts_counter": "{count} / {max} contas", + "collections.hints.accounts_counter": "{count}/{max} contas", "collections.last_updated_at": "Última actualización: {date}", "collections.manage_accounts": "Xestionar contas", "collections.mark_as_sensitive": "Marcar como sensible", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Límite de 40 caracteres", "collections.new_collection": "Nova colección", "collections.no_collections_yet": "Aínda non tes coleccións.", - "collections.old_last_post_note": "Hai máis dunha semana da última publicación", - "collections.remove_account": "Retirar esta conta", + "collections.remove_account": "Retirar", "collections.report_collection": "Denunciar esta colección", "collections.revoke_collection_inclusion": "Sácame desta colección", "collections.revoke_inclusion.confirmation": "Quitámoste da colección \"{collection}\"", "collections.revoke_inclusion.error": "Algo fallou, inténtao outra vez máis tarde.", - "collections.search_accounts_label": "Buscar contas para engadir…", + "collections.search_accounts_label": "Busca contas para engadilas", "collections.search_accounts_max_reached": "Acadaches o máximo de contas permitidas", "collections.sensitive": "Sensible", "collections.share_short": "Compartir", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "Desbotar o borrador?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Tes cambios sen gardar para a vista previa ou descrición do multimedia, descartamos os cambios?", - "confirmations.follow_to_collection.confirm": "Seguir e engadir á colección", - "confirmations.follow_to_collection.message": "Tes que seguir a {name} para poder engadila a unha colección.", - "confirmations.follow_to_collection.title": "Seguir a conta?", "confirmations.follow_to_list.confirm": "Seguir e engadir á lista", "confirmations.follow_to_list.message": "Tes que seguir a {name} para poder engadila a unha lista.", "confirmations.follow_to_list.title": "Seguir á usuaria?", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index d41324b495..2319a6381e 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -359,8 +359,8 @@ "collection.share_template_other": "הציצו על האוסף המעניין הזה: {link}", "collection.share_template_own": "הציצו על האוסף החדש שלי: {link}", "collections.account_count": "{count, plural, one {חשבון אחד} other {# חשבונות}}", - "collections.accounts.empty_description": "להוסיף עד ל־{count} חשבונות שאתם עוקבים אחריהם", "collections.accounts.empty_title": "האוסף הזה ריק", + "collections.block_collection_owner": "חסימת חשבון", "collections.by_account": "מאת {account_handle}", "collections.collection_description": "תיאור", "collections.collection_language": "שפה", @@ -370,7 +370,8 @@ "collections.confirm_account_removal": "בוודאות להסיר חשבון זה מהאוסף?", "collections.content_warning": "אזהרת תוכן", "collections.continue": "המשך", - "collections.create.accounts_subtitle": "רק חשבונות נעקבים שבחרו להופיע ב\"תגליות\" ניתנים להוספה.", + "collections.copy_link": "להעתקת הקישור", + "collections.copy_link_confirmation": "קישור לחשבון הועתק לקליפבורד", "collections.create.accounts_title": "את מי תבליטו באוסף זה?", "collections.create.basic_details_title": "פרטים בסיסיים", "collections.create.steps": "צעד {step} מתוך {total}", @@ -390,7 +391,6 @@ "collections.error_loading_collections": "חלה שגיאה בנסיון לטעון את אוספיך.", "collections.hidden_accounts_description": "חסמת או השתקת {count, plural,one {המשתמש.ת}other {המשתמשים}} האלו", "collections.hidden_accounts_link": "{count, plural, one {חשבון מוסתר אחד} other {# חשבונות מוסתרים}}", - "collections.hints.accounts_counter": "{count} \\ {max} חשבונות", "collections.last_updated_at": "עדכון אחרון: {date}", "collections.manage_accounts": "ניהול חשבונות", "collections.mark_as_sensitive": "מסומנים כרגישים", @@ -398,17 +398,17 @@ "collections.name_length_hint": "מגבלה של 40 תווים", "collections.new_collection": "אוסף חדש", "collections.no_collections_yet": "עוד אין אוספים.", - "collections.old_last_post_note": "פרסמו לאחרונה לפני יותר משבוע", - "collections.remove_account": "הסר חשבון זה", "collections.report_collection": "דיווח על אוסף זה", "collections.revoke_collection_inclusion": "הסירוני מאוסף זה", "collections.revoke_inclusion.confirmation": "הוסרת מֿ\"{collection}\"", "collections.revoke_inclusion.error": "הייתה שגיאה. נסו שוב מאוחר יותר.", - "collections.search_accounts_label": "לחפש חשבונות להוספה…", "collections.search_accounts_max_reached": "הגעת למספר החשבונות המירבי", "collections.sensitive": "רגיש", + "collections.share_short": "שיתוף", "collections.topic_hint": "הוספת תגית שמסייעת לאחרים להבין את הנושא הראשי של האוסף.", "collections.topic_special_chars_hint": "תווים מיוחדים יוסרו בעת השמירה", + "collections.unlisted_collections_description": "אוספים אלו לא מוצגים לאחרים בפרופיל שלך. למי שיקבל את הקישור תהיה גישה לגלות את התכנים.", + "collections.unlisted_collections_with_count": "אוספים שאינם לתצוגה ({count})", "collections.view_collection": "צפיה באוסף", "collections.view_other_collections_by_user": "צפייה באוספים אחרים של משתמש.ת אלו", "collections.visibility_public": "פומבי", @@ -500,9 +500,6 @@ "confirmations.discard_draft.post.title": "לוותר על הטיוטא?", "confirmations.discard_edit_media.confirm": "השלך", "confirmations.discard_edit_media.message": "יש לך שינויים לא שמורים לתיאור המדיה. להשליך אותם בכל זאת?", - "confirmations.follow_to_collection.confirm": "עקיבה והוספה לאוסף", - "confirmations.follow_to_collection.message": "כדי להכניס את {name} לאוסף, ראשית יש לעקוב אחריהם.", - "confirmations.follow_to_collection.title": "לעקוב אחר החשבון?", "confirmations.follow_to_list.confirm": "עקיבה והוספה לרשימה", "confirmations.follow_to_list.message": "כדי להכניס את {name} לרשימה, ראשית יש לעקוב אחריהם.", "confirmations.follow_to_list.title": "לעקוב אחר המשתמש.ת?", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 6b9926de7a..f6d6281cbf 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -358,7 +358,6 @@ "collection.share_template_other": "Nézd meg ezt a gyűjteményt: {link}", "collection.share_template_own": "Nézd meg az új gyűjteményemet: {link}", "collections.account_count": "{count, plural, one {# fiók} other {# fiók}}", - "collections.accounts.empty_description": "Adj hozzá legfeljebb {count} követett fiókot", "collections.accounts.empty_title": "Ez a gyűjtemény üres", "collections.by_account": "szerző: {account_handle}", "collections.collection_description": "Leírás", @@ -369,7 +368,6 @@ "collections.confirm_account_removal": "Biztos, hogy eltávolítod ezt a fiókot ebből a gyűjteményből?", "collections.content_warning": "Tartalmi figyelmeztetés", "collections.continue": "Folytatás", - "collections.create.accounts_subtitle": "Csak azok a követett fiókok adhatóak hozzá, melyek engedélyezték a felfedezést.", "collections.create.accounts_title": "Kit emelsz ki ebben a gyűjteményben?", "collections.create.basic_details_title": "Alapvető részletek", "collections.create.steps": "{step}. lépés/{total}", @@ -389,7 +387,6 @@ "collections.error_loading_collections": "Hiba történt a gyűjtemények betöltése során.", "collections.hidden_accounts_description": "Blokkoltad vagy letiltottad {count, plural, one {ezt a felhasználót} other {ezeket a felhasználókat}}", "collections.hidden_accounts_link": "{count, plural, one {# rejtett fiók} other {# rejtett fiók}}", - "collections.hints.accounts_counter": "{count} / {max} fiók", "collections.last_updated_at": "Utoljára frissítve: {date}", "collections.manage_accounts": "Fiókok kezelése", "collections.mark_as_sensitive": "Megjelelölés érzénykenként", @@ -397,13 +394,10 @@ "collections.name_length_hint": "40 karakteres korlát", "collections.new_collection": "Új gyűjtemény", "collections.no_collections_yet": "Még nincsenek gyűjtemények.", - "collections.old_last_post_note": "Egy hete osztott meg legutóbb", - "collections.remove_account": "Fiók eltávolítása", "collections.report_collection": "Gyűjtemény jelentése", "collections.revoke_collection_inclusion": "Saját magam eltávolítása ebből a gyűjteményből", "collections.revoke_inclusion.confirmation": "El lettél távolítva innen: „{collection}”", "collections.revoke_inclusion.error": "Hiba történt, próbáld újra később.", - "collections.search_accounts_label": "Hozzáadandó fiókok keresése…", "collections.search_accounts_max_reached": "Elérte a hozzáadott fiókok maximális számát", "collections.sensitive": "Érzékeny", "collections.topic_hint": "Egy hashtag hozzáadása segít másoknak abban, hogy megértsék a gyűjtemény fő témáját.", @@ -499,9 +493,6 @@ "confirmations.discard_draft.post.title": "Elveted a piszkozatot?", "confirmations.discard_edit_media.confirm": "Elvetés", "confirmations.discard_edit_media.message": "Mentetlen változtatásaid vannak a média leírásában vagy előnézetében, mindenképp elveted?", - "confirmations.follow_to_collection.confirm": "Követés, és gyűjteményhez adás", - "confirmations.follow_to_collection.message": "Követned kell {name} felhasználót, hogy hozzáadhasd egy gyűjteményhez.", - "confirmations.follow_to_collection.title": "Fiók követése?", "confirmations.follow_to_list.confirm": "Követés, és hozzáadás a listához", "confirmations.follow_to_list.message": "Követned kell {name} felhasználót, hogy hozzáadhasd a listához.", "confirmations.follow_to_list.title": "Felhasználó követése?", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 2150311462..5287814001 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Kíktu á þetta áhugaverða safn: {link}", "collection.share_template_own": "Kíktu á nýja safnið mitt: {link}", "collections.account_count": "{count, plural, one {# aðgangur} other {# aðgangar}}", - "collections.accounts.empty_description": "Bættu við allt að {count} aðgöngum sem þú fylgist með", + "collections.accounts.empty_description": "Bættu við allt að {count} aðgöngum", + "collections.accounts.empty_editor_title": "Enginn er enn í þessu safni", "collections.accounts.empty_title": "Þetta safn er tómt", "collections.block_collection_owner": "Útiloka notandaaðgang", "collections.by_account": "frá {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Halda áfram", "collections.copy_link": "Afrita tengil", "collections.copy_link_confirmation": "Afritaði tengil á safn á klippispjald", - "collections.create.accounts_subtitle": "Einungis er hægt að bæta við notendum sem hafa samþykkt að vera með í opinberri birtingu.", "collections.create.accounts_title": "Hvern vilt þú gera áberandi í þessu safni?", "collections.create.basic_details_title": "Grunnupplýsingar", "collections.create.steps": "Skref {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Villa kom upp þegar reynt var að hlaða inn söfnunum þínum.", "collections.hidden_accounts_description": "Þú hefur lokað á eða þaggað niður í {count, plural, one {þessum notanda} other {þessum notendum}}", "collections.hidden_accounts_link": "{count, plural, one {# falinn aðgangur} other {# faldir aðgangar}}", - "collections.hints.accounts_counter": "{count} / {max} aðgangar", + "collections.hints.accounts_counter": "{count}/{max} aðgöngum", "collections.last_updated_at": "Síðast uppfært: {date}", "collections.manage_accounts": "Sýsla með notandaaðganga", "collections.mark_as_sensitive": "Merkja sem viðkvæmt", @@ -401,13 +401,12 @@ "collections.name_length_hint": "40 stafa takmörk", "collections.new_collection": "Nýtt safn", "collections.no_collections_yet": "Engin söfn ennþá.", - "collections.old_last_post_note": "Birti síðast fyrir meira en viku síðan", - "collections.remove_account": "Fjarlægja þennan aðgang", + "collections.remove_account": "Fjarlægja", "collections.report_collection": "Kæra þetta safn", "collections.revoke_collection_inclusion": "Fjarlægja mig úr þessu safni", "collections.revoke_inclusion.confirmation": "Þú varst fjarlægð/ur úr \"{collection}\"", "collections.revoke_inclusion.error": "Upp kom villa, reyndu aftur síðar.", - "collections.search_accounts_label": "Leita að aðgöngum til að bæta við…", + "collections.search_accounts_label": "Leitaðu að aðgangi til að bæta við", "collections.search_accounts_max_reached": "Þú hefur þegar bætt við leyfilegum hámarksfjölda aðganga", "collections.sensitive": "Viðkvæmt", "collections.share_short": "Deila", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "Henda drögum að færslunni þinni?", "confirmations.discard_edit_media.confirm": "Henda", "confirmations.discard_edit_media.message": "Þú ert með óvistaðar breytingar á lýsingu myndefnis eða forskoðunar, henda þeim samt?", - "confirmations.follow_to_collection.confirm": "Fylgjast með og bæta í safn", - "confirmations.follow_to_collection.message": "Þú þarft að fylgjast með {name} til að geta bætt viðkomandi í safn.", - "confirmations.follow_to_collection.title": "Fylgjast með notandaaðgangnum?", "confirmations.follow_to_list.confirm": "Fylgjast með og bæta á lista", "confirmations.follow_to_list.message": "Þú þarft að fylgjast með {name} til að bæta viðkomandi á lista.", "confirmations.follow_to_list.title": "Fylgjast með notanda?", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index fda06fdb1a..4bfcee28ca 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -359,7 +359,6 @@ "collection.share_template_other": "Dai un'occhiata a questa fantastica collezione: {link}", "collection.share_template_own": "Dai un'occhiata alla mia collezione: {link}", "collections.account_count": "{count, plural, one {# account} other {# account}}", - "collections.accounts.empty_description": "Aggiungi fino a {count} account che segui", "collections.accounts.empty_title": "Questa collezione è vuota", "collections.block_collection_owner": "Blocca l'account", "collections.by_account": "di {account_handle}", @@ -373,7 +372,6 @@ "collections.continue": "Continua", "collections.copy_link": "Copia il сollegamento", "collections.copy_link_confirmation": "Collegamento della collezione copiato negli appunti", - "collections.create.accounts_subtitle": "Possono essere aggiunti solo gli account che segui e che hanno aderito alla funzione di scoperta.", "collections.create.accounts_title": "Chi includerai in questa collezione?", "collections.create.basic_details_title": "Dettagli di base", "collections.create.steps": "Step {step}/{total}", @@ -393,7 +391,6 @@ "collections.error_loading_collections": "Si è verificato un errore durante il tentativo di caricare le tue collezioni.", "collections.hidden_accounts_description": "Hai bloccato o silenziato {count, plural, one {questo utente} other {questi utenti}}", "collections.hidden_accounts_link": "{count, plural, one {# account nascosto} other {# account nascosti}}", - "collections.hints.accounts_counter": "{count} / {max} account", "collections.last_updated_at": "Ultimo aggiornamento: {date}", "collections.manage_accounts": "Gestisci account", "collections.mark_as_sensitive": "Segna come sensibile", @@ -401,13 +398,10 @@ "collections.name_length_hint": "Limite di 40 caratteri", "collections.new_collection": "Nuova collezione", "collections.no_collections_yet": "Nessuna collezione ancora.", - "collections.old_last_post_note": "Ultimo post più di una settimana fa", - "collections.remove_account": "Rimuovi questo account", "collections.report_collection": "Segnala questa collezione", "collections.revoke_collection_inclusion": "Rimuovimi da questa collezione", "collections.revoke_inclusion.confirmation": "Sei stato/a rimosso/a da \"{collection}\"", "collections.revoke_inclusion.error": "Si è verificato un errore, si prega di riprovare più tardi.", - "collections.search_accounts_label": "Cerca account da aggiungere…", "collections.search_accounts_max_reached": "Hai aggiunto il numero massimo di account", "collections.sensitive": "Sensibile", "collections.share_short": "Condividi", @@ -506,9 +500,6 @@ "confirmations.discard_draft.post.title": "Scartare la tua bozza del post?", "confirmations.discard_edit_media.confirm": "Scarta", "confirmations.discard_edit_media.message": "Hai delle modifiche non salvate alla descrizione o anteprima del media, scartarle comunque?", - "confirmations.follow_to_collection.confirm": "Segui e aggiungi alla collezione", - "confirmations.follow_to_collection.message": "Devi seguire {name} per aggiungerlo/a ad una collezione.", - "confirmations.follow_to_collection.title": "Seguire l'account?", "confirmations.follow_to_list.confirm": "Segui e aggiungi alla lista", "confirmations.follow_to_list.message": "Devi seguire {name} per aggiungerli a una lista.", "confirmations.follow_to_list.title": "Seguire l'utente?", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index ac63875904..0d6c7fbf7c 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -191,7 +191,6 @@ "collection.share_modal.title_new": "新しいコレクションを共有しよう!", "collection.share_template_other": "この素敵なコレクションを見てみてください: {link}", "collection.share_template_own": "私の新しいコレクションを見てみてください: {link}", - "collections.accounts.empty_description": "フォローしている {count} アカウントまで追加できます", "collections.accounts.empty_title": "このコレクションは空です", "collections.by_account": "{account_handle} による", "collections.collection_description": "詳細", @@ -201,7 +200,6 @@ "collections.confirm_account_removal": "このコレクションからこのアカウントを削除してもよろしいですか?", "collections.content_warning": "閲覧注意", "collections.continue": "次に進む", - "collections.create.accounts_subtitle": "あなたがフォローしていてディスカバリー機能にオプトインしたアカウントのみを追加することができます。", "collections.create.accounts_title": "このコレクションで誰に注目しますか?", "collections.create.basic_details_title": "基本情報", "collections.create.steps": "ステップ {step}/{total}", @@ -214,15 +212,12 @@ "collections.detail.loading": "コレクションを読み込み中…", "collections.detail.share": "コレクションを共有", "collections.detail.you_are_in_this_collection": "あなたはこのコレクションで紹介されています", - "collections.hints.accounts_counter": "{count} / {max} アカウント", "collections.mark_as_sensitive": "閲覧注意としてマーク", "collections.mark_as_sensitive_hint": "コレクションの説明とアカウントを閲覧注意で隠します。コレクション名は表示されます。", "collections.name_length_hint": "40 文字まで", "collections.new_collection": "新規のコレクション", "collections.no_collections_yet": "コレクションはまだありません。", - "collections.remove_account": "このアカウントを削除する", "collections.report_collection": "このコレクションを通報する", - "collections.search_accounts_label": "追加するアカウントを探す…", "collections.topic_hint": "ハッシュタグを追加して、このコレクションの主なトピックを知ってもらいましょう。", "collections.visibility_public": "公開", "collections.visibility_public_hint": "検索結果やその他おすすめに表示されて、見つけてもらうことができます。", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index 74e957bf2a..1bd1bc8c88 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -259,15 +259,12 @@ "collections.detail.sensitive_content": "Agbur amḥulfu", "collections.detail.share": "Zuzer talkensit-a", "collections.edit_details": "Ẓreg talqayt", - "collections.hints.accounts_counter": "{count} / {max} n yimiḍanen", "collections.manage_accounts": "Sefrek imiḍanen", "collections.name_length_hint": "talast n 40 n yisekkilen", "collections.new_collection": "Talkensit tamaynut", "collections.no_collections_yet": "Ur ɛad llant tilkensa.", - "collections.remove_account": "Kkes amiḍan-a", "collections.report_collection": "Cetki ɣef telkensit-a", "collections.revoke_collection_inclusion": "Kkes-iyi seg telkensit-a", - "collections.search_accounts_label": "Nadi ɣef imiḍanen ara ternuḍ…", "collections.view_collection": "Wali talkensit", "collections.view_other_collections_by_user": "Wali tilkensa nniḍen sɣur useqdac-a", "collections.visibility_public": "Azayaz", @@ -346,8 +343,6 @@ "confirmations.discard_draft.post.cancel": "Tuɣalin ar urewway", "confirmations.discard_draft.post.title": "Deggeṛ arewway n yizen?", "confirmations.discard_edit_media.confirm": "Sefsex", - "confirmations.follow_to_collection.confirm": "Ḍfeṛ-it sakin rnu-t ɣer telkensit", - "confirmations.follow_to_collection.title": "Ad tefreḍ amiḍan-a?", "confirmations.follow_to_list.confirm": "Ḍfeṛ-it sakin rnu-t ɣer tebdart", "confirmations.follow_to_list.title": "Ḍfer aseqdac?", "confirmations.hide_featured_tab.confirm": "Ffer accer", diff --git a/app/javascript/mastodon/locales/nan-TW.json b/app/javascript/mastodon/locales/nan-TW.json index 1406d80f90..454e004432 100644 --- a/app/javascript/mastodon/locales/nan-TW.json +++ b/app/javascript/mastodon/locales/nan-TW.json @@ -358,7 +358,6 @@ "collection.share_template_other": "緊看覓chit ê時行ê收藏:{link}", "collection.share_template_own": "緊看覓我ê收藏:{link}", "collections.account_count": "{count, plural, other {# ê口座}}", - "collections.accounts.empty_description": "加lí跟tuè ê口座,上tsē {count} ê", "collections.accounts.empty_title": "收藏內底無半項", "collections.by_account": "tuì {account_handle}", "collections.collection_description": "說明", @@ -369,7 +368,6 @@ "collections.confirm_account_removal": "Lí確定beh對收藏suá掉tsit ê口座?", "collections.content_warning": "內容警告", "collections.continue": "繼續", - "collections.create.accounts_subtitle": "Kan-ta通加lí所綴而且選擇加入探索ê。", "collections.create.accounts_title": "Lí想beh佇收藏內底予siáng標做特色ê?", "collections.create.basic_details_title": "基本ê資料", "collections.create.steps": "第 {step} 步,總共 {total} 步", @@ -387,7 +385,6 @@ "collections.detail.you_are_in_this_collection": "Lí已經hőng加kàu tsit ê收藏", "collections.edit_details": "編輯詳細", "collections.error_loading_collections": "佇載入lí ê收藏ê時陣出tshê。", - "collections.hints.accounts_counter": "{count} / {max} ê口座", "collections.last_updated_at": "上尾更新tī:{date}", "collections.manage_accounts": "管理口座", "collections.mark_as_sensitive": "標做敏感ê", @@ -395,13 +392,10 @@ "collections.name_length_hint": "限制 40 字", "collections.new_collection": "新ê收藏", "collections.no_collections_yet": "Iáu無收藏。", - "collections.old_last_post_note": "頂改佇超過一禮拜進前PO文", - "collections.remove_account": "Suá掉tsit ê口座", "collections.report_collection": "檢舉tsit ê收藏", "collections.revoke_collection_inclusion": "Kā我對收藏內底suá掉", "collections.revoke_inclusion.confirmation": "Lí已經對「{collection}」hőng suá掉", "collections.revoke_inclusion.error": "出tshê ah,請小等leh koh試。", - "collections.search_accounts_label": "Tshuē口座來加添……", "collections.search_accounts_max_reached": "Lí已經加kàu口座數ê盡磅ah。", "collections.sensitive": "敏感ê", "collections.topic_hint": "加 hashtag,幫tsān別lâng了解tsit ê收藏ê主題。", @@ -497,9 +491,6 @@ "confirmations.discard_draft.post.title": "Kám beh棄sak lí PO文ê草稿?", "confirmations.discard_edit_media.confirm": "棄sak", "confirmations.discard_edit_media.message": "Lí佇媒體敘述á是先看māi ê所在有iáu buē儲存ê改變,kám beh kā in棄sak?", - "confirmations.follow_to_collection.confirm": "跟tuè,加入kàu收藏", - "confirmations.follow_to_collection.message": "Beh kā {name} 加添kàu收藏,lí tio̍h先跟tuè伊。", - "confirmations.follow_to_collection.title": "敢beh跟tuè口座?", "confirmations.follow_to_list.confirm": "跟tuè,加入kàu列單", "confirmations.follow_to_list.message": "Beh kā {name} 加添kàu列單,lí tio̍h先跟tuè伊。", "confirmations.follow_to_list.title": "Kám beh跟tuè tsit ê用者?", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index afdf82d6a1..9f1acc5684 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Bekijk deze coole verzameling: {link}", "collection.share_template_own": "Bekijk mijn nieuwe verzameling: {link}", "collections.account_count": "{count, plural, one {# account} other {# accounts}}", - "collections.accounts.empty_description": "Tot {count} accounts die je volgt toevoegen", + "collections.accounts.empty_description": "Tot {count} accounts toevoegen", + "collections.accounts.empty_editor_title": "Er is nog nog niemand in deze verzameling", "collections.accounts.empty_title": "Deze verzameling is leeg", "collections.block_collection_owner": "Account blokkeren", "collections.by_account": "door {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Doorgaan", "collections.copy_link": "Link kopiëren", "collections.copy_link_confirmation": "Verzamelinglink naar het klembord gekopieerd", - "collections.create.accounts_subtitle": "Alleen accounts die je volgt en ontdekt willen worden, kunnen worden toegevoegd.", "collections.create.accounts_title": "Wie wil je aan deze verzameling toevoegen?", "collections.create.basic_details_title": "Basisgegevens", "collections.create.steps": "Stap {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Er is een fout opgetreden bij het laden van je verzamelingen.", "collections.hidden_accounts_description": "Je hebt {count, plural, one {deze gebruiker} other {deze gebruikers}} geblokkeerd", "collections.hidden_accounts_link": "{count, plural, one {# verborgen account} other {# verborgen accounts}}", - "collections.hints.accounts_counter": "{count} / {max} accounts", + "collections.hints.accounts_counter": "{count}/{max} accounts", "collections.last_updated_at": "Laatst bijgewerkt: {date}", "collections.manage_accounts": "Accounts beheren", "collections.mark_as_sensitive": "Als gevoelig markeren", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Limiet van 40 tekens", "collections.new_collection": "Nieuwe verzameling", "collections.no_collections_yet": "Nog geen verzamelingen.", - "collections.old_last_post_note": "Meer dan een week geleden voor het laatst een bericht geplaatst", - "collections.remove_account": "Dit account verwijderen", + "collections.remove_account": "Verwijderen", "collections.report_collection": "Deze verzameling rapporteren", "collections.revoke_collection_inclusion": "Mezelf uit deze verzameling verwijderen", "collections.revoke_inclusion.confirmation": "Je bent uit \"{collection}\" verwijderd", "collections.revoke_inclusion.error": "Er is een fout opgetreden. Probeer het later opnieuw.", - "collections.search_accounts_label": "Naar accounts zoeken om toe te voegen…", + "collections.search_accounts_label": "Naar een account zoeken om toe te voegen", "collections.search_accounts_max_reached": "Je hebt het maximum aantal accounts toegevoegd", "collections.sensitive": "Gevoelig", "collections.share_short": "Delen", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "Conceptbericht verwijderen?", "confirmations.discard_edit_media.confirm": "Verwijderen", "confirmations.discard_edit_media.message": "Je hebt niet-opgeslagen wijzigingen in de mediabeschrijving of voorvertonning, wil je deze toch verwijderen?", - "confirmations.follow_to_collection.confirm": "Volgen en aan verzameling toevoegen", - "confirmations.follow_to_collection.message": "Je moet {name} volgen om dit account aan een verzameling toe te kunnen voegen.", - "confirmations.follow_to_collection.title": "Account volgen?", "confirmations.follow_to_list.confirm": "Volgen en toevoegen aan de lijst", "confirmations.follow_to_list.message": "Je moet {name} volgen om dit account aan een lijst toe te kunnen voegen.", "confirmations.follow_to_list.title": "Gebruiker volgen?", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 3f0833efcb..f6b0cd9255 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -356,7 +356,6 @@ "collection.share_template_other": "Sjekk denne samlinga: {link}", "collection.share_template_own": "Sjekk den nye samlinga mi: {link}", "collections.account_count": "{count, plural, one {# konto} other {# kontoar}}", - "collections.accounts.empty_description": "Legg til opp til {count} kontoar du fylgjer", "collections.accounts.empty_title": "Denne samlinga er tom", "collections.by_account": "av {account_handle}", "collections.collection_description": "Skildring", @@ -367,7 +366,6 @@ "collections.confirm_account_removal": "Er du sikker på at du vil fjerna denne brukarkontoen frå samlinga?", "collections.content_warning": "Innhaldsåtvaring", "collections.continue": "Hald fram", - "collections.create.accounts_subtitle": "Du kan berre leggja til kontoar du fylgjer og som har sagt ja til å bli oppdaga.", "collections.create.accounts_title": "Kven vil du leggja vekt på denne samlinga?", "collections.create.basic_details_title": "Grunnleggjande opplysingar", "collections.create.steps": "Steg {step}/{total}", @@ -385,7 +383,6 @@ "collections.detail.you_are_in_this_collection": "Du er framheva i denne samlinga", "collections.edit_details": "Rediger detaljar", "collections.error_loading_collections": "Noko gjekk gale då me prøvde å henta samlingane dine.", - "collections.hints.accounts_counter": "{count} av {max} kontoar", "collections.last_updated_at": "Sist oppdatert: {date}", "collections.manage_accounts": "Handter kontoar", "collections.mark_as_sensitive": "Merk som ømtolig", @@ -393,13 +390,10 @@ "collections.name_length_hint": "Maks 40 teikn", "collections.new_collection": "Ny samling", "collections.no_collections_yet": "Du har ingen samlingar enno.", - "collections.old_last_post_note": "Sist lagt ut for over ei veke sidan", - "collections.remove_account": "Fjern denne kontoen", "collections.report_collection": "Rapporter denne samlinga", "collections.revoke_collection_inclusion": "Fjern meg frå denne samlinga", "collections.revoke_inclusion.confirmation": "Du er fjerna frå «{collection}»", "collections.revoke_inclusion.error": "Noko gjekk gale, prøv att seinare.", - "collections.search_accounts_label": "Søk etter kontoar å leggja til…", "collections.search_accounts_max_reached": "Du har nådd grensa for kor mange kontoar du kan leggja til", "collections.sensitive": "Ømtolig", "collections.topic_hint": "Legg til ein emneknagg som hjelper andre å forstå hovudemnet for denne samlinga.", @@ -493,9 +487,6 @@ "confirmations.discard_draft.post.title": "Forkast kladden?", "confirmations.discard_edit_media.confirm": "Forkast", "confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkasta dei likevel?", - "confirmations.follow_to_collection.confirm": "Fylg og legg til samlinga", - "confirmations.follow_to_collection.message": "Du må fylgja {name} for å leggja dei til ei samling.", - "confirmations.follow_to_collection.title": "Fylg kontoen?", "confirmations.follow_to_list.confirm": "Fylg og legg til lista", "confirmations.follow_to_list.message": "Du må fylgja {name} for å leggja dei til ei liste.", "confirmations.follow_to_list.title": "Vil du fylgja brukaren?", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 981f50e79f..37128f1d8c 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -278,7 +278,6 @@ "confirmations.delete_list.title": "Slett liste?", "confirmations.discard_edit_media.confirm": "Forkast", "confirmations.discard_edit_media.message": "Du har ulagrede endringer i mediebeskrivelsen eller i forhåndsvisning, forkast dem likevel?", - "confirmations.follow_to_collection.title": "Følg konto?", "confirmations.follow_to_list.confirm": "Følg og legg til i liste", "confirmations.follow_to_list.message": "Du må følge {name} for å kunne legge vedkommende til i en liste.", "confirmations.follow_to_list.title": "Følg bruker?", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 1ca1ab1d26..9162bcc4a5 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -359,7 +359,6 @@ "collection.share_template_other": "Confira esta coleção incrível: {link}", "collection.share_template_own": "Confira minha nova coleção: {link}", "collections.account_count": "{count, plural, one {# conta} other {# conta}}", - "collections.accounts.empty_description": "Adicionar até {count} contas que você segue", "collections.accounts.empty_title": "Esta coleção está vazia", "collections.by_account": "por {account_handle}", "collections.collection_description": "Descrição", @@ -370,7 +369,6 @@ "collections.confirm_account_removal": "Tem certeza que deseja remover esta conta da sua coleção?", "collections.content_warning": "Aviso de conteúdo", "collections.continue": "Continuar", - "collections.create.accounts_subtitle": "Somente é possível adicionar contas que você segue e que optaram por serem descobertas.", "collections.create.accounts_title": "Quen você vai apresentar nesta coleção?", "collections.create.basic_details_title": "Detalhes básicos", "collections.create.steps": "Passo {step}/{total}", @@ -390,7 +388,6 @@ "collections.error_loading_collections": "Houve um erro ao tentar carregar suas coleções.", "collections.hidden_accounts_description": "Você bloqueou ou silencilou {count, plural, one{este usuário} other {estes usuários}}", "collections.hidden_accounts_link": "{count, plural, one {# conta oculta} other {# contas ocultas}}", - "collections.hints.accounts_counter": "{count} / {max} contas", "collections.last_updated_at": "Atualizado pela última vez em {date}", "collections.manage_accounts": "Gerenciar contas", "collections.mark_as_sensitive": "Marcar como sensível", @@ -398,13 +395,10 @@ "collections.name_length_hint": "limite de 40 caracteres", "collections.new_collection": "Nova coleção", "collections.no_collections_yet": "Ainda não há coleções.", - "collections.old_last_post_note": "Publicado pela última vez semana passada", - "collections.remove_account": "Remover esta conta", "collections.report_collection": "Denunciar esta coleção", "collections.revoke_collection_inclusion": "Remover-me desta coleção", "collections.revoke_inclusion.confirmation": "Você foi removido de \"{collection}\"", "collections.revoke_inclusion.error": "Houve um erro, por favor tente novamente mais tarde.", - "collections.search_accounts_label": "Buscar contas para adicionar…", "collections.search_accounts_max_reached": "Você acrescentou o numero máximo de contas", "collections.sensitive": "Sensível", "collections.topic_hint": "Adicione uma hashtag que ajude os outros a entender o tópico principal desta coleção.", @@ -500,9 +494,6 @@ "confirmations.discard_draft.post.title": "Eliminar seu rascunho de publicação?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Há mudanças não salvas na descrição ou pré-visualização da mídia. Descartar assim mesmo?", - "confirmations.follow_to_collection.confirm": "Seguir e adicionar a coleção", - "confirmations.follow_to_collection.message": "Você deve seguir {name} para adicionar a uma coleção.", - "confirmations.follow_to_collection.title": "Seguir conta?", "confirmations.follow_to_list.confirm": "Seguir e adicionar à lista", "confirmations.follow_to_list.message": "Você precisa seguir {name} para adicioná-lo à lista.", "confirmations.follow_to_list.title": "Seguir usuário?", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index ddf76ab27b..8013055645 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -358,7 +358,6 @@ "collection.share_template_other": "Veja esta coleção interessante: {link}", "collection.share_template_own": "Veja a minha nova coleção: {link}", "collections.account_count": "{count, plural, one {# conta} other {# contas}}", - "collections.accounts.empty_description": "Adicione até {count} contas que segue", "collections.accounts.empty_title": "Esta coleção está vazia", "collections.block_collection_owner": "Bloquear conta", "collections.by_account": "por {account_handle}", @@ -372,7 +371,6 @@ "collections.continue": "Continuar", "collections.copy_link": "Copiar hiperligação", "collections.copy_link_confirmation": "A hiperligação da coleção foi copiada para a área de transferência", - "collections.create.accounts_subtitle": "Apenas as contas que segue e que optaram por ser descobertas podem ser adicionadas.", "collections.create.accounts_title": "Quem vai destacar nesta coleção?", "collections.create.basic_details_title": "Informações básicas", "collections.create.steps": "Passo {step}/{total}", @@ -390,7 +388,6 @@ "collections.detail.you_are_in_this_collection": "Está em destaque nesta coleção", "collections.edit_details": "Editar detalhes", "collections.error_loading_collections": "Ocorreu um erro ao tentar carregar as suas coleções.", - "collections.hints.accounts_counter": "{count} / {max} contas", "collections.last_updated_at": "Última atualização: {date}", "collections.manage_accounts": "Gerir contas", "collections.mark_as_sensitive": "Marcar como sensível", @@ -398,13 +395,10 @@ "collections.name_length_hint": "Limite de 40 carateres", "collections.new_collection": "Nova coleção", "collections.no_collections_yet": "Ainda não existem coleções.", - "collections.old_last_post_note": "Última publicação há mais de uma semana", - "collections.remove_account": "Remover esta conta", "collections.report_collection": "Denunciar esta coleção", "collections.revoke_collection_inclusion": "Remover-me desta coleção", "collections.revoke_inclusion.confirmation": "Foi removido da coleção \"{collection}\"", "collections.revoke_inclusion.error": "Ocorreu um erro, por favor tente novamente mais tarde.", - "collections.search_accounts_label": "Procurar contas para adicionar…", "collections.search_accounts_max_reached": "Já adicionou o máximo de contas", "collections.sensitive": "Sensível", "collections.share_short": "Partilhar", @@ -501,9 +495,6 @@ "confirmations.discard_draft.post.title": "Descartar o rascunho da publicação?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Tem alterações não guardadas na descrição ou na pré-visualização do ficheiro multimédia. Deseja ignorá-las na mesma?", - "confirmations.follow_to_collection.confirm": "Seguir e adicionar à coleção", - "confirmations.follow_to_collection.message": "Precisa de seguir {name} para o/a adicionar a uma coleção.", - "confirmations.follow_to_collection.title": "Seguir conta?", "confirmations.follow_to_list.confirm": "Seguir e adicionar à lista", "confirmations.follow_to_list.message": "Tens de seguir {name} para o adicionares a uma lista.", "confirmations.follow_to_list.title": "Seguir utilizador?", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index e9b7a0888d..c78429d240 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -330,7 +330,6 @@ "collection.share_template_other": "Зацените эту замечательную подборку: {link}", "collection.share_template_own": "Зацените мою новую подборку: {link}", "collections.account_count": "{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}", - "collections.accounts.empty_description": "Вы можете добавить максимум {count} пользователей, на которых вы подписаны", "collections.accounts.empty_title": "В этой подборке пока никого нет", "collections.by_account": "составил(а) {account_handle}", "collections.collection_description": "Описание", @@ -341,7 +340,6 @@ "collections.confirm_account_removal": "Вы уверены, что хотите исключить этого пользователя из подборки?", "collections.content_warning": "Предупреждение о содержании", "collections.continue": "Продолжить", - "collections.create.accounts_subtitle": "Вы можете добавить пользователя в подборку, только если вы подписаны на него, а его профиль доступен для поиска.", "collections.create.accounts_title": "Кого вы выберете для этой подборки?", "collections.create.basic_details_title": "Основная информация", "collections.create.steps": "Шаг {step}/{total}", @@ -356,7 +354,6 @@ "collections.manage_accounts": "Управление аккаунтами", "collections.name_length_hint": "Не более 40 символов", "collections.new_collection": "Новая подборка", - "collections.remove_account": "Исключить этого пользователя", "collections.report_collection": "Пожаловаться на подборку", "collections.revoke_collection_inclusion": "Исключить себя из этой подборки", "collections.revoke_inclusion.confirmation": "Вы были исключены из подборки «{collection}»", @@ -450,9 +447,6 @@ "confirmations.discard_draft.post.title": "Стереть несохранённый черновик поста?", "confirmations.discard_edit_media.confirm": "Сбросить", "confirmations.discard_edit_media.message": "У вас есть несохранённые изменения, касающиеся описания медиа или области предпросмотра. Сбросить их?", - "confirmations.follow_to_collection.confirm": "Подписаться и добавить", - "confirmations.follow_to_collection.message": "Чтобы добавить пользователя {name} в подборку, вы должны быть на него подписаны.", - "confirmations.follow_to_collection.title": "Подписаться на пользователя?", "confirmations.follow_to_list.confirm": "Подписаться и добавить", "confirmations.follow_to_list.message": "Чтобы добавить пользователя {name} в список, вы должны быть на него подписаны.", "confirmations.follow_to_list.title": "Подписаться на пользователя?", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index afc6ca3d1a..849884b8a1 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -354,8 +354,10 @@ "collection.share_template_other": "Shihni këtë koleksion të hijshëm: {link}", "collection.share_template_own": "Shihni koleksionin tim të ri: {link}", "collections.account_count": "{count, plural, one {# llogari} other {# llogari}}", - "collections.accounts.empty_description": "Shtoni deri në {count} llogari që ndiqni", + "collections.accounts.empty_description": "Shtoni deri në {count} llogari", + "collections.accounts.empty_editor_title": "Në këtë koleksion s’ka ende njeri", "collections.accounts.empty_title": "Ky koleksion është i zbrazët", + "collections.block_collection_owner": "Bllokoje llogarinë", "collections.by_account": "nga {account_handle}", "collections.collection_description": "Përshkrim", "collections.collection_language": "Gjuhë", @@ -365,7 +367,8 @@ "collections.confirm_account_removal": "Jeni i sigurt se doni të hiqet kjo llogari nga ky koleksion?", "collections.content_warning": "Sinjalizim lënde", "collections.continue": "Vazhdo", - "collections.create.accounts_subtitle": "Mund të shtohen vetëm llogari që ju ndiqni të cilat kanë zgjedhur të jenë të zbulueshme.", + "collections.copy_link": "Kopjoji lidhjen", + "collections.copy_link_confirmation": "U kopjua në të papastër lidhja e koleksionit", "collections.create.accounts_title": "Kë do të shfaqni në këtë koleksion?", "collections.create.basic_details_title": "Hollësi bazë", "collections.create.steps": "Hapi {step}/{total}", @@ -385,7 +388,7 @@ "collections.error_loading_collections": "Pati një gabim teksa provohej të ngarkoheshin koleksionet tuaj.", "collections.hidden_accounts_description": "Bllokuat, ose heshtuat {count, plural, one {këtë përdorues} other {këta përdorues}}", "collections.hidden_accounts_link": "{count, plural, one {# llogari e fshehur} other {# llogari të fshehura}}", - "collections.hints.accounts_counter": "{count} / {max} llogari", + "collections.hints.accounts_counter": "{count}/{max} llogari", "collections.last_updated_at": "Përditësuar së fundi më: {date}", "collections.manage_accounts": "Administroni llogari", "collections.mark_as_sensitive": "Vëri shenjë si rezervat", @@ -393,15 +396,15 @@ "collections.name_length_hint": "Kufi 40 shenja", "collections.new_collection": "Koleksion i ri", "collections.no_collections_yet": "Ende pa koleksione.", - "collections.old_last_post_note": "Të postuarat e fundit gjatë një jave më parë", - "collections.remove_account": "Hiqe këtë llogari", + "collections.remove_account": "Hiqe", "collections.report_collection": "Raportojeni këtë koleksion", "collections.revoke_collection_inclusion": "Hiqmëni nga ky koleksion", "collections.revoke_inclusion.confirmation": "U hoqët nga “{collection}”", "collections.revoke_inclusion.error": "Pati një gabim, ju lutemi, riprovoni më vonë.", - "collections.search_accounts_label": "Kërkoni për llogari për shtim…", + "collections.search_accounts_label": "Kërkoni për një llogari për t’u shtuar", "collections.search_accounts_max_reached": "Keni shtuar numrin maksimum të llogarive", "collections.sensitive": "Rezervat", + "collections.share_short": "Ndajeni me të tjerë", "collections.topic_hint": "Shtoni një hashtag që ndihmon të tjerët të kuptojnë temën kryesore të këtij koleksion.", "collections.topic_special_chars_hint": "Shenjat e posaçme do të hiqen, kur ruhet", "collections.unlisted_collections_description": "Këto nuk shfaqen në profilin tuaj për të tjerët. Cilido me lidhjen mund t’i zbulojë.", @@ -497,9 +500,6 @@ "confirmations.discard_draft.post.title": "Të hidhet tej skica e postimit tuaj?", "confirmations.discard_edit_media.confirm": "Hidhe tej", "confirmations.discard_edit_media.message": "Keni ndryshime të paruajtura te përshkrimi ose paraparja e medias, të hidhen tej, sido qoftë?", - "confirmations.follow_to_collection.confirm": "Ndiqe dhe shtoje në koleksion", - "confirmations.follow_to_collection.message": "Që ta shtoni te një koleksion, duhet të jeni duke e ndjekur {name}.", - "confirmations.follow_to_collection.title": "Të ndiqet llogaria?", "confirmations.follow_to_list.confirm": "Ndiqe dhe shtoje te listë", "confirmations.follow_to_list.message": "Lypset të jeni duke e ndjekur {name}, që të shtohte te një listë.", "confirmations.follow_to_list.title": "Të ndiqet përdoruesi?", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index f7f8ececb9..472b49d24e 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -357,7 +357,6 @@ "collection.share_template_other": "Kolla in denna coola samling: {link}", "collection.share_template_own": "Kolla in min nya samling: {link}", "collections.account_count": "{count, plural, one {# konto} other {# konton}}", - "collections.accounts.empty_description": "Lägg till upp till {count} konton som du följer", "collections.accounts.empty_title": "Denna samling är tom", "collections.block_collection_owner": "Blockera konto", "collections.by_account": "av {account_handle}", @@ -370,7 +369,6 @@ "collections.content_warning": "Innehållsvarning", "collections.continue": "Fortsätt", "collections.copy_link": "Kopiera länk", - "collections.create.accounts_subtitle": "Endast konton som du följer som har valt att upptäcka kan läggas till.", "collections.create.accounts_title": "Vem kommer du att visa i den här samlingen?", "collections.create.basic_details_title": "Grundläggande detaljer", "collections.create.steps": "Steg {step}/{total}", @@ -388,7 +386,6 @@ "collections.detail.you_are_in_this_collection": "Du är med i denna samling", "collections.edit_details": "Redigera detaljer", "collections.error_loading_collections": "Det uppstod ett fel när dina samlingar skulle laddas.", - "collections.hints.accounts_counter": "{count} / {max} konton", "collections.last_updated_at": "Senast uppdaterad: {date}", "collections.manage_accounts": "Hantera konton", "collections.mark_as_sensitive": "Markera som känsligt innehåll", @@ -396,13 +393,10 @@ "collections.name_length_hint": "Begränsat till 40 tecken", "collections.new_collection": "Ny samling", "collections.no_collections_yet": "Inga samlingar än.", - "collections.old_last_post_note": "Skapade senast ett inlägg för över en vecka sedan", - "collections.remove_account": "Ta bort detta konto", "collections.report_collection": "Rapportera denna samling", "collections.revoke_collection_inclusion": "Ta bort mig själv från denna samling", "collections.revoke_inclusion.confirmation": "Du har tagits bort från \"{collection}\"", "collections.revoke_inclusion.error": "Ett fel uppstod, försök igen senare.", - "collections.search_accounts_label": "Sök efter konton för att lägga till…", "collections.search_accounts_max_reached": "Du har lagt till maximalt antal konton", "collections.sensitive": "Känsligt", "collections.share_short": "Dela", @@ -497,9 +491,6 @@ "confirmations.discard_draft.post.title": "Vill du förkasta utkastet?", "confirmations.discard_edit_media.confirm": "Kasta", "confirmations.discard_edit_media.message": "Du har osparade ändringar till mediabeskrivningen eller förhandsgranskningen, kasta bort dem ändå?", - "confirmations.follow_to_collection.confirm": "Följ och lägg till i samlingen", - "confirmations.follow_to_collection.message": "Du måste följa {name} för att lägga till dem i en samling.", - "confirmations.follow_to_collection.title": "Följ kontot?", "confirmations.follow_to_list.confirm": "Följ och lägg till i listan", "confirmations.follow_to_list.message": "Du måste följa {name} för att lägga till dem i en lista.", "confirmations.follow_to_list.title": "Följ användare?", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index e2450305c0..0555f4eca2 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -152,7 +152,6 @@ "closed_registrations_modal.find_another_server": "ค้นหาเซิร์ฟเวอร์อื่น", "closed_registrations_modal.preamble": "Mastodon เป็นแบบกระจายศูนย์ ดังนั้นไม่ว่าคุณจะสร้างบัญชีของคุณที่ใด คุณจะสามารถติดตามและโต้ตอบกับใครก็ตามในเซิร์ฟเวอร์นี้ คุณยังสามารถโฮสต์บัญชีด้วยตนเองได้อีกด้วย!", "closed_registrations_modal.title": "การลงทะเบียนใน Mastodon", - "collections.accounts.empty_description": "สามารถเพิ่มได้สูงสุด {count} บัญชีที่คุณติดตาม", "column.about": "เกี่ยวกับ", "column.blocks": "ผู้ใช้ที่ปิดกั้นอยู่", "column.bookmarks": "ที่คั่นหน้า", @@ -221,9 +220,6 @@ "confirmations.discard_draft.post.title": "ละทิ้งโพสต์แบบร่างของคุณ?", "confirmations.discard_edit_media.confirm": "ละทิ้ง", "confirmations.discard_edit_media.message": "คุณมีการเปลี่ยนแปลงคำอธิบายหรือตัวอย่างสื่อที่ยังไม่ได้บันทึก ละทิ้งการเปลี่ยนแปลงเหล่านั้นต่อไป?", - "confirmations.follow_to_collection.confirm": "ติดตามและเพิ่มไปยังคอลเล็กชัน", - "confirmations.follow_to_collection.message": "คุณต้องติดตาม {name} ถึงจะสามารถเพิ่มพวกเขาลงคอลเล็กชันได้", - "confirmations.follow_to_collection.title": "ติดตามบัญชีนี้ไหม?", "confirmations.follow_to_list.confirm": "ติดตามและเพิ่มไปยังรายการ", "confirmations.follow_to_list.message": "คุณต้องติดตาม {name} ถึงจะสามารถเพิ่มพวกเขาลงลิสต์ได้", "confirmations.follow_to_list.title": "ติดตามผู้ใช้?", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index f669d04515..0d1ece0c91 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -359,8 +359,8 @@ "collection.share_template_other": "Bu harika koleksiyona göz atın: {link}", "collection.share_template_own": "Yeni koleksiyonuma göz atın: {link}", "collections.account_count": "{count, plural, one {# hesap} other {# hesap}}", - "collections.accounts.empty_description": "Takip ettiğiniz hesapların sayısını {count} kadar artırın", "collections.accounts.empty_title": "Bu koleksiyon boş", + "collections.block_collection_owner": "Hesabı engelle", "collections.by_account": "yazan {account_handle}", "collections.collection_description": "Açıklama", "collections.collection_language": "Dil", @@ -370,7 +370,8 @@ "collections.confirm_account_removal": "Bu hesabı bu koleksiyondan çıkarmak istediğinizden emin misiniz?", "collections.content_warning": "İçerik uyarısı", "collections.continue": "Devam et", - "collections.create.accounts_subtitle": "Yalnızca keşif seçeneğini etkinleştirmiş takip ettiğiniz hesaplar eklenebilir.", + "collections.copy_link": "Bağlantıyı kopyala", + "collections.copy_link_confirmation": "Koleksiyon bağlantısı panoya kopyalandı", "collections.create.accounts_title": "Bu koleksiyonda kimleri öne çıkaracaksınız?", "collections.create.basic_details_title": "Temel bilgiler", "collections.create.steps": "Adım {step}/{total}", @@ -390,7 +391,6 @@ "collections.error_loading_collections": "Koleksiyonlarınızı yüklemeye çalışırken bir hata oluştu.", "collections.hidden_accounts_description": "Bu {count, plural, one {kullanıcıyı} other {kullanıcıları}} engellediniz veya sessize aldınız", "collections.hidden_accounts_link": "{count, plural, one {# gizli hesap} other {# gizli hesap}}", - "collections.hints.accounts_counter": "{count} / {max} hesap", "collections.last_updated_at": "Son güncelleme: {date}", "collections.manage_accounts": "Hesapları yönet", "collections.mark_as_sensitive": "Hassas olarak işaretle", @@ -398,15 +398,13 @@ "collections.name_length_hint": "40 karakterle sınırlı", "collections.new_collection": "Yeni koleksiyon", "collections.no_collections_yet": "Henüz hiçbir koleksiyon yok.", - "collections.old_last_post_note": "Son gönderi bir haftadan önce", - "collections.remove_account": "Bu hesabı çıkar", "collections.report_collection": "Bu koleksiyonu bildir", "collections.revoke_collection_inclusion": "Beni bu koleksiyondan çıkar", "collections.revoke_inclusion.confirmation": "\"{collection}\" koleksiyonundan çıkarıldınız", "collections.revoke_inclusion.error": "Bir hata oluştu, lütfen daha sonra tekrar deneyin.", - "collections.search_accounts_label": "Eklemek için hesap arayın…", "collections.search_accounts_max_reached": "Maksimum hesabı eklediniz", "collections.sensitive": "Hassas", + "collections.share_short": "Paylaş", "collections.topic_hint": "Bu koleksiyonun ana konusunu başkalarının anlamasına yardımcı olacak bir etiket ekleyin.", "collections.topic_special_chars_hint": "Kaydederken özel karakterler silinecektir", "collections.unlisted_collections_description": "Bunlar profilinizde başkalarına görünmez. Bağlantıya sahip olan herkes bunları görebilir.", @@ -502,9 +500,6 @@ "confirmations.discard_draft.post.title": "Taslak gönderiniz silinsin mi?", "confirmations.discard_edit_media.confirm": "Vazgeç", "confirmations.discard_edit_media.message": "Medya açıklaması veya ön izlemede kaydedilmemiş değişiklikleriniz var, yine de vazgeçmek istiyor musunuz?", - "confirmations.follow_to_collection.confirm": "Takip et ve koleksiyona ekle", - "confirmations.follow_to_collection.message": "Bir koleksiyona eklemek için {name} kişisini takip etmeniz gerekiyor.", - "confirmations.follow_to_collection.title": "Hesabı takip et?", "confirmations.follow_to_list.confirm": "Takip et ve yapılacaklar listesine ekle", "confirmations.follow_to_list.message": "Bir listeye eklemek için {name} kişisini takip etmeniz gerekiyor.", "confirmations.follow_to_list.title": "Kullanıcıyı takip et?", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index d01c04ec70..f643718e5e 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -191,7 +191,6 @@ "closed_registrations_modal.title": "Реєстрація на Mastodon", "collections.detail.accounts_heading": "Облікові записи", "collections.edit_details": "Редагувати подробиці", - "collections.remove_account": "Вилучити обліковий запис", "column.about": "Про застосунок", "column.blocks": "Заблоковані користувачі", "column.bookmarks": "Закладки", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 6733c4d394..2e5d481938 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -359,7 +359,8 @@ "collection.share_template_other": "Xem ngay gói khởi đầu tuyệt vời này: {link}", "collection.share_template_own": "Xem ngay gói khởi đầu mới của tôi: {link}", "collections.account_count": "{count, plural, other {# tài khoản}}", - "collections.accounts.empty_description": "Thêm tối đa {count} tài khoản mà bạn theo dõi", + "collections.accounts.empty_description": "Thêm tối đa {count} tài khoản", + "collections.accounts.empty_editor_title": "Chưa có ai trong gói khởi đầu này", "collections.accounts.empty_title": "Gói khởi đầu này trống", "collections.block_collection_owner": "Chặn tài khoản", "collections.by_account": "bởi {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "Tiếp tục", "collections.copy_link": "Sao chép liên kết", "collections.copy_link_confirmation": "Sao chép liên kết tài khoản vào clipboard", - "collections.create.accounts_subtitle": "Chỉ những tài khoản bạn theo dõi và đã chọn khám phá mới có thể được thêm vào.", "collections.create.accounts_title": "Bạn sẽ chọn ai để giới thiệu trong gói khởi đầu này?", "collections.create.basic_details_title": "Thông tin cơ bản", "collections.create.steps": "Bước {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "Đã xảy ra lỗi khi tải những gói khởi đầu của bạn.", "collections.hidden_accounts_description": "Bạn vừa chặn hoặc phớt lờ {count, plural, other {tài khoản này}}", "collections.hidden_accounts_link": "{count, plural, other {# tài khoản đã ẩn}}", - "collections.hints.accounts_counter": "{count} / {max} tài khoản", + "collections.hints.accounts_counter": "{count}/{max} tài khoản", "collections.last_updated_at": "Lần cuối cập nhật: {date}", "collections.manage_accounts": "Quản lý tài khoản", "collections.mark_as_sensitive": "Đánh dấu nhạy cảm", @@ -401,13 +401,12 @@ "collections.name_length_hint": "Giới hạn 40 ký tự", "collections.new_collection": "Gói khởi đầu mới", "collections.no_collections_yet": "Chưa có gói khởi đầu.", - "collections.old_last_post_note": "Đăng lần cuối hơn một tuần trước", - "collections.remove_account": "Gỡ tài khoản này", + "collections.remove_account": "Gỡ", "collections.report_collection": "Báo cáo gói khởi đầu này", "collections.revoke_collection_inclusion": "Xóa tôi khỏi gói khởi đầu này", "collections.revoke_inclusion.confirmation": "Bạn đã được gỡ khỏi \"{collection}\"", "collections.revoke_inclusion.error": "Đã có lỗi, xin vui lòng thử lại.", - "collections.search_accounts_label": "Tìm tài khoản để thêm…", + "collections.search_accounts_label": "Tìm tài khoản để thêm", "collections.search_accounts_max_reached": "Bạn đã đạt đến số lượng tài khoản tối đa", "collections.sensitive": "Nhạy cảm", "collections.share_short": "Chia sẻ", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "Bỏ tút đang soạn?", "confirmations.discard_edit_media.confirm": "Bỏ qua", "confirmations.discard_edit_media.message": "Bạn chưa lưu thay đổi của phần mô tả hoặc bản xem trước của media, vẫn bỏ qua?", - "confirmations.follow_to_collection.confirm": "Theo dõi & thêm vào gói khởi đầu", - "confirmations.follow_to_collection.message": "Bạn cần theo dõi {name} trước khi thêm họ vào gói khởi đầu.", - "confirmations.follow_to_collection.title": "Theo dõi tài khoản?", "confirmations.follow_to_list.confirm": "Theo dõi & thêm vào danh sách", "confirmations.follow_to_list.message": "Bạn cần theo dõi {name} trước khi thêm họ vào danh sách.", "confirmations.follow_to_list.title": "Theo dõi tài khoản?", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 3498733f9b..944c311350 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -359,7 +359,8 @@ "collection.share_template_other": "发现了个收藏列表,来看看:{link}", "collection.share_template_own": "我的新收藏列表,来看看:{link}", "collections.account_count": "{count, plural, other {# 个账号}}", - "collections.accounts.empty_description": "添加你关注的账号,最多 {count} 个", + "collections.accounts.empty_description": "添加最多 {count} 个账号", + "collections.accounts.empty_editor_title": "此收藏列表中暂无用户", "collections.accounts.empty_title": "收藏列表为空", "collections.block_collection_owner": "屏蔽账户", "collections.by_account": "由 {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "继续", "collections.copy_link": "复制链接", "collections.copy_link_confirmation": "已复制收藏列表链接到剪贴板", - "collections.create.accounts_subtitle": "只有你关注的且已经主动加入发现功能的账号可以添加。", "collections.create.accounts_title": "你想在收藏列表中添加哪些人?", "collections.create.basic_details_title": "基本信息", "collections.create.steps": "第 {step} 步(共 {total} 步)", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "加载你的收藏列表时发生错误。", "collections.hidden_accounts_description": "你已屏蔽或隐藏{count, plural, other {部分用户}}", "collections.hidden_accounts_link": "{count, plural, other {# 个隐藏账号}}", - "collections.hints.accounts_counter": "{count} / {max} 个账号", + "collections.hints.accounts_counter": "{count}/{max} 个账号", "collections.last_updated_at": "最后更新:{date}", "collections.manage_accounts": "管理账户", "collections.mark_as_sensitive": "标记为敏感内容", @@ -401,13 +401,12 @@ "collections.name_length_hint": "40 字限制", "collections.new_collection": "新建收藏列表", "collections.no_collections_yet": "尚无收藏列表。", - "collections.old_last_post_note": "上次发言于一周多以前", - "collections.remove_account": "移除此账号", + "collections.remove_account": "移除", "collections.report_collection": "举报此收藏列表", "collections.revoke_collection_inclusion": "将自己从此收藏列表中移除", "collections.revoke_inclusion.confirmation": "你已被从“{collection}”中移除", "collections.revoke_inclusion.error": "出现错误,请稍后重试。", - "collections.search_accounts_label": "搜索要添加的账号…", + "collections.search_accounts_label": "搜索要添加的账号", "collections.search_accounts_max_reached": "你添加的账号数量已达上限", "collections.sensitive": "敏感内容", "collections.share_short": "分享", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "丢弃你的嘟文草稿?", "confirmations.discard_edit_media.confirm": "丢弃", "confirmations.discard_edit_media.message": "你还有未保存的媒体描述或预览修改,仍要丢弃吗?", - "confirmations.follow_to_collection.confirm": "关注并添加到收藏列表", - "confirmations.follow_to_collection.message": "你需要先关注 {name},才能将其添加到收藏列表。", - "confirmations.follow_to_collection.title": "要关注账号吗?", "confirmations.follow_to_list.confirm": "关注并添加到列表", "confirmations.follow_to_list.message": "你需要先关注 {name},才能将其添加到列表。", "confirmations.follow_to_list.title": "确定要关注此用户?", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index d5e9b0d7c1..839c32b684 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -359,7 +359,8 @@ "collection.share_template_other": "來看看這個酷酷的收藏名單:{link}", "collection.share_template_own": "來看看我的新收藏名單:{link}", "collections.account_count": "{count, plural, other {# 個帳號}}", - "collections.accounts.empty_description": "加入最多 {count} 個您跟隨之帳號", + "collections.accounts.empty_description": "加入最多 {count} 個帳號", + "collections.accounts.empty_editor_title": "此收藏名單尚未有任何人", "collections.accounts.empty_title": "此收藏名單是空的", "collections.block_collection_owner": "封鎖帳號", "collections.by_account": "來自 {account_handle}", @@ -373,7 +374,6 @@ "collections.continue": "繼續", "collections.copy_link": "複製連結", "collections.copy_link_confirmation": "複製收藏名單連結至剪貼簿", - "collections.create.accounts_subtitle": "僅能加入您跟隨並選擇加入探索功能之帳號。", "collections.create.accounts_title": "您想於此收藏名單推薦誰?", "collections.create.basic_details_title": "基本資料", "collections.create.steps": "步驟 {step}/{total}", @@ -393,7 +393,7 @@ "collections.error_loading_collections": "讀取您的收藏名單時發生錯誤。", "collections.hidden_accounts_description": "您已封鎖或靜音{count, plural, other {這些使用者}}", "collections.hidden_accounts_link": "{count, plural, other {# 個隱藏帳號}}", - "collections.hints.accounts_counter": "{count} / {max} 個帳號", + "collections.hints.accounts_counter": "{count}/{max} 個帳號", "collections.last_updated_at": "最後更新:{date}", "collections.manage_accounts": "管理帳號", "collections.mark_as_sensitive": "標記為敏感內容", @@ -401,13 +401,12 @@ "collections.name_length_hint": "40 字限制", "collections.new_collection": "新增收藏名單", "collections.no_collections_yet": "您沒有任何收藏名單。", - "collections.old_last_post_note": "上次發表嘟文已超過一週", - "collections.remove_account": "移除此帳號", + "collections.remove_account": "移除", "collections.report_collection": "檢舉此收藏名單", "collections.revoke_collection_inclusion": "將我自此收藏名單中移除", "collections.revoke_inclusion.confirmation": "您已自「{collection}」中被移除", "collections.revoke_inclusion.error": "發生錯誤,請稍候重試。", - "collections.search_accounts_label": "搜尋帳號以加入...", + "collections.search_accounts_label": "搜尋帳號以加入", "collections.search_accounts_max_reached": "您新增之帳號數已達上限", "collections.sensitive": "敏感內容", "collections.share_short": "分享", @@ -506,9 +505,6 @@ "confirmations.discard_draft.post.title": "是否捨棄您的嘟文草稿?", "confirmations.discard_edit_media.confirm": "捨棄", "confirmations.discard_edit_media.message": "您於媒體描述或預覽區塊有未儲存的變更。是否要捨棄這些變更?", - "confirmations.follow_to_collection.confirm": "跟隨並加入至收藏名單", - "confirmations.follow_to_collection.message": "您必須先跟隨 {name} 以將其加入至收藏名單。", - "confirmations.follow_to_collection.title": "是否跟隨此帳號?", "confirmations.follow_to_list.confirm": "跟隨並加入至列表", "confirmations.follow_to_list.message": "您必須先跟隨 {name} 以將其加入至列表。", "confirmations.follow_to_list.title": "是否跟隨該使用者?", From d5f0e3726041a36c97d09747d4a0190b2c345958 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 17 Apr 2026 04:09:37 -0400 Subject: [PATCH 19/37] Include hosts resolver in request socket DNS lookup (#38699) --- app/lib/request.rb | 8 +++----- spec/lib/fasp/request_spec.rb | 16 +++++++++------- spec/lib/request_spec.rb | 35 +++++++++++++++++++++-------------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index 66d7ece70f..376a37638c 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -292,11 +292,9 @@ class Request begin addresses = [IPAddr.new(host)] rescue IPAddr::InvalidAddressError - Resolv::DNS.open do |dns| - dns.timeouts = 5 - addresses = dns.getaddresses(host) - addresses = addresses.grep(Resolv::IPv6).take(2) + addresses.grep_v(Resolv::IPv6).take(2) - end + resolvers = [Resolv::Hosts.new, Resolv::DNS.new.tap { |dns| dns.timeouts = 5 }] + addresses = Resolv.new(resolvers).getaddresses(host) + addresses = addresses.grep(Resolv::IPv6::Regex).take(2) + addresses.grep_v(Resolv::IPv6::Regex).take(2) end socks = [] diff --git a/spec/lib/fasp/request_spec.rb b/spec/lib/fasp/request_spec.rb index 6456057b5e..cdc20270cd 100644 --- a/spec/lib/fasp/request_spec.rb +++ b/spec/lib/fasp/request_spec.rb @@ -86,14 +86,16 @@ RSpec.describe Fasp::Request do WebMock.enable! end + let(:resolv_service) { instance_double(Resolv) } + + before do + allow(Resolv).to receive(:new).and_return(resolv_service) + allow(resolv_service).to receive(:getaddresses).with('reqprov.example.com').and_return(%w(0.0.0.0 2001:db8::face)) + end + it 'raises Mastodon::ValidationError' do - resolver = instance_double(Resolv::DNS) - - allow(resolver).to receive(:getaddresses).with('reqprov.example.com').and_return(%w(0.0.0.0 2001:db8::face)) - allow(resolver).to receive(:timeouts=).and_return(nil) - allow(Resolv::DNS).to receive(:open).and_yield(resolver) - - expect { subject.send(method, '/test_path') }.to raise_error(Mastodon::ValidationError) + expect { subject.send(method, '/test_path') } + .to raise_error(Mastodon::ValidationError) end end end diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index da61823455..228c8bbb67 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -66,15 +66,20 @@ RSpec.describe Request do expect(a_request(:get, 'http://example.com')).to have_been_made.once end - it 'executes a HTTP request when the first address is private' do - resolver = instance_double(Resolv::DNS) + context 'when first address is private' do + let(:resolv_service) { instance_double(Resolv) } - allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:4860:4860::8844)) - allow(resolver).to receive(:timeouts=).and_return(nil) - allow(Resolv::DNS).to receive(:open).and_yield(resolver) + before do + allow(Resolv).to receive(:new).and_return(resolv_service) + allow(resolv_service).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:4860:4860::8844)) + end - expect { |block| subject.perform(&block) }.to yield_control - expect(a_request(:get, 'http://example.com')).to have_been_made.once + it 'executes a HTTP request' do + expect { |block| subject.perform(&block) } + .to yield_control + expect(a_request(:get, 'http://example.com')) + .to have_been_made.once + end end it 'makes a request with expected headers, yields, and closes the underlying connection' do @@ -123,14 +128,16 @@ RSpec.describe Request do WebMock.enable! end + let(:resolv_service) { instance_double(Resolv) } + + before do + allow(Resolv).to receive(:new).with([be_a(Resolv::Hosts), be_a(Resolv::DNS)]).and_return(resolv_service) + allow(resolv_service).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:db8::face)) + end + it 'raises Mastodon::ValidationError' do - resolver = instance_double(Resolv::DNS) - - allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:db8::face)) - allow(resolver).to receive(:timeouts=).and_return(nil) - allow(Resolv::DNS).to receive(:open).and_yield(resolver) - - expect { subject.perform }.to raise_error Mastodon::ValidationError + expect { subject.perform } + .to raise_error Mastodon::ValidationError end end From 3411d06f9eddd6e52574b713bda5b5bac2008d0e Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 17 Apr 2026 04:31:37 -0400 Subject: [PATCH 20/37] Pull user settings defaults from configuration (#38592) --- app/helpers/settings_helper.rb | 4 ++++ .../preferences/appearance/show.html.haml | 8 ++++---- .../preferences/notifications/show.html.haml | 2 +- .../preferences/posting_defaults/show.html.haml | 2 +- spec/helpers/settings_helper_spec.rb | 16 ++++++++++++++++ 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 44113f3d47..196eac52d5 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -23,6 +23,10 @@ module SettingsHelper ) end + def user_settings_collection(value) + UserSettings.definition_for(value)&.in || [] + end + def author_attribution_name(account) return if account.nil? diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 4a63790256..c024d2d515 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -36,7 +36,7 @@ .input.horizontal-options = ff.input :'web.color_scheme', as: :radio_buttons, - collection: %w(auto light dark), + collection: user_settings_collection('web.color_scheme'), include_blank: false, label: I18n.t('simple_form.labels.defaults.setting_color_scheme'), label_method: ->(contrast) { I18n.t("color_scheme.#{contrast}", default: contrast) }, @@ -45,7 +45,7 @@ .input.horizontal-options = ff.input :'web.contrast', as: :radio_buttons, - collection: %w(auto high), + collection: user_settings_collection('web.contrast'), include_blank: false, label: I18n.t('simple_form.labels.defaults.setting_contrast'), label_method: ->(contrast) { I18n.t("contrast.#{contrast}", default: contrast) }, @@ -55,7 +55,7 @@ .fields-group = f.simple_fields_for :settings, current_user.settings do |ff| = ff.input :'web.emoji_style', - collection: %w(auto twemoji native), + collection: user_settings_collection('web.emoji_style'), include_blank: false, hint: I18n.t('simple_form.hints.defaults.setting_emoji_style'), label: I18n.t('simple_form.labels.defaults.setting_emoji_style'), @@ -110,7 +110,7 @@ = ff.input :'web.display_media', as: :radio_buttons, collection_wrapper_tag: 'ul', - collection: %w(default show_all hide_all), + collection: user_settings_collection('web.display_media'), hint: false, item_wrapper_tag: 'li', label_method: ->(item) { t("simple_form.hints.defaults.setting_display_media_#{item}") }, diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index 033cb15dd7..fb206b1308 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -35,7 +35,7 @@ - if SoftwareUpdate.check_enabled? && current_user.can?(:view_devops) .fields-group = ff.input :'notification_emails.software_updates', - collection: %w(none critical patch all), + collection: user_settings_collection('notification_emails.software_updates'), hint: false, include_blank: false, label_method: ->(setting) { I18n.t("simple_form.labels.notification_emails.software_updates.#{setting}") }, diff --git a/app/views/settings/preferences/posting_defaults/show.html.haml b/app/views/settings/preferences/posting_defaults/show.html.haml index 30a2f01f88..9e14585013 100644 --- a/app/views/settings/preferences/posting_defaults/show.html.haml +++ b/app/views/settings/preferences/posting_defaults/show.html.haml @@ -26,7 +26,7 @@ .fields-group = ff.input :default_quote_policy, - collection: %w(public followers nobody), + collection: user_settings_collection('default_quote_policy'), include_blank: false, label_method: ->(policy) { I18n.t("statuses.quote_policies.#{policy}") }, label: I18n.t('simple_form.labels.defaults.setting_default_quote_policy'), diff --git a/spec/helpers/settings_helper_spec.rb b/spec/helpers/settings_helper_spec.rb index ecff2edbfa..63773634e9 100644 --- a/spec/helpers/settings_helper_spec.rb +++ b/spec/helpers/settings_helper_spec.rb @@ -3,6 +3,22 @@ require 'rails_helper' RSpec.describe SettingsHelper do + describe '#user_settings_collection' do + subject { helper.user_settings_collection(value) } + + context 'with valid value' do + let(:value) { 'web.contrast' } + + it { is_expected.to eq(%w(auto high)) } + end + + context 'with invalid value' do + let(:value) { 'web.nothing_at_this_key_at_all_fake_fake_fake' } + + it { is_expected.to be_empty } + end + end + describe 'session_device_icon' do context 'with a mobile device' do let(:session) { SessionActivation.new(user_agent: 'Mozilla/5.0 (iPhone)') } From e571994b5c6700cd896795f792978a709a09c5ce Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 17 Apr 2026 10:50:35 +0200 Subject: [PATCH 21/37] Remove "View other collections from this user" from collection menu (#38728) --- .../components/collection_menu.tsx | 31 +++---------------- app/javascript/mastodon/locales/en.json | 1 - 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/app/javascript/mastodon/features/collections/components/collection_menu.tsx b/app/javascript/mastodon/features/collections/components/collection_menu.tsx index 1cbb7279e9..661e879df5 100644 --- a/app/javascript/mastodon/features/collections/components/collection_menu.tsx +++ b/app/javascript/mastodon/features/collections/components/collection_menu.tsx @@ -2,8 +2,6 @@ import { useCallback, useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { matchPath } from 'react-router'; - import { showAlert } from '@/mastodon/actions/alerts'; import { initBlockModal } from '@/mastodon/actions/blocks'; import { useAccount } from '@/mastodon/hooks/useAccount'; @@ -35,10 +33,6 @@ const messages = defineMessages({ id: 'collections.copy_link_confirmation', defaultMessage: 'Copied collection link to clipboard', }, - viewOtherCollections: { - id: 'collections.view_other_collections_by_user', - defaultMessage: 'View other collections by this user', - }, delete: { id: 'collections.delete_collection', defaultMessage: 'Delete collection', @@ -168,25 +162,11 @@ export const CollectionMenu: React.FC<{ return ownerItems; } } else { - const nonOwnerItems: MenuItem[] = [viewCollectionItem, ...shareItems]; - - if (context !== 'notifications' && ownerAccount) { - const featuredCollectionsPath = `/@${ownerAccount.acct}/featured`; - // Don't show menu link to featured collections while on that very page - if ( - !matchPath(location.pathname, { - path: featuredCollectionsPath, - exact: true, - }) - ) { - nonOwnerItems.push({ - text: intl.formatMessage(messages.viewOtherCollections), - to: featuredCollectionsPath, - }); - } - } - - nonOwnerItems.push(null); + const nonOwnerItems: MenuItem[] = [ + viewCollectionItem, + ...shareItems, + null, + ]; // Collection notifications already have a prominent 'Remove me' button if (currentAccountInCollection && context !== 'notifications') { @@ -218,7 +198,6 @@ export const CollectionMenu: React.FC<{ dispatch, openDeleteConfirmation, context, - ownerAccount, currentAccountInCollection, openReportModal, openBlockModal, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 51d91c2115..449679aab5 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -415,7 +415,6 @@ "collections.unlisted_collections_description": "These don’t appear on your profile to others. Anyone with the link can discover them.", "collections.unlisted_collections_with_count": "Unlisted collections ({count})", "collections.view_collection": "View collection", - "collections.view_other_collections_by_user": "View other collections by this user", "collections.visibility_public": "Public", "collections.visibility_public_hint": "Discoverable in search results and other areas where recommendations appear.", "collections.visibility_title": "Visibility", From 570f2ef4828044312a88ad410fa8e66939e75c56 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 17 Apr 2026 15:18:04 +0200 Subject: [PATCH 22/37] Allow grouping items in Combobox component (#38730) --- .../form_fields/combobox.module.scss | 12 + .../form_fields/combobox_field.stories.tsx | 104 ++++---- .../components/form_fields/combobox_field.tsx | 223 ++++++++++++------ .../features/collections/editor/accounts.tsx | 39 ++- 4 files changed, 239 insertions(+), 139 deletions(-) diff --git a/app/javascript/mastodon/components/form_fields/combobox.module.scss b/app/javascript/mastodon/components/form_fields/combobox.module.scss index 732893200c..ca7bc2020b 100644 --- a/app/javascript/mastodon/components/form_fields/combobox.module.scss +++ b/app/javascript/mastodon/components/form_fields/combobox.module.scss @@ -38,6 +38,18 @@ overscroll-behavior-y: contain; } +.groupLabel { + padding-block: 12px 4px; + padding-inline: 12px; + font-size: 13px; + font-weight: bold; + text-transform: uppercase; + + ul:first-child > & { + padding-top: 4px; + } +} + .menuItem { display: flex; align-items: center; diff --git a/app/javascript/mastodon/components/form_fields/combobox_field.stories.tsx b/app/javascript/mastodon/components/form_fields/combobox_field.stories.tsx index 2c4b82fdcd..32fc736423 100644 --- a/app/javascript/mastodon/components/form_fields/combobox_field.stories.tsx +++ b/app/javascript/mastodon/components/form_fields/combobox_field.stories.tsx @@ -4,40 +4,46 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { ComboboxField } from './combobox_field'; -const ComboboxDemo: React.FC = () => { +interface Fruit { + id: string; + name: string; + type: 'citrus' | 'berryish' | 'seedy' | 'stony' | 'longish' | 'chonky'; + disabled?: boolean; +} + +const ComboboxDemo: React.FC<{ withGroups?: boolean }> = ({ withGroups }) => { const [searchValue, setSearchValue] = useState(''); - const items = [ - { id: '1', name: 'Apple' }, - { id: '2', name: 'Banana' }, - { id: '3', name: 'Cherry', disabled: true }, - { id: '4', name: 'Date' }, - { id: '5', name: 'Fig', disabled: true }, - { id: '6', name: 'Grape' }, - { id: '7', name: 'Honeydew' }, - { id: '8', name: 'Kiwi' }, - { id: '9', name: 'Lemon' }, - { id: '10', name: 'Mango' }, - { id: '11', name: 'Nectarine' }, - { id: '12', name: 'Orange' }, - { id: '13', name: 'Papaya' }, - { id: '14', name: 'Quince' }, - { id: '15', name: 'Raspberry' }, - { id: '16', name: 'Strawberry' }, - { id: '17', name: 'Tangerine' }, - { id: '19', name: 'Vanilla bean' }, - { id: '20', name: 'Watermelon' }, - { id: '22', name: 'Yellow Passion Fruit' }, - { id: '23', name: 'Zucchini' }, - { id: '24', name: 'Cantaloupe' }, - { id: '25', name: 'Blackberry' }, - { id: '26', name: 'Persimmon' }, - { id: '27', name: 'Lychee' }, - { id: '28', name: 'Dragon Fruit' }, - { id: '29', name: 'Passion Fruit' }, - { id: '30', name: 'Starfruit' }, + const items: Fruit[] = [ + { id: '1', name: 'Apple', type: 'seedy' }, + { id: '2', name: 'Banana', type: 'longish' }, + { id: '3', name: 'Cherry', type: 'berryish', disabled: true }, + { id: '4', name: 'Date', type: 'stony' }, + { id: '5', name: 'Fig', type: 'seedy', disabled: true }, + { id: '6', name: 'Grape', type: 'berryish' }, + { id: '7', name: 'Honeydew', type: 'chonky' }, + { id: '8', name: 'Kiwi', type: 'seedy' }, + { id: '9', name: 'Lemon', type: 'citrus' }, + { id: '10', name: 'Mango', type: 'stony' }, + { id: '11', name: 'Nectarine', type: 'stony' }, + { id: '12', name: 'Orange', type: 'citrus' }, + { id: '13', name: 'Papaya', type: 'seedy' }, + { id: '14', name: 'Quince', type: 'seedy' }, + { id: '15', name: 'Raspberry', type: 'berryish' }, + { id: '16', name: 'Strawberry', type: 'berryish' }, + { id: '17', name: 'Tangerine', type: 'citrus' }, + { id: '19', name: 'Vanilla bean', type: 'longish' }, + { id: '20', name: 'Watermelon', type: 'chonky' }, + { id: '22', name: 'Yellow Passion Fruit', type: 'seedy' }, + { id: '23', name: 'Zucchini', type: 'longish' }, + { id: '24', name: 'Cantaloupe', type: 'chonky' }, + { id: '25', name: 'Blackberry', type: 'berryish' }, + { id: '26', name: 'Persimmon', type: 'seedy' }, + { id: '27', name: 'Lychee', type: 'berryish' }, + { id: '28', name: 'Dragon Fruit', type: 'seedy' }, + { id: '29', name: 'Passion Fruit', type: 'seedy' }, + { id: '30', name: 'Starfruit', type: 'seedy' }, ]; - type Fruit = (typeof items)[number]; const getItemId = useCallback((item: Fruit) => item.id, []); const getIsItemDisabled = useCallback((item: Fruit) => !!item.disabled, []); @@ -66,12 +72,16 @@ const ComboboxDemo: React.FC = () => { ) : items; + const groupedResults = withGroups + ? Object.groupBy(results, (item) => item.type) + : results; + return ( ; -export const Example: Story = { - args: { - // Adding these types to keep TS happy, they're not passed on to `ComboboxDemo` - label: '', - value: '', - onChange: () => undefined, - items: [], - getItemId: () => '', - renderItem: () => <>Nothing, - onSelectItem: () => undefined, - }, +// These args are just used to keep TS happy, +// they're not passed to `ComboboxDemo` +const dummyArgs = { + label: '', + value: '', + onChange: () => undefined, + items: [], + getItemId: () => '', + renderItem: () => <>Nothing, + onSelectItem: () => undefined, +}; + +export const Simple: Story = { + args: dummyArgs, +}; + +export const WithGroups: Story = { + render: () => , + args: dummyArgs, }; diff --git a/app/javascript/mastodon/components/form_fields/combobox_field.tsx b/app/javascript/mastodon/components/form_fields/combobox_field.tsx index 057258847e..6cc68b90bf 100644 --- a/app/javascript/mastodon/components/form_fields/combobox_field.tsx +++ b/app/javascript/mastodon/components/form_fields/combobox_field.tsx @@ -1,4 +1,11 @@ -import { forwardRef, useCallback, useId, useRef, useState } from 'react'; +import { + forwardRef, + useCallback, + useId, + useMemo, + useRef, + useState, +} from 'react'; import { useIntl } from 'react-intl'; @@ -28,10 +35,10 @@ export interface ComboboxItemState { isDisabled: boolean; } -interface ComboboxProps extends Omit< - TextInputProps, - 'icon' -> { +interface ComboboxProps< + Item extends ComboboxItem, + GroupKey extends string, +> extends Omit { /** * The value of the combobox's text input */ @@ -46,34 +53,44 @@ interface ComboboxProps extends Omit< */ isLoading?: boolean; /** - * The set of options/suggestions that should be rendered in the dropdown menu. + * The set of options/suggestions that should be rendered in the dropdown menu, + * optionally separated into groups by providing an object */ - items: T[]; + items: Item[] | Partial>; /** * A function that must return a unique id for each option passed via `items` */ - getItemId?: (item: T) => string; + getItemId?: (item: Item) => string; /** * Providing this function turns the combobox into a multi-select box that assumes * multiple options to be selectable. Single-selection is handled automatically. */ - getIsItemSelected?: (item: T) => boolean; + getIsItemSelected?: (item: Item) => boolean; /** * Use this function to mark items as disabled, if needed */ - getIsItemDisabled?: (item: T) => boolean; + getIsItemDisabled?: (item: Item) => boolean; /** * Customise the rendering of each option. * The rendered content must not contain other interactive content! */ renderItem: ( - item: T, + item: Item, state: ComboboxItemState, ) => React.ReactElement | string; + /** + * Customise the rendering of group titles. + * The `titleId` must be attached to the element that provides the + * accessible name for the group. + */ + renderGroupTitle?: ( + groupKey: GroupKey, + titleId: string, + ) => React.ReactElement | string; /** * The main selection handler, called when an option is selected or deselected. */ - onSelectItem: (item: T) => void; + onSelectItem: (item: Item) => void; /** * Icon to be displayed in the text input */ @@ -88,8 +105,8 @@ interface ComboboxProps extends Omit< suppressMenu?: boolean; } -interface Props - extends ComboboxProps, CommonFieldWrapperProps {} +interface Props + extends ComboboxProps, CommonFieldWrapperProps {} /** * The combobox field allows users to select one or more items @@ -100,8 +117,11 @@ interface Props * [research & implementations](https://sarahmhigley.com/writing/select-your-poison/). */ -export const ComboboxFieldWithRef = ( - { id, label, hint, status, required, ...otherProps }: Props, +export const ComboboxFieldWithRef = < + Item extends ComboboxItem, + GroupKey extends string, +>( + { id, label, hint, status, required, ...otherProps }: Props, ref: React.ForwardedRef, ) => ( ( // Using a type assertion to maintain the full type signature of ComboboxWithRef // (including its generic type) after wrapping it with `forwardRef`. export const ComboboxField = forwardRef(ComboboxFieldWithRef) as { - ( - props: Props & { ref?: React.ForwardedRef }, + ( + props: Props & { + ref?: React.ForwardedRef; + }, ): ReturnType; displayName: string; }; ComboboxField.displayName = 'ComboboxField'; -const ComboboxWithRef = ( +const ComboboxWithRef = ( { value, isLoading = false, @@ -135,6 +157,7 @@ const ComboboxWithRef = ( getIsItemDisabled, getIsItemSelected, disabled, + renderGroupTitle, renderItem, onSelectItem, onChange, @@ -144,7 +167,7 @@ const ComboboxWithRef = ( icon = SearchIcon, className, ...otherProps - }: ComboboxProps, + }: ComboboxProps, ref: React.ForwardedRef, ) => { const intl = useIntl(); @@ -157,15 +180,23 @@ const ComboboxWithRef = ( ); const [shouldMenuOpen, setShouldMenuOpen] = useState(false); + const hasGroups = !Array.isArray(items); + const flatItems = useMemo( + () => (hasGroups ? (Object.values(items).flat() as Item[]) : items), + [hasGroups, items], + ); + const statusMessage = useGetA11yStatusMessage({ value, isLoading, - itemCount: items.length, + itemCount: flatItems.length, }); const showStatusMessageInMenu = - !!statusMessage && value.length > 0 && items.length === 0; + !!statusMessage && value.length > 0 && flatItems.length === 0; const hasMenuContent = - !disabled && !suppressMenu && (items.length > 0 || showStatusMessageInMenu); + !disabled && + !suppressMenu && + (flatItems.length > 0 || showStatusMessageInMenu); const isMenuOpen = shouldMenuOpen && hasMenuContent; const openMenu = useCallback(() => { @@ -178,10 +209,10 @@ const ComboboxWithRef = ( }, []); const resetHighlight = useCallback(() => { - const firstItem = items[0]; + const firstItem = flatItems[0]; const firstItemId = firstItem ? getItemId(firstItem) : null; setHighlightedItemId(firstItemId); - }, [getItemId, items]); + }, [getItemId, flatItems]); const highlightItem = useCallback((id: string | null) => { setHighlightedItemId(id); @@ -216,7 +247,7 @@ const ComboboxWithRef = ( const selectItem = useCallback( (itemId: string | null) => { - const item = items.find((item) => item.id === itemId); + const item = flatItems.find((item) => item.id === itemId); if (item) { const isDisabled = getIsItemDisabled?.(item) ?? false; if (!isDisabled) { @@ -229,7 +260,7 @@ const ComboboxWithRef = ( } inputRef.current?.focus(); }, - [closeMenu, closeOnSelect, getIsItemDisabled, items, onSelectItem], + [closeMenu, closeOnSelect, getIsItemDisabled, flatItems, onSelectItem], ); const handleSelectItem = useCallback( @@ -246,38 +277,38 @@ const ComboboxWithRef = ( const moveHighlight = useCallback( (direction: number) => { - if (items.length === 0) { + if (flatItems.length === 0) { return; } - const highlightedItemIndex = items.findIndex( + const highlightedItemIndex = flatItems.findIndex( (item) => getItemId(item) === highlightedItemId, ); if (highlightedItemIndex === -1) { // If no item is highlighted yet, highlight the first or last if (direction > 0) { - const firstItem = items.at(0); + const firstItem = flatItems.at(0); highlightItem(firstItem ? getItemId(firstItem) : null); } else { - const lastItem = items.at(-1); + const lastItem = flatItems.at(-1); highlightItem(lastItem ? getItemId(lastItem) : null); } } else { // If there is a highlighted item, select the next or previous item // and wrap around at the start or end: let newIndex = highlightedItemIndex + direction; - if (newIndex >= items.length) { + if (newIndex >= flatItems.length) { newIndex = 0; } else if (newIndex < 0) { - newIndex = items.length - 1; + newIndex = flatItems.length - 1; } - const newHighlightedItem = items[newIndex]; + const newHighlightedItem = flatItems[newIndex]; highlightItem( newHighlightedItem ? getItemId(newHighlightedItem) : null, ); } }, - [getItemId, highlightItem, highlightedItemId, items], + [getItemId, highlightItem, highlightedItemId, flatItems], ); useOnClickOutside(wrapperRef, closeMenu); @@ -331,6 +362,38 @@ const ComboboxWithRef = ( ], ); + const renderItems = (items: Item[]) => + items.map((item) => { + const id = getItemId(item); + const isDisabled = getIsItemDisabled?.(item); + const isHighlighted = id === highlightedItemId; + // If `getIsItemSelected` is defined, we assume 'multi-select' + // behaviour and don't set `aria-selected` based on highlight, + // but based on selected item state. + const isSelected = getIsItemSelected + ? getIsItemSelected(item) + : isHighlighted; + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events +
  • + {renderItem(item, { + isSelected, + isDisabled: isDisabled ?? false, + })} +
  • + ); + }); + const mergeRefs = useCallback( (element: HTMLInputElement | null) => { inputRef.current = element; @@ -406,42 +469,42 @@ const ComboboxWithRef = ( > {({ props, placement }) => (
    - {showStatusMessageInMenu ? ( - {statusMessage} - ) : ( -
      - {items.map((item) => { - const id = getItemId(item); - const isDisabled = getIsItemDisabled?.(item); - const isHighlighted = id === highlightedItemId; - // If `getIsItemSelected` is defined, we assume 'multi-select' - // behaviour and don't set `aria-selected` based on highlight, - // but based on selected item state. - const isSelected = getIsItemSelected - ? getIsItemSelected(item) - : isHighlighted; - return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events -
    • - {renderItem(item, { - isSelected, - isDisabled: isDisabled ?? false, - })} -
    • - ); - })} -
    - )} + + {hasGroups ? ( +
    + {(Object.keys(items) as GroupKey[]).map((groupKey) => { + const groupItems = items[groupKey]; + const groupTitleId = `${listId}-group-${groupKey}`; + const groupTitle = renderGroupTitle?.( + groupKey, + groupTitleId, + ) ?? {groupKey}; + + if (!groupItems?.length) return null; + + return ( +
      +
    • + {groupTitle} +
    • + {renderItems(groupItems)} +
    + ); + })} +
    + ) : ( +
      + {renderItems(items)} +
    + )} +
    )} @@ -452,14 +515,28 @@ const ComboboxWithRef = ( // Using a type assertion to maintain the full type signature of ComboboxWithRef // (including its generic type) after wrapping it with `forwardRef`. export const Combobox = forwardRef(ComboboxWithRef) as { - ( - props: ComboboxProps & { ref?: React.ForwardedRef }, + ( + props: ComboboxProps & { + ref?: React.ForwardedRef; + }, ): ReturnType; displayName: string; }; Combobox.displayName = 'Combobox'; +const StatusMessageWrapper: React.FC<{ + showStatus: boolean; + status: string; + children: React.ReactNode; +}> = ({ showStatus, status, children }) => { + if (showStatus) { + return {status}; + } + + return children; +}; + function useGetA11yStatusMessage({ itemCount, value, diff --git a/app/javascript/mastodon/features/collections/editor/accounts.tsx b/app/javascript/mastodon/features/collections/editor/accounts.tsx index 3fc26f4cfd..da99bdbae3 100644 --- a/app/javascript/mastodon/features/collections/editor/accounts.tsx +++ b/app/javascript/mastodon/features/collections/editor/accounts.tsx @@ -4,6 +4,7 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; +import type { ApiMutedAccountJSON } from 'mastodon/api_types/accounts'; import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; import { AccountListItem } from 'mastodon/components/account_list_item'; import { Avatar } from 'mastodon/components/avatar'; @@ -54,13 +55,10 @@ const AddedAccountItem: React.FC<{ return ; }; -interface SuggestionItem { - id: string; - isDisabled?: boolean; -} - -const SuggestedAccountItem: React.FC = ({ id }) => { - const account = useAccount(id); +const SuggestedAccountItem: React.FC<{ account: ApiMutedAccountJSON }> = ( + props, +) => { + const account = useAccount(props.account.id); if (!account) return null; @@ -72,12 +70,15 @@ const SuggestedAccountItem: React.FC = ({ id }) => { ); }; -const renderAccountItem = (item: SuggestionItem) => ( - +const renderAccountItem = (account: ApiMutedAccountJSON) => ( + ); -const getItemId = (item: SuggestionItem) => item.id; -const getIsItemDisabled = (item: SuggestionItem) => item.isDisabled ?? false; +const getItemId = (account: ApiMutedAccountJSON) => account.id; + +// Disable accounts who can't be added to a collection +const getIsItemDisabled = (account: ApiMutedAccountJSON) => + !['automatic', 'manual'].includes(account.feature_approval.current_user); export const CollectionAccounts: React.FC<{ collection?: ApiCollectionJSON | null; @@ -117,14 +118,6 @@ export const CollectionAccounts: React.FC<{ filterResults: (account) => !accountIds.includes(account.id), }); - const suggestedItems = suggestedAccounts.map(({ id, feature_approval }) => ({ - id, - // Disable accounts who can't be added to a collection - isDisabled: !['automatic', 'manual'].includes( - feature_approval.current_user, - ), - })); - const handleSearchValueChange = useCallback( (e: React.ChangeEvent) => { setSearchValue(e.target.value); @@ -155,7 +148,7 @@ export const CollectionAccounts: React.FC<{ ); const addAccountItem = useCallback( - (item: SuggestionItem) => { + (item: ApiMutedAccountJSON) => { dispatch( updateCollectionEditorField({ field: 'accountIds', @@ -189,7 +182,7 @@ export const CollectionAccounts: React.FC<{ ); const instantAddAccountItem = useCallback( - (item: SuggestionItem) => { + (item: ApiMutedAccountJSON) => { if (id) { void dispatch( addCollectionItem({ collectionId: id, accountId: item.id }), @@ -211,7 +204,7 @@ export const CollectionAccounts: React.FC<{ ); const handleSelectItem = useCallback( - (item: SuggestionItem) => { + (item: ApiMutedAccountJSON) => { if (isEditMode) { instantAddAccountItem(item); } else { @@ -266,7 +259,7 @@ export const CollectionAccounts: React.FC<{ onKeyDown={handleSearchKeyDown} disabled={hasMaxAccounts} isLoading={isLoadingSuggestions} - items={suggestedItems} + items={suggestedAccounts} getItemId={getItemId} getIsItemDisabled={getIsItemDisabled} renderItem={renderAccountItem} From 5722b1bbc5652c2f1cc2b6e974df17183a90b7ca Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 17 Apr 2026 09:25:39 -0400 Subject: [PATCH 23/37] Remove invalid options from recovery codes controller (#38733) --- .../two_factor_authentication/recovery_codes_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb index 6ec53224d3..565409612c 100644 --- a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb +++ b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb @@ -7,7 +7,7 @@ module Settings skip_before_action :require_functional! - before_action :require_challenge!, on: :create + before_action :require_challenge! def create @recovery_codes = current_user.generate_otp_backup_codes! From b846f88e16b6cd13706042fb041f7ca5d953e6b7 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Fri, 17 Apr 2026 15:28:39 +0200 Subject: [PATCH 24/37] Improve collection item behavior in REST API (#38732) --- app/models/collection.rb | 1 + app/models/collection_item.rb | 1 + .../rest/collection_item_serializer.rb | 8 +++--- app/serializers/rest/collection_serializer.rb | 6 ++++- spec/models/collection_spec.rb | 25 ++++++++++++++---- .../rest/collection_item_serializer_spec.rb | 24 ++++++++++++----- .../rest/collection_serializer_spec.rb | 26 +++++++++++++++++++ 7 files changed, 75 insertions(+), 16 deletions(-) diff --git a/app/models/collection.rb b/app/models/collection.rb index 5956b7c087..8d372eb8f4 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -65,6 +65,7 @@ class Collection < ApplicationRecord def items_for(account = nil) result = collection_items.with_accounts + result = account == self.account ? result.pending_or_accepted : result.accepted result = result.not_blocked_by(account) unless account.nil? result end diff --git a/app/models/collection_item.rb b/app/models/collection_item.rb index c2ba2e9b68..189cc29e69 100644 --- a/app/models/collection_item.rb +++ b/app/models/collection_item.rb @@ -43,6 +43,7 @@ class CollectionItem < ApplicationRecord scope :not_blocked_by, ->(account) { where.not(accounts: { id: account.blocking }) } scope :local, -> { joins(:collection).merge(Collection.local) } scope :accepted_partial, ->(account) { joins(:account).merge(Account.local).accepted.where(uri: nil, account_id: account.id) } + scope :pending_or_accepted, -> { where(state: [:pending, :accepted]) } def revoke! update!(state: :revoked) diff --git a/app/serializers/rest/collection_item_serializer.rb b/app/serializers/rest/collection_item_serializer.rb index 49cd8c7a0e..4d75910a7a 100644 --- a/app/serializers/rest/collection_item_serializer.rb +++ b/app/serializers/rest/collection_item_serializer.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true class REST::CollectionItemSerializer < ActiveModel::Serializer - delegate :accepted?, to: :object - attributes :id, :state, :created_at - attribute :account_id, if: :accepted? + attribute :account_id, if: :accepted_or_pending? def id object.id.to_s @@ -14,4 +12,8 @@ class REST::CollectionItemSerializer < ActiveModel::Serializer def account_id object.account_id.to_s end + + def accepted_or_pending? + object.pending? || object.accepted? + end end diff --git a/app/serializers/rest/collection_serializer.rb b/app/serializers/rest/collection_serializer.rb index c9696b9aef..c1c2c70d32 100644 --- a/app/serializers/rest/collection_serializer.rb +++ b/app/serializers/rest/collection_serializer.rb @@ -29,7 +29,11 @@ class REST::CollectionSerializer < ActiveModel::Serializer end def items - object.items_for(current_user&.account) + @items ||= object.items_for(current_user&.account) + end + + def item_count + items.size end def account_id diff --git a/spec/models/collection_spec.rb b/spec/models/collection_spec.rb index 64e93b6797..619359a19d 100644 --- a/spec/models/collection_spec.rb +++ b/spec/models/collection_spec.rb @@ -64,11 +64,18 @@ RSpec.describe Collection do describe '#item_for' do subject { Fabricate(:collection) } - let!(:items) { Fabricate.times(2, :collection_item, collection: subject) } + let!(:accepted_items) { Fabricate.times(2, :collection_item, collection: subject, state: :accepted) } + let!(:pending_item) { Fabricate(:collection_item, collection: subject, state: :pending) } + + before do + %i(rejected revoked).each do |state| + Fabricate(:collection_item, collection: subject, state:) + end + end context 'when given no account' do - it 'returns all items' do - expect(subject.items_for).to match_array(items) + it 'returns all accepted items' do + expect(subject.items_for).to match_array(accepted_items) end end @@ -76,11 +83,19 @@ RSpec.describe Collection do let(:account) { Fabricate(:account) } before do - account.block!(items.first.account) + account.block!(accepted_items.first.account) end it 'does not return items blocked by this account' do - expect(subject.items_for(account)).to contain_exactly(items.last) + expect(subject.items_for(account)).to contain_exactly(accepted_items.last) + end + end + + context 'when given the owner of the collection' do + let(:account) { subject.account } + + it 'returns accepted and pending items' do + expect(subject.items_for(account)).to match_array(accepted_items + [pending_item]) end end end diff --git a/spec/serializers/rest/collection_item_serializer_spec.rb b/spec/serializers/rest/collection_item_serializer_spec.rb index a16af9bce0..b57daf1157 100644 --- a/spec/serializers/rest/collection_item_serializer_spec.rb +++ b/spec/serializers/rest/collection_item_serializer_spec.rb @@ -11,23 +11,33 @@ RSpec.describe REST::CollectionItemSerializer do state:) end - context 'when state is `accepted`' do - let(:state) { :accepted } - + shared_examples 'full result' do it 'includes the relevant attributes including the account' do expect(subject) .to include( 'id' => '2342', 'account_id' => collection_item.account_id.to_s, - 'state' => 'accepted', + 'state' => state.to_s, 'created_at' => match_api_datetime_format ) end end - %i(pending rejected revoked).each do |unaccepted_state| - context "when state is `#{unaccepted_state}`" do - let(:state) { unaccepted_state } + context 'when state is `accepted`' do + let(:state) { :accepted } + + it_behaves_like 'full result' + end + + context 'when state is `pending`' do + let(:state) { :pending } + + it_behaves_like 'full result' + end + + %i(rejected revoked).each do |rejected_state| + context "when state is `#{rejected_state}`" do + let(:state) { rejected_state } it 'does not include an account' do expect(subject.keys).to_not include('account_id') diff --git a/spec/serializers/rest/collection_serializer_spec.rb b/spec/serializers/rest/collection_serializer_spec.rb index dbb9803753..8055c2afbe 100644 --- a/spec/serializers/rest/collection_serializer_spec.rb +++ b/spec/serializers/rest/collection_serializer_spec.rb @@ -67,4 +67,30 @@ RSpec.describe REST::CollectionSerializer do end end end + + context 'when the collection has items in different states' do + before do + %i(accepted pending rejected revoked).each do |state| + Fabricate(:collection_item, collection:, state:) + end + end + + context 'when `current_user` is the owner of the collection' do + let(:current_user) { collection.account.user } + + it 'includes accepted and pending items' do + expect(subject['item_count']).to eq 2 + expect(subject['items'].size).to eq 2 + expect(subject['items'].pluck('state')).to contain_exactly('accepted', 'pending') + end + end + + context 'when `current_user` is not the owner of the collection' do + it 'only includes the accepted item' do + expect(subject['item_count']).to eq 1 + expect(subject['items'].size).to eq 1 + expect(subject['items'].first['state']).to eq 'accepted' + end + end + end end From 475e6833ffc77724926edd5cb275a8e2369e0c23 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 17 Apr 2026 09:31:14 -0400 Subject: [PATCH 25/37] Update to copy and order for media display options (#38731) Co-authored-by: Claire --- app/models/user_settings.rb | 2 +- config/locales/simple_form.en.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index 3a6e6f249b..1590ce4e89 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -35,7 +35,7 @@ class UserSettings setting :missing_alt_text_modal, default: true setting :reduce_motion, default: false setting :expand_content_warnings, default: false - setting :display_media, default: 'default', in: %w(default show_all hide_all) + setting :display_media, default: 'default', in: %w(hide_all default show_all) setting :auto_play, default: false setting :emoji_style, default: 'auto', in: %w(auto native twemoji) setting :color_scheme, default: 'auto', in: %w(auto light dark) diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 7102746076..72f6548532 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -61,9 +61,9 @@ en: setting_default_quote_policy_private: Followers-only posts authored on Mastodon can't be quoted by others. setting_default_quote_policy_unlisted: When people quote you, their post will also be hidden from trending timelines. setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click - setting_display_media_default: Hide media marked as sensitive - setting_display_media_hide_all: Always hide media - setting_display_media_show_all: Always show media + setting_display_media_default: Warn before showing media marked as sensitive + setting_display_media_hide_all: Warn before showing all media + setting_display_media_show_all: Show all media without warning, including media marked as sensitive setting_emoji_style: How to display emojis. "Auto" will try using native emoji, but falls back to Twemoji for legacy browsers. setting_quick_boosting_html: When enabled, clicking on the %{boost_icon} Boost icon will immediately boost instead of opening the boost/quote dropdown menu. Relocates the quoting action to the %{options_icon} (Options) menu. setting_system_scrollbars_ui: Applies only to desktop browsers based on Safari and Chrome From 9afaa23e78bea086b2c70f20abbaa6946725b167 Mon Sep 17 00:00:00 2001 From: Shlee Date: Fri, 17 Apr 2026 23:06:38 +0930 Subject: [PATCH 26/37] Fix incorrect `only` option in `before_validation` filters (#38704) --- app/models/block.rb | 2 +- app/models/collection_item.rb | 2 +- app/models/follow.rb | 2 +- app/models/follow_request.rb | 2 +- app/models/quote.rb | 2 +- app/models/report.rb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/block.rb b/app/models/block.rb index 662cc1ac20..a5e8ad7ffa 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -25,7 +25,7 @@ class Block < ApplicationRecord false # Force uri_for to use uri attribute end - before_validation :set_uri, only: :create + before_validation :set_uri, on: :create after_commit :invalidate_blocking_cache after_commit :invalidate_follow_recommendations_cache diff --git a/app/models/collection_item.rb b/app/models/collection_item.rb index 189cc29e69..cc77583674 100644 --- a/app/models/collection_item.rb +++ b/app/models/collection_item.rb @@ -36,7 +36,7 @@ class CollectionItem < ApplicationRecord validates :uri, presence: true, if: :remote_item_with_remote_account? before_validation :set_position, on: :create - before_validation :set_activity_uri, only: :create, if: :local_item_with_remote_account? + before_validation :set_activity_uri, on: :create, if: :local_item_with_remote_account? scope :ordered, -> { order(position: :asc) } scope :with_accounts, -> { includes(account: [:account_stat, :user]) } diff --git a/app/models/follow.rb b/app/models/follow.rb index c8930c2f86..e8da0077c9 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -42,7 +42,7 @@ class Follow < ApplicationRecord destroy! end - before_validation :set_uri, only: :create + before_validation :set_uri, on: :create after_create :increment_cache_counters after_destroy :remove_endorsements after_destroy :decrement_cache_counters diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index 906de95db2..5d8b5f9c08 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -51,7 +51,7 @@ class FollowRequest < ApplicationRecord false # Force uri_for to use uri attribute end - before_validation :set_uri, only: :create + before_validation :set_uri, on: :create after_commit :invalidate_follow_recommendations_cache private diff --git a/app/models/quote.rb b/app/models/quote.rb index 44c3599a7b..c49a66c278 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -35,7 +35,7 @@ class Quote < ApplicationRecord belongs_to :quoted_account, class_name: 'Account', optional: true before_validation :set_accounts - before_validation :set_activity_uri, only: :create, if: -> { account.local? && quoted_account&.remote? } + before_validation :set_activity_uri, on: :create, if: -> { account.local? && quoted_account&.remote? } validates :activity_uri, presence: true, if: -> { account.local? && quoted_account&.remote? } validates :approval_uri, absence: true, if: -> { quoted_account&.local? } validate :validate_visibility diff --git a/app/models/report.rb b/app/models/report.rb index 282a1f7570..117005c862 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -68,7 +68,7 @@ class Report < ApplicationRecord violation: 2_000, } - before_validation :set_uri, only: :create + before_validation :set_uri, on: :create after_create_commit :trigger_create_webhooks after_update_commit :trigger_update_webhooks From 1d3ca80bf7a0ec7bb17b5c9e4905e24a3bca0872 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 17 Apr 2026 09:57:18 -0400 Subject: [PATCH 27/37] Use model constants more consistently for view expiration collections (#38589) --- app/helpers/invites_helper.rb | 11 ----------- app/models/invite.rb | 2 ++ app/models/ip_block.rb | 2 ++ app/views/admin/ip_blocks/new.html.haml | 2 +- app/views/invites/_form.html.haml | 15 ++++++++++++--- 5 files changed, 17 insertions(+), 15 deletions(-) delete mode 100644 app/helpers/invites_helper.rb diff --git a/app/helpers/invites_helper.rb b/app/helpers/invites_helper.rb deleted file mode 100644 index c189061db0..0000000000 --- a/app/helpers/invites_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module InvitesHelper - def invites_max_uses_options - [1, 5, 10, 25, 50, 100] - end - - def invites_expires_options - [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week] - end -end diff --git a/app/models/invite.rb b/app/models/invite.rb index ca692d937e..8f3463a322 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -21,7 +21,9 @@ class Invite < ApplicationRecord COMMENT_SIZE_LIMIT = 420 ELIGIBLE_CODE_CHARACTERS = [*('a'..'z'), *('A'..'Z'), *('0'..'9')].freeze + EXPIRATION_DURATIONS = [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].freeze HOMOGLYPHS = %w(0 1 I l O).freeze + MAX_USES_COUNTS = [1, 5, 10, 25, 50, 100].freeze VALID_CODE_CHARACTERS = (ELIGIBLE_CODE_CHARACTERS - HOMOGLYPHS).freeze belongs_to :user, inverse_of: :invites diff --git a/app/models/ip_block.rb b/app/models/ip_block.rb index 5bbfc1fd24..6d766c8d0a 100644 --- a/app/models/ip_block.rb +++ b/app/models/ip_block.rb @@ -20,6 +20,8 @@ class IpBlock < ApplicationRecord include InetContainer include Paginable + EXPIRATION_DURATIONS = [1.day, 2.weeks, 1.month, 6.months, 1.year, 3.years].freeze + enum :severity, { sign_up_requires_approval: 5000, sign_up_block: 5500, diff --git a/app/views/admin/ip_blocks/new.html.haml b/app/views/admin/ip_blocks/new.html.haml index acf632e476..3ef5351d3e 100644 --- a/app/views/admin/ip_blocks/new.html.haml +++ b/app/views/admin/ip_blocks/new.html.haml @@ -12,7 +12,7 @@ .fields-group = f.input :expires_in, - collection: [1.day, 2.weeks, 1.month, 6.months, 1.year, 3.years].map(&:to_i), + collection: IpBlock::EXPIRATION_DURATIONS.map(&:to_i), label_method: ->(i) { I18n.t("admin.ip_blocks.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt'), wrapper: :with_block_label diff --git a/app/views/invites/_form.html.haml b/app/views/invites/_form.html.haml index dbbb785e83..514b9cf016 100644 --- a/app/views/invites/_form.html.haml +++ b/app/views/invites/_form.html.haml @@ -2,12 +2,21 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = form.input :max_uses, wrapper: :with_label, collection: invites_max_uses_options, label_method: ->(num) { I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt') + = form.input :max_uses, + collection: Invite::MAX_USES_COUNTS, + label_method: ->(count) { I18n.t('invites.max_uses', count:) }, + prompt: I18n.t('invites.max_uses_prompt'), + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = form.input :expires_in, wrapper: :with_label, collection: invites_expires_options.map(&:to_i), label_method: ->(i) { I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') + = form.input :expires_in, + collection: Invite::EXPIRATION_DURATIONS.map(&:to_i), + label_method: ->(duration) { I18n.t("invites.expires_in.#{duration}") }, + prompt: I18n.t('invites.expires_in_prompt'), + wrapper: :with_label .fields-group - = form.input :autofollow, wrapper: :with_label + = form.input :autofollow, + wrapper: :with_label .actions = form.button :button, t('invites.generate'), type: :submit From ea33d7fba6325719763a76fde76aa724baf3ce5f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 17 Apr 2026 10:01:07 -0400 Subject: [PATCH 28/37] Add `AccountMigration#remaining_cooldown_days` method (#38561) --- app/models/account_migration.rb | 4 ++++ app/views/settings/migrations/show.html.haml | 2 +- spec/models/account_migration_spec.rb | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/models/account_migration.rb b/app/models/account_migration.rb index c2c6355a18..ffa74cd21f 100644 --- a/app/models/account_migration.rb +++ b/app/models/account_migration.rb @@ -60,6 +60,10 @@ class AccountMigration < ApplicationRecord created_at + COOLDOWN_PERIOD end + def remaining_cooldown_days + ((cooldown_at - Time.current) / 1.day).ceil + end + private def set_target_account diff --git a/app/views/settings/migrations/show.html.haml b/app/views/settings/migrations/show.html.haml index 296075a28e..8c3bae704f 100644 --- a/app/views/settings/migrations/show.html.haml +++ b/app/views/settings/migrations/show.html.haml @@ -24,7 +24,7 @@ = simple_form_for @migration, url: settings_migration_path do |f| - if on_cooldown? %p.hint - %span.warning-hint= t('migrations.on_cooldown', count: ((@cooldown.cooldown_at - Time.now.utc) / 1.day.seconds).ceil) + %span.warning-hint= t('migrations.on_cooldown', count: @cooldown.remaining_cooldown_days) - else %p.hint= t('migrations.warning.before') diff --git a/spec/models/account_migration_spec.rb b/spec/models/account_migration_spec.rb index 1bb238f7ef..55d299799c 100644 --- a/spec/models/account_migration_spec.rb +++ b/spec/models/account_migration_spec.rb @@ -51,4 +51,24 @@ RSpec.describe AccountMigration do it { is_expected.to_not allow_value(target_acct).for(:acct) } end end + + describe '#remaining_cooldown_days' do + subject { account_migration.remaining_cooldown_days } + + before { stub_const('AccountMigration::COOLDOWN_PERIOD', 30.days) } + + let(:account_migration) { Fabricate :account_migration, created_at: } + + context 'with a record still in cooldown' do + let(:created_at) { 15.days.ago } + + it { is_expected.to eq(15) } + end + + context 'with a record out of cooldown' do + let(:created_at) { 150.days.ago } + + it { is_expected.to be_negative } + end + end end From 05a1c170c285221860e27cb3239092401c3cca8b Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 17 Apr 2026 17:13:18 +0200 Subject: [PATCH 29/37] Update design of account search dropdown in collection editor (#38739) --- .../form_fields/combobox.module.scss | 10 +- .../components/form_fields/combobox_field.tsx | 30 ++++-- .../mastodon/components/list_item/index.tsx | 67 ++++++++------ .../list_item/list_item.stories.tsx | 17 +++- .../features/collections/editor/accounts.tsx | 92 +++++++++++++++++-- .../features/collections/editor/details.tsx | 22 ++--- .../collections/editor/styles.module.scss | 16 ++++ app/javascript/mastodon/locales/en.json | 4 + 8 files changed, 192 insertions(+), 66 deletions(-) diff --git a/app/javascript/mastodon/components/form_fields/combobox.module.scss b/app/javascript/mastodon/components/form_fields/combobox.module.scss index ca7bc2020b..ef230a77b8 100644 --- a/app/javascript/mastodon/components/form_fields/combobox.module.scss +++ b/app/javascript/mastodon/components/form_fields/combobox.module.scss @@ -25,7 +25,7 @@ .popover { z-index: 9999; box-sizing: border-box; - max-height: max(200px, 30dvh); + max-height: min(320px, 50dvh); padding: 4px; border-radius: 4px; color: var(--color-text-primary); @@ -63,13 +63,7 @@ user-select: none; &[data-highlighted='true'] { - color: var(--color-text-on-brand-base); - background: var(--color-bg-brand-base); - - &[aria-disabled='true'] { - color: var(--color-text-on-disabled); - background: var(--color-bg-disabled); - } + background: var(--color-bg-overlay-highlight); } &[aria-disabled='true'] { diff --git a/app/javascript/mastodon/components/form_fields/combobox_field.tsx b/app/javascript/mastodon/components/form_fields/combobox_field.tsx index 6cc68b90bf..64cbb3ce8a 100644 --- a/app/javascript/mastodon/components/form_fields/combobox_field.tsx +++ b/app/javascript/mastodon/components/form_fields/combobox_field.tsx @@ -82,11 +82,12 @@ interface ComboboxProps< * Customise the rendering of group titles. * The `titleId` must be attached to the element that provides the * accessible name for the group. + * Return `null` to omit rendering the group title. */ renderGroupTitle?: ( groupKey: GroupKey, titleId: string, - ) => React.ReactElement | string; + ) => React.ReactElement | null; /** * The main selection handler, called when an option is selected or deselected. */ @@ -182,7 +183,12 @@ const ComboboxWithRef = ( const hasGroups = !Array.isArray(items); const flatItems = useMemo( - () => (hasGroups ? (Object.values(items).flat() as Item[]) : items), + () => + hasGroups + ? (Object.values(items) + .flat() + .filter((i) => !!i) as Item[]) + : items, [hasGroups, items], ); @@ -478,10 +484,11 @@ const ComboboxWithRef = ( {(Object.keys(items) as GroupKey[]).map((groupKey) => { const groupItems = items[groupKey]; const groupTitleId = `${listId}-group-${groupKey}`; - const groupTitle = renderGroupTitle?.( + const customGroupTitle = renderGroupTitle?.( groupKey, groupTitleId, - ) ?? {groupKey}; + ); + const hasTitle = customGroupTitle !== null; if (!groupItems?.length) return null; @@ -489,11 +496,18 @@ const ComboboxWithRef = (
      -
    • - {groupTitle} -
    • + {hasTitle && ( +
    • + {customGroupTitle ?? ( + {groupKey} + )} +
    • + )} {renderItems(groupItems)}
    ); diff --git a/app/javascript/mastodon/components/list_item/index.tsx b/app/javascript/mastodon/components/list_item/index.tsx index 66758bd4de..263e206fb9 100644 --- a/app/javascript/mastodon/components/list_item/index.tsx +++ b/app/javascript/mastodon/components/list_item/index.tsx @@ -14,8 +14,9 @@ interface WrapperProps extends Omit< /** * A basic list item component that can be used as a base for more bespoke list items. * - * Depending on functionality, use `ListItemButton` or `ListItemLink` as a child of the - * wrapper component. + * Choose the child of the wrapper component based on needed interactivity: + * `ListItemContent` for a non-interactive item, `ListItemButton` or `ListItemLink` + * for interactive items. */ export const ListItemWrapper: React.FC = ({ icon, @@ -33,10 +34,31 @@ export const ListItemWrapper: React.FC = ({ ); }; -interface LinkProps extends React.ComponentPropsWithoutRef { +interface WithSubtitle { subtitle?: React.ReactNode; } +interface ContentProps + extends React.ComponentPropsWithoutRef<'h3'>, WithSubtitle {} + +export const ListItemContent: React.FC = ({ + subtitle, + children, + ...otherProps +}) => { + return ( + <> +

    + {children} +

    + {subtitle &&
    {subtitle}
    } + + ); +}; + +interface LinkProps + extends React.ComponentPropsWithoutRef, WithSubtitle {} + export const ListItemLink: React.FC = ({ subtitle, children, @@ -44,20 +66,16 @@ export const ListItemLink: React.FC = ({ ...otherProps }) => { return ( - <> -

    - - {children} - -

    - {subtitle &&
    {subtitle}
    } - + + + {children} + + ); }; -interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> { - subtitle?: React.ReactNode; -} +interface ButtonProps + extends React.ComponentPropsWithoutRef<'button'>, WithSubtitle {} export const ListItemButton: React.FC = ({ subtitle, @@ -66,17 +84,14 @@ export const ListItemButton: React.FC = ({ ...otherProps }) => { return ( - <> -

    - -

    - {subtitle &&
    {subtitle}
    } - + + + ); }; diff --git a/app/javascript/mastodon/components/list_item/list_item.stories.tsx b/app/javascript/mastodon/components/list_item/list_item.stories.tsx index d46102c1c3..dd066683c9 100644 --- a/app/javascript/mastodon/components/list_item/list_item.stories.tsx +++ b/app/javascript/mastodon/components/list_item/list_item.stories.tsx @@ -7,18 +7,31 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac import { Icon } from '../icon'; -import { ListItemWrapper, ListItemButton, ListItemLink } from './index'; +import { + ListItemWrapper, + ListItemContent, + ListItemButton, + ListItemLink, +} from './index'; const meta = { title: 'Components/ListItem', component: ListItemWrapper, - subcomponents: { ListItemButton, ListItemLink }, + subcomponents: { ListItemContent, ListItemButton, ListItemLink }, } satisfies Meta; export default meta; type Story = StoryObj; +export const NonInteractive: Story = { + render: () => ( + }> + View more + + ), +}; + export const WithButton: Story = { render: () => ( ; }; -const SuggestedAccountItem: React.FC<{ account: ApiMutedAccountJSON }> = ( - props, -) => { - const account = useAccount(props.account.id); +const SuggestedAccountItem: React.FC<{ id: string }> = ({ id }) => { + const account = useAccount(id); + const handle = useAccountHandle(account, domain); if (!account) return null; return ( - <> - - - + } + > + + + + ); }; const renderAccountItem = (account: ApiMutedAccountJSON) => ( - + ); +type GroupKey = 'available' | 'mustFollow' | 'disabled'; + +function groupSuggestions(accounts: ApiMutedAccountJSON[]) { + const { available, disabled } = Object.groupBy(accounts, (account) => { + if (getIsItemDisabled(account)) { + return 'disabled'; + } + // if (account.locked && !relationship?.following) { + // return 'mustFollow'; + // } + return 'available'; + }); + + // Returning a new object ensures a fixed property order + return { available, disabled }; +} + +const renderGroupTitle = (groupKey: GroupKey, titleId: string) => { + if (groupKey === 'available') { + return null; + } + + let title: React.ReactElement; + let description: React.ReactElement; + + if (groupKey === 'mustFollow') { + title = ( + + ); + description = ( + + ); + } else { + title = ( + + ); + description = ( + + ); + } + + return ( + + + {title} + + + ); +}; + const getItemId = (account: ApiMutedAccountJSON) => account.id; // Disable accounts who can't be added to a collection @@ -259,10 +330,11 @@ export const CollectionAccounts: React.FC<{ onKeyDown={handleSearchKeyDown} disabled={hasMaxAccounts} isLoading={isLoadingSuggestions} - items={suggestedAccounts} + items={groupSuggestions(suggestedAccounts)} getItemId={getItemId} getIsItemDisabled={getIsItemDisabled} renderItem={renderAccountItem} + renderGroupTitle={renderGroupTitle} onSelectItem={handleSelectItem} status={ hasMaxAccounts diff --git a/app/javascript/mastodon/features/collections/editor/details.tsx b/app/javascript/mastodon/features/collections/editor/details.tsx index f4e561e6e3..ae944ba41f 100644 --- a/app/javascript/mastodon/features/collections/editor/details.tsx +++ b/app/javascript/mastodon/features/collections/editor/details.tsx @@ -276,18 +276,16 @@ export const CollectionDetails: React.FC = () => {
    -
    - -
    +
    ); diff --git a/app/javascript/mastodon/features/collections/editor/styles.module.scss b/app/javascript/mastodon/features/collections/editor/styles.module.scss index 577a6d7d12..1befc06cc1 100644 --- a/app/javascript/mastodon/features/collections/editor/styles.module.scss +++ b/app/javascript/mastodon/features/collections/editor/styles.module.scss @@ -58,3 +58,19 @@ .scrollableWrapper { margin-inline: -16px; } + +.suggestion { + padding: 4px 0; + + [aria-disabled='true'] & { + opacity: 0.6; + } +} + +.suggestionGroup { + padding: 4px 0; + + // Undo default group styles: + font-weight: 400; + text-transform: none; +} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 449679aab5..601293fb22 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -410,6 +410,10 @@ "collections.search_accounts_max_reached": "You have added the maximum number of accounts", "collections.sensitive": "Sensitive", "collections.share_short": "Share", + "collections.suggestions.can_not_add": "Can’t be added", + "collections.suggestions.can_not_add_desc": "These accounts may have opted out of discovery, or they might be on a server that doesn’t support collections.", + "collections.suggestions.must_follow": "Must follow first", + "collections.suggestions.must_follow_desc": "These accounts review all follow requests. Followers can add them to collections.", "collections.topic_hint": "Add a hashtag that helps others understand the main topic of this collection.", "collections.topic_special_chars_hint": "Special characters will be removed when saving", "collections.unlisted_collections_description": "These don’t appear on your profile to others. Anyone with the link can discover them.", From b15d234ccb5d004180561c440dd45536d70e67b2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 17 Apr 2026 11:36:09 -0400 Subject: [PATCH 30/37] Add `domain_variants` helper to `DomainNormalizable` concern (#38539) --- app/models/concerns/account/attribution_domains.rb | 3 +-- app/models/concerns/domain_normalizable.rb | 5 +++++ app/models/domain_block.rb | 4 +--- app/models/email_domain_block.rb | 4 +--- app/models/preview_card_provider.rb | 3 +-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/models/concerns/account/attribution_domains.rb b/app/models/concerns/account/attribution_domains.rb index 22bf27cc5d..ba5c092e6c 100644 --- a/app/models/concerns/account/attribution_domains.rb +++ b/app/models/concerns/account/attribution_domains.rb @@ -12,8 +12,7 @@ module Account::AttributionDomains end def can_be_attributed_from?(domain) - segments = domain.split('.') - variants = segments.map.with_index { |_, i| segments[i..].join('.') }.to_set + variants = self.class.domain_variants(domain).to_set self[:attribution_domains].to_set.intersect?(variants) end end diff --git a/app/models/concerns/domain_normalizable.rb b/app/models/concerns/domain_normalizable.rb index 6571a40c54..2746280f0c 100644 --- a/app/models/concerns/domain_normalizable.rb +++ b/app/models/concerns/domain_normalizable.rb @@ -17,6 +17,11 @@ module DomainNormalizable SQL ) end + + def domain_variants(domain) + segments = domain.to_s.split('.') + Array.new(segments.size) { |i| segments[i..].join('.') } + end end private diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 74a494517a..72d2d78e41 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -69,9 +69,7 @@ class DomainBlock < ApplicationRecord return if domain.blank? uri = Addressable::URI.new.tap { |u| u.host = domain.strip.delete('/') } - segments = uri.normalized_host.split('.') - variants = segments.map.with_index { |_, i| segments[i..].join('.') } - + variants = domain_variants(uri.normalized_host) where(domain: variants).by_domain_length.first rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError nil diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb index c4ab8b9ed2..c1b961517f 100644 --- a/app/models/email_domain_block.rb +++ b/app/models/email_domain_block.rb @@ -67,9 +67,7 @@ class EmailDomainBlock < ApplicationRecord @uris.flat_map do |uri| next if uri.nil? - segments = uri.normalized_host.split('.') - - segments.map.with_index { |_, i| segments[i..].join('.') } + self.class.module_parent.domain_variants(uri.normalized_host) end.uniq end diff --git a/app/models/preview_card_provider.rb b/app/models/preview_card_provider.rb index 9d77d45e22..5c052de1e6 100644 --- a/app/models/preview_card_provider.rb +++ b/app/models/preview_card_provider.rb @@ -36,7 +36,6 @@ class PreviewCardProvider < ApplicationRecord scope :not_trendable, -> { where(trendable: false) } def self.matching_domain(domain) - segments = domain.split('.') - where(domain: segments.map.with_index { |_, i| segments[i..].join('.') }).by_domain_length.first + where(domain: domain_variants(domain)).by_domain_length.first end end From 87c66c8b961d423ece6ebf88a14d16139f5dda8b Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 15 Apr 2026 20:03:48 +0200 Subject: [PATCH 31/37] [Glitch] Add more actions to collections notifications & context menus Port 543db6d24c8764fe3975a138c777ffb1c28ae29e to glitch-soc Signed-off-by: Claire --- .../components/collection_menu.tsx | 118 +++++++++++++----- .../features/collections/detail/index.tsx | 8 +- .../notification_collection.module.scss | 17 +++ .../components/notification_collection.tsx | 43 ++++++- 4 files changed, 147 insertions(+), 39 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/notifications_v2/components/notification_collection.module.scss diff --git a/app/javascript/flavours/glitch/features/collections/components/collection_menu.tsx b/app/javascript/flavours/glitch/features/collections/components/collection_menu.tsx index 874caeff42..37e518b9aa 100644 --- a/app/javascript/flavours/glitch/features/collections/components/collection_menu.tsx +++ b/app/javascript/flavours/glitch/features/collections/components/collection_menu.tsx @@ -4,6 +4,8 @@ import { defineMessages, useIntl } from 'react-intl'; import { matchPath } from 'react-router'; +import { showAlert } from '@/flavours/glitch/actions/alerts'; +import { initBlockModal } from '@/flavours/glitch/actions/blocks'; import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import MoreVertIcon from '@/material-icons/400-24px/more_vert.svg?react'; import { openModal } from 'flavours/glitch/actions/modal'; @@ -21,6 +23,18 @@ const messages = defineMessages({ id: 'collections.view_collection', defaultMessage: 'View collection', }, + share: { + id: 'collections.share_short', + defaultMessage: 'Share', + }, + copyLink: { + id: 'collections.copy_link', + defaultMessage: 'Copy link', + }, + copyLinkConfirmation: { + id: 'collections.copy_link_confirmation', + defaultMessage: 'Copied collection link to clipboard', + }, viewOtherCollections: { id: 'collections.view_other_collections_by_user', defaultMessage: 'View other collections by this user', @@ -33,6 +47,10 @@ const messages = defineMessages({ id: 'collections.report_collection', defaultMessage: 'Report this collection', }, + blockOwner: { + id: 'collections.block_collection_owner', + defaultMessage: 'Block account', + }, revoke: { id: 'collections.revoke_collection_inclusion', defaultMessage: 'Remove myself from this collection', @@ -42,15 +60,29 @@ const messages = defineMessages({ export const CollectionMenu: React.FC<{ collection: ApiCollectionJSON; - context: 'list' | 'collection'; + context: 'list' | 'notifications' | 'collection'; className?: string; }> = ({ collection, context, className }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const { id, name, account_id } = collection; - const isOwnCollection = account_id === me; + const { id, name, account_id, items } = collection; const ownerAccount = useAccount(account_id); + const isOwnCollection = account_id === me; + const currentAccountInCollection = items.find( + (item) => item.account_id === me, + ); + + const openShareModal = useCallback(() => { + dispatch( + openModal({ + modalType: 'SHARE_COLLECTION', + modalProps: { + collection, + }, + }), + ); + }, [collection, dispatch]); const openDeleteConfirmation = useCallback(() => { dispatch( @@ -75,9 +107,9 @@ export const CollectionMenu: React.FC<{ ); }, [collection, dispatch]); - const currentAccountInCollection = collection.items.find( - (item) => item.account_id === me, - ); + const openBlockModal = useCallback(() => { + dispatch(initBlockModal(ownerAccount)); + }, [ownerAccount, dispatch]); const openRevokeConfirmation = useCallback(() => { void dispatch( @@ -92,8 +124,28 @@ export const CollectionMenu: React.FC<{ }, [collection.id, currentAccountInCollection?.id, dispatch]); const menu = useMemo(() => { + const viewCollectionItem: MenuItem = { + text: intl.formatMessage(messages.view), + to: `/collections/${id}`, + }; + const shareItems: MenuItem[] = [ + { + text: intl.formatMessage(messages.share), + action: openShareModal, + }, + { + text: intl.formatMessage(messages.copyLink), + action: () => { + void navigator.clipboard.writeText(`/collections/${id}`); + dispatch(showAlert({ message: messages.copyLinkConfirmation })); + }, + }, + ]; + if (isOwnCollection) { - const commonItems: MenuItem[] = [ + const ownerItems: MenuItem[] = [ + ...shareItems, + null, { text: intl.formatMessage(editorMessages.manageAccounts), to: `/collections/${id}/edit`, @@ -111,18 +163,14 @@ export const CollectionMenu: React.FC<{ ]; if (context === 'list') { - return [ - { text: intl.formatMessage(messages.view), to: `/collections/${id}` }, - null, - ...commonItems, - ]; + return [viewCollectionItem, ...ownerItems]; } else { - return commonItems; + return ownerItems; } } else { - const items: MenuItem[] = []; + const nonOwnerItems: MenuItem[] = [viewCollectionItem, ...shareItems]; - if (ownerAccount) { + if (context !== 'notifications' && ownerAccount) { const featuredCollectionsPath = `/@${ownerAccount.acct}/featured`; // Don't show menu link to featured collections while on that very page if ( @@ -131,42 +179,50 @@ export const CollectionMenu: React.FC<{ exact: true, }) ) { - items.push( - ...[ - { - text: intl.formatMessage(messages.viewOtherCollections), - to: featuredCollectionsPath, - }, - null, - ], - ); + nonOwnerItems.push({ + text: intl.formatMessage(messages.viewOtherCollections), + to: featuredCollectionsPath, + }); } } - if (currentAccountInCollection) { - items.push({ + nonOwnerItems.push(null); + + // Collection notifications already have a prominent 'Remove me' button + if (currentAccountInCollection && context !== 'notifications') { + nonOwnerItems.push({ text: intl.formatMessage(messages.revoke), action: openRevokeConfirmation, }); } - items.push({ + nonOwnerItems.push({ text: intl.formatMessage(messages.report), action: openReportModal, }); - return items; + if (currentAccountInCollection) { + nonOwnerItems.push({ + text: intl.formatMessage(messages.blockOwner), + action: openBlockModal, + }); + } + + return nonOwnerItems; } }, [ - isOwnCollection, intl, id, + openShareModal, + isOwnCollection, + dispatch, openDeleteConfirmation, context, - currentAccountInCollection, - openRevokeConfirmation, ownerAccount, + currentAccountInCollection, openReportModal, + openBlockModal, + openRevokeConfirmation, ]); return ( diff --git a/app/javascript/flavours/glitch/features/collections/detail/index.tsx b/app/javascript/flavours/glitch/features/collections/detail/index.tsx index 1e6ec7bb4f..4c592949ed 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/index.tsx +++ b/app/javascript/flavours/glitch/features/collections/detail/index.tsx @@ -115,7 +115,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ ); const isCurrentUserInCollection = !isOwnCollection && currentUserIndex > -1; - const handleShare = useCallback(() => { + const openShareModal = useCallback(() => { dispatch( openModal({ modalType: 'SHARE_COLLECTION', @@ -132,9 +132,9 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ if (isNewCollection) { // Replace with current pathname to clear `newCollection` state history.replace(location.pathname); - handleShare(); + openShareModal(); } - }, [history, handleShare, isNewCollection, location.pathname]); + }, [history, openShareModal, isNewCollection, location.pathname]); return (
    @@ -150,7 +150,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ icon='share-icon' title={intl.formatMessage(messages.share)} className={classes.iconButton} - onClick={handleShare} + onClick={openShareModal} /> = ({ notification, unread }) => { const { collection, type } = notification; const collectionCreatorAccount = useAccount(collection.account_id); + const confirmRevoke = useConfirmRevoke(collection); return (
    + ), }} /> @@ -54,7 +64,12 @@ export const NotificationCollection: React.FC<{ defaultMessage='{name} edited a collection you’re in' values={{ name: ( - + ), }} /> @@ -63,6 +78,26 @@ export const NotificationCollection: React.FC<{
    + +
    + + + +
    ); From 7c9d73d1cbcf59a378136a8a749f3b9d80bf2f58 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 16 Apr 2026 15:59:38 +0200 Subject: [PATCH 32/37] [Glitch] Update design of collection accounts editor Port 5a38246ee8dffad6789fb4a3a7723477d2d97b4c to glitch-soc Signed-off-by: Claire --- .../empty_state/empty_state.module.scss | 9 + .../empty_state/empty_state.stories.tsx | 20 +- .../glitch/components/empty_state/index.tsx | 18 +- .../components/empty_message.module.scss | 8 - .../components/empty_message.tsx | 8 +- .../features/collections/editor/accounts.tsx | 350 ++++++++---------- .../features/collections/editor/details.tsx | 4 +- .../features/collections/editor/index.tsx | 4 +- .../collections/editor/styles.module.scss | 78 ++-- .../collections/editor/wizard_step_header.tsx | 23 -- .../collections/editor/wizard_step_title.tsx | 21 ++ .../glitch/hooks/useSearchAccounts.ts | 7 +- 12 files changed, 238 insertions(+), 312 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/account_featured/components/empty_message.module.scss delete mode 100644 app/javascript/flavours/glitch/features/collections/editor/wizard_step_header.tsx create mode 100644 app/javascript/flavours/glitch/features/collections/editor/wizard_step_title.tsx diff --git a/app/javascript/flavours/glitch/components/empty_state/empty_state.module.scss b/app/javascript/flavours/glitch/components/empty_state/empty_state.module.scss index e9602f2e41..b58c565ac5 100644 --- a/app/javascript/flavours/glitch/components/empty_state/empty_state.module.scss +++ b/app/javascript/flavours/glitch/components/empty_state/empty_state.module.scss @@ -35,3 +35,12 @@ text-wrap: pretty; } } + +[data-color-scheme='dark'] .defaultImage { + --color-skin-1: #3a3a50; + --color-skin-2: #67678e; + --color-skin-3: #44445f; + --color-outline: #21212c; + --color-shadow: #181820; + --color-highlight: #b2b1c8; +} diff --git a/app/javascript/flavours/glitch/components/empty_state/empty_state.stories.tsx b/app/javascript/flavours/glitch/components/empty_state/empty_state.stories.tsx index 8515a6ea1a..83fce03468 100644 --- a/app/javascript/flavours/glitch/components/empty_state/empty_state.stories.tsx +++ b/app/javascript/flavours/glitch/components/empty_state/empty_state.stories.tsx @@ -29,12 +29,6 @@ export const Default: Story = { }, }; -export const WithoutMessage: Story = { - args: { - message: undefined, - }, -}; - export const WithAction: Story = { args: { ...Default.args, @@ -42,3 +36,17 @@ export const WithAction: Story = { children: , }, }; + +export const WithoutImage: Story = { + args: { + ...Default.args, + image: null, + }, +}; + +export const WithoutMessage: Story = { + args: { + ...Default.args, + message: undefined, + }, +}; diff --git a/app/javascript/flavours/glitch/components/empty_state/index.tsx b/app/javascript/flavours/glitch/components/empty_state/index.tsx index 59210bbc87..0ef0d67995 100644 --- a/app/javascript/flavours/glitch/components/empty_state/index.tsx +++ b/app/javascript/flavours/glitch/components/empty_state/index.tsx @@ -1,31 +1,39 @@ import { FormattedMessage } from 'react-intl'; +import ElephantImage from '@/images/elephant_ui.svg?react'; + import classes from './empty_state.module.scss'; +const images = { + default: , +}; + /** * Simple empty state component with a neutral default title and customisable message. * - * Action buttons can be passed as `children` + * Action buttons can be passed as `children`. */ export const EmptyState: React.FC<{ - image?: React.ReactNode; + image?: keyof typeof images | React.ReactElement | null; title?: React.ReactNode; message?: React.ReactNode; children?: React.ReactNode; }> = ({ - image, + image = 'default', title = ( ), message, children, }) => { + const imageToRender = typeof image === 'string' ? images[image] : image; + return (
    - {(title || message || image) && ( + {(title || message || imageToRender) && (
    - {image} + {imageToRender} {!!title &&

    {title}

    } {!!message &&

    {message}

    }
    diff --git a/app/javascript/flavours/glitch/features/account_featured/components/empty_message.module.scss b/app/javascript/flavours/glitch/features/account_featured/components/empty_message.module.scss deleted file mode 100644 index 25dcf19433..0000000000 --- a/app/javascript/flavours/glitch/features/account_featured/components/empty_message.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -[data-color-scheme='dark'] .image { - --color-skin-1: #3a3a50; - --color-skin-2: #67678e; - --color-skin-3: #44445f; - --color-outline: #21212c; - --color-shadow: #181820; - --color-highlight: #b2b1c8; -} diff --git a/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx b/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx index 6c3573da21..83ab894824 100644 --- a/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx +++ b/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx @@ -12,9 +12,6 @@ import { LimitedAccountHint } from '@/flavours/glitch/features/account_timeline/ import { areCollectionsEnabled } from '@/flavours/glitch/features/collections/utils'; import { useCurrentAccountId } from '@/flavours/glitch/hooks/useAccountId'; import { useAppDispatch } from '@/flavours/glitch/store'; -import ElephantImage from '@/images/elephant_ui.svg?react'; - -import classes from './empty_message.module.scss'; interface EmptyMessageProps { suspended: boolean; @@ -54,14 +51,11 @@ export const EmptyMessage: React.FC = ({ const hasCollections = areCollectionsEnabled(); - const image = ; - if (me === accountId) { if (hasCollections) { // Return only here to insert the "Create a collection" button as the action for the empty state. return ( = ({ } } - return ; + return ; }; diff --git a/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx b/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx index 3338815b65..26960e69ab 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx @@ -4,22 +4,19 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; -import CancelIcon from '@/material-icons/400-24px/cancel.svg?react'; -import CheckIcon from '@/material-icons/400-24px/check.svg?react'; -import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import { showAlertForError } from 'flavours/glitch/actions/alerts'; import { openModal } from 'flavours/glitch/actions/modal'; import { apiFollowAccount } from 'flavours/glitch/api/accounts'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; import { Account } from 'flavours/glitch/components/account'; import { Avatar } from 'flavours/glitch/components/avatar'; -import { Badge } from 'flavours/glitch/components/badge'; import { Button } from 'flavours/glitch/components/button'; import { DisplayName } from 'flavours/glitch/components/display_name'; import { EmptyState } from 'flavours/glitch/components/empty_state'; -import { FormStack, Combobox } from 'flavours/glitch/components/form_fields'; -import { Icon } from 'flavours/glitch/components/icon'; -import { IconButton } from 'flavours/glitch/components/icon_button'; +import { + FormStack, + ComboboxField, +} from 'flavours/glitch/components/form_fields'; import { Article, ItemList, @@ -37,75 +34,35 @@ import { import { store, useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import classes from './styles.module.scss'; -import { WizardStepHeader } from './wizard_step_header'; +import { WizardStepTitle } from './wizard_step_title'; -const MAX_ACCOUNT_COUNT = 25; - -function isOlderThanAWeek(date?: string): boolean { - if (!date) return false; - - const targetDate = new Date(date); - const sevenDaysAgo = new Date(); - sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); - return targetDate < sevenDaysAgo; -} +const MAX_ACCOUNT_COUNT = 3; const AddedAccountItem: React.FC<{ accountId: string; onRemove: (id: string) => void; }> = ({ accountId, onRemove }) => { - const intl = useIntl(); - const account = useAccount(accountId); - const handleRemoveAccount = useCallback(() => { onRemove(accountId); }, [accountId, onRemove]); - const lastStatusAt = account?.last_status_at; - - const lastPostHint = useMemo( - () => - (!lastStatusAt || isOlderThanAWeek(lastStatusAt)) && ( - - } - icon={} - className={classes.accountBadge} - /> - ), - [lastStatusAt], - ); - return ( - - + + ); }; interface SuggestionItem { id: string; - isSelected: boolean; } -const SuggestedAccountItem: React.FC = ({ id, isSelected }) => { +const SuggestedAccountItem: React.FC = ({ id }) => { const account = useAccount(id); if (!account) return null; @@ -114,23 +71,15 @@ const SuggestedAccountItem: React.FC = ({ id, isSelected }) => { <> - {isSelected && ( - - )} ); }; const renderAccountItem = (item: SuggestionItem) => ( - + ); const getItemId = (item: SuggestionItem) => item.id; -const getIsItemSelected = (item: SuggestionItem) => item.isSelected; export const CollectionAccounts: React.FC<{ collection?: ApiCollectionJSON | null; @@ -139,9 +88,8 @@ export const CollectionAccounts: React.FC<{ const dispatch = useAppDispatch(); const history = useHistory(); - const { id, items } = collection ?? {}; + const { id, items: collectionItems } = collection ?? {}; const isEditMode = !!id; - const collectionItems = items; const addedAccountIds = useAppSelector( (state) => state.collections.editor.accountIds, @@ -157,22 +105,25 @@ export const CollectionAccounts: React.FC<{ const [searchValue, setSearchValue] = useState(''); + const hasAccounts = accountIds.length > 0; const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT; const { accountIds: suggestedAccountIds, isLoading: isLoadingSuggestions, searchAccounts, + resetAccounts, } = useSearchAccounts({ withRelationships: true, filterResults: (account) => + !accountIds.includes(account.id) && // Only suggest accounts who allow being featured/recommended account.feature_approval.current_user === 'automatic', }); const suggestedItems = suggestedAccountIds.map((id) => ({ id, - isSelected: accountIds.includes(id), + isDisabled: accountIds.includes(id), })); const handleSearchValueChange = useCallback( @@ -242,12 +193,12 @@ export const CollectionAccounts: React.FC<{ ); const addAccountItem = useCallback( - (accountId: string) => { - confirmFollowStatus(accountId, () => { + (item: SuggestionItem) => { + confirmFollowStatus(item.id, () => { dispatch( updateCollectionEditorField({ field: 'accountIds', - value: [...accountIds, accountId], + value: [...accountIds, item.id], }), ); }); @@ -255,17 +206,6 @@ export const CollectionAccounts: React.FC<{ [accountIds, confirmFollowStatus, dispatch], ); - const toggleAccountItem = useCallback( - (item: SuggestionItem) => { - if (accountIds.includes(item.id)) { - removeAccountItem(item.id); - } else { - addAccountItem(item.id); - } - }, - [accountIds, addAccountItem, removeAccountItem], - ); - const instantRemoveAccountItem = useCallback( (accountId: string) => { const itemId = collectionItems?.find( @@ -289,23 +229,16 @@ export const CollectionAccounts: React.FC<{ ); const instantAddAccountItem = useCallback( - (collectionId: string, accountId: string) => { - confirmFollowStatus(accountId, () => { - void dispatch(addCollectionItem({ collectionId, accountId })); + (item: SuggestionItem) => { + confirmFollowStatus(item.id, () => { + if (id) { + void dispatch( + addCollectionItem({ collectionId: id, accountId: item.id }), + ); + } }); }, - [confirmFollowStatus, dispatch], - ); - - const instantToggleAccountItem = useCallback( - (item: SuggestionItem) => { - if (accountIds.includes(item.id)) { - instantRemoveAccountItem(item.id); - } else if (id) { - instantAddAccountItem(id, item.id); - } - }, - [accountIds, id, instantAddAccountItem, instantRemoveAccountItem], + [confirmFollowStatus, dispatch, id], ); const handleRemoveAccountItem = useCallback( @@ -319,6 +252,20 @@ export const CollectionAccounts: React.FC<{ [isEditMode, instantRemoveAccountItem, removeAccountItem], ); + const handleSelectItem = useCallback( + (item: SuggestionItem) => { + if (isEditMode) { + instantAddAccountItem(item); + } else { + addAccountItem(item); + } + + setSearchValue(''); + resetAccounts(); + }, + [addAccountItem, instantAddAccountItem, isEditMode, resetAccounts], + ); + const handleSubmit = useCallback( (e: React.FormEvent) => { e.preventDefault(); @@ -333,117 +280,114 @@ export const CollectionAccounts: React.FC<{ ); const inputId = useId(); - const inputLabel = intl.formatMessage({ - id: 'collections.search_accounts_label', - defaultMessage: 'Search for accounts to add…', - }); + const AccountsHeadingElement = id ? 'h2' : 'h3'; return (
    - {!id && ( - - } - description={ - - } - /> - )} - - - {hasMaxAccounts && ( - - )} - - - - } - message={ - - } - /> - } - > - {accountIds.map((accountId, index) => ( -
    - -
    - ))} -
    -
    -
    - {!isEditMode && ( -
    -
    - - {(text) =>
    {text}
    } -
    - -
    + } + /> + )} + +
    + +
    + {hasAccounts && ( + + + + )} + + + + } + message={ + + } + /> + } + > + {accountIds.map((accountId, index) => ( +
    + +
    + ))} +
    +
    +
    + + {!isEditMode && hasAccounts && ( +
    +
    )} diff --git a/app/javascript/flavours/glitch/features/collections/editor/details.tsx b/app/javascript/flavours/glitch/features/collections/editor/details.tsx index 35fac2e09a..a85d84b118 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/details.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/details.tsx @@ -36,7 +36,7 @@ import { import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import classes from './styles.module.scss'; -import { WizardStepHeader } from './wizard_step_header'; +import { WizardStepTitle } from './wizard_step_title'; export const CollectionDetails: React.FC = () => { const dispatch = useAppDispatch(); @@ -152,7 +152,7 @@ export const CollectionDetails: React.FC = () => {
    {!id && ( - = ({ multiColumn }) => { const intl = useIntl(); const dispatch = useAppDispatch(); - const { id } = useParams<{ id?: string }>(); + const { id = null } = useParams<{ id?: string }>(); const { path } = useRouteMatch(); const collection = useAppSelector((state) => id ? state.collections.collections[id] : undefined, diff --git a/app/javascript/flavours/glitch/features/collections/editor/styles.module.scss b/app/javascript/flavours/glitch/features/collections/editor/styles.module.scss index 1991aa4211..577a6d7d12 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/styles.module.scss +++ b/app/javascript/flavours/glitch/features/collections/editor/styles.module.scss @@ -1,10 +1,17 @@ +.header { + display: flex; + flex-direction: column; + gap: 12px; +} + .step { font-size: 13px; color: var(--color-text-secondary); } .title { - font-size: 22px; + font-size: 17px; + font-weight: 500; line-height: 1.2; margin-top: 4px; } @@ -14,79 +21,40 @@ margin-top: 8px; } -/* Make form stretch full height of the column */ +.listHeading { + margin-bottom: 8px; + font-size: 15px; + font-weight: 500; + line-height: 1.2; +} + +/* Ensure sticky footer isn't covered by mobile bottom nav */ .form { --bottom-spacing: 0; - box-sizing: border-box; - display: flex; - flex-direction: column; - min-height: 100%; - padding-bottom: var(--bottom-spacing); + padding-bottom: 0; @media (width < 760px) { --bottom-spacing: var(--mobile-bottom-nav-height); } } -.selectedSuggestionIcon { - box-sizing: border-box; - width: 18px; - height: 18px; - margin-left: auto; - padding: 2px; - border-radius: 100%; - color: var(--color-text-on-brand-base); - background: var(--color-bg-brand-base); - - [data-highlighted='true'] & { - color: var(--color-bg-brand-base); - background: var(--color-text-on-brand-base); - } -} - -.formFieldStack { - flex-grow: 1; -} - -.scrollableWrapper, -.scrollableInner { - margin-inline: -8px; -} - -.submitDisabledCallout { - align-self: center; -} - .stickyFooter { position: sticky; bottom: var(--bottom-spacing); + margin-top: -16px; padding: 16px; background-image: linear-gradient( to bottom, transparent, - var(--color-bg-primary) 32px + var(--color-bg-primary) 24px ); } -.itemCountReadout { - text-align: center; +.formFieldStack { + gap: 16px; } -.actionWrapper { - display: flex; - flex-direction: column; - width: min-content; - min-width: 240px; - margin-inline: auto; - gap: 8px; -} - -.accountBadge { - margin-inline-start: 56px; - - @container (width < 360px) { - margin-top: 4px; - margin-inline-start: 46px; - } +.scrollableWrapper { + margin-inline: -16px; } diff --git a/app/javascript/flavours/glitch/features/collections/editor/wizard_step_header.tsx b/app/javascript/flavours/glitch/features/collections/editor/wizard_step_header.tsx deleted file mode 100644 index 3bc9a4d7ef..0000000000 --- a/app/javascript/flavours/glitch/features/collections/editor/wizard_step_header.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FormattedMessage } from 'react-intl'; - -import classes from './styles.module.scss'; - -export const WizardStepHeader: React.FC<{ - step: number; - title: React.ReactElement; - description?: React.ReactElement; -}> = ({ step, title, description }) => { - return ( -
    - - {(content) =>

    {content}

    } -
    -

    {title}

    - {!!description &&

    {description}

    } -
    - ); -}; diff --git a/app/javascript/flavours/glitch/features/collections/editor/wizard_step_title.tsx b/app/javascript/flavours/glitch/features/collections/editor/wizard_step_title.tsx new file mode 100644 index 0000000000..4d328521e7 --- /dev/null +++ b/app/javascript/flavours/glitch/features/collections/editor/wizard_step_title.tsx @@ -0,0 +1,21 @@ +import { FormattedMessage } from 'react-intl'; + +import classes from './styles.module.scss'; + +export const WizardStepTitle: React.FC<{ + step: number; + title: React.ReactElement; +}> = ({ step, title }) => { + return ( +
    +

    + +

    +

    {title}

    +
    + ); +}; diff --git a/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts b/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts index 576d124e24..e7e091bfcc 100644 --- a/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts +++ b/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { useDebouncedCallback } from 'use-debounce'; @@ -73,8 +73,13 @@ export function useSearchAccounts({ { leading: true, trailing: true }, ); + const resetAccounts = useCallback(() => { + setAccountIds([]); + }, []); + return { searchAccounts, + resetAccounts, accountIds, isLoading: loadingState === 'loading', isError: loadingState === 'error', From fe869936ab1f4909b605900c6e1373ab69128561 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 16 Apr 2026 20:05:04 +0200 Subject: [PATCH 33/37] [Glitch] Fix `Bundle` being used with incorrect prop types by using type-dependent `key` Port 0e6180a5afdecca880ec94077b7086dcbcaa29ab to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/components/status.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index db26e5f012..8005965459 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -565,7 +565,7 @@ class Status extends ImmutablePureComponent { ); } else if (['image', 'gifv', 'unknown'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) { media.push( - + {Component => ( + {Component => ( + {Component => ( Date: Thu, 16 Apr 2026 20:05:36 +0200 Subject: [PATCH 34/37] [Glitch] Implement new Collection inclusion rules in Collection accounts editor Port a40b07164019ee38290eb2167b648a9f46c2634a to glitch-soc Signed-off-by: Claire --- .../features/collections/editor/accounts.tsx | 104 ++++++------------ .../glitch/features/lists/members.tsx | 5 +- .../follow_to_collection.tsx | 43 -------- .../components/confirmation_modals/index.ts | 1 - .../features/ui/components/modal_root.jsx | 2 - .../glitch/hooks/useSearchAccounts.ts | 10 +- 6 files changed, 41 insertions(+), 124 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/follow_to_collection.tsx diff --git a/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx b/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx index 26960e69ab..879e46c241 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx @@ -4,11 +4,8 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; -import { showAlertForError } from 'flavours/glitch/actions/alerts'; -import { openModal } from 'flavours/glitch/actions/modal'; -import { apiFollowAccount } from 'flavours/glitch/api/accounts'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; -import { Account } from 'flavours/glitch/components/account'; +import { AccountListItem } from 'flavours/glitch/components/account_list_item'; import { Avatar } from 'flavours/glitch/components/avatar'; import { Button } from 'flavours/glitch/components/button'; import { DisplayName } from 'flavours/glitch/components/display_name'; @@ -24,19 +21,18 @@ import { } from 'flavours/glitch/components/scrollable_list/components'; import { useAccount } from 'flavours/glitch/hooks/useAccount'; import { useSearchAccounts } from 'flavours/glitch/hooks/useSearchAccounts'; -import { me } from 'flavours/glitch/initial_state'; import { addCollectionItem, getCollectionItemIds, removeCollectionItem, updateCollectionEditorField, } from 'flavours/glitch/reducers/slices/collections'; -import { store, useAppDispatch, useAppSelector } from 'flavours/glitch/store'; +import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import classes from './styles.module.scss'; import { WizardStepTitle } from './wizard_step_title'; -const MAX_ACCOUNT_COUNT = 3; +const MAX_ACCOUNT_COUNT = 25; const AddedAccountItem: React.FC<{ accountId: string; @@ -46,20 +42,24 @@ const AddedAccountItem: React.FC<{ onRemove(accountId); }, [accountId, onRemove]); - return ( - + const renderButton = useCallback( + () => ( - + ), + [handleRemoveAccount], ); + + return ; }; interface SuggestionItem { id: string; + isDisabled?: boolean; } const SuggestedAccountItem: React.FC = ({ id }) => { @@ -80,6 +80,7 @@ const renderAccountItem = (item: SuggestionItem) => ( ); const getItemId = (item: SuggestionItem) => item.id; +const getIsItemDisabled = (item: SuggestionItem) => item.isDisabled ?? false; export const CollectionAccounts: React.FC<{ collection?: ApiCollectionJSON | null; @@ -109,21 +110,22 @@ export const CollectionAccounts: React.FC<{ const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT; const { - accountIds: suggestedAccountIds, + accounts: suggestedAccounts, isLoading: isLoadingSuggestions, searchAccounts, resetAccounts, } = useSearchAccounts({ withRelationships: true, - filterResults: (account) => - !accountIds.includes(account.id) && - // Only suggest accounts who allow being featured/recommended - account.feature_approval.current_user === 'automatic', + // Don't suggest accounts that were already added + filterResults: (account) => !accountIds.includes(account.id), }); - const suggestedItems = suggestedAccountIds.map((id) => ({ + const suggestedItems = suggestedAccounts.map(({ id, feature_approval }) => ({ id, - isDisabled: accountIds.includes(id), + // Disable accounts who can't be added to a collection + isDisabled: !['automatic', 'manual'].includes( + feature_approval.current_user, + ), })); const handleSearchValueChange = useCallback( @@ -143,43 +145,6 @@ export const CollectionAccounts: React.FC<{ [], ); - const relationships = useAppSelector((state) => state.relationships); - - const confirmFollowStatus = useCallback( - (accountId: string, onFollowing: () => void) => { - const relationship = relationships.get(accountId); - - if (!relationship) { - return; - } - - if ( - accountId === me || - relationship.following || - relationship.requested - ) { - onFollowing(); - } else { - dispatch( - openModal({ - modalType: 'CONFIRM_FOLLOW_TO_COLLECTION', - modalProps: { - accountId, - onConfirm: () => { - apiFollowAccount(accountId) - .then(onFollowing) - .catch((err: unknown) => { - store.dispatch(showAlertForError(err)); - }); - }, - }, - }), - ); - } - }, - [dispatch, relationships], - ); - const removeAccountItem = useCallback( (accountId: string) => { dispatch( @@ -194,16 +159,14 @@ export const CollectionAccounts: React.FC<{ const addAccountItem = useCallback( (item: SuggestionItem) => { - confirmFollowStatus(item.id, () => { - dispatch( - updateCollectionEditorField({ - field: 'accountIds', - value: [...accountIds, item.id], - }), - ); - }); + dispatch( + updateCollectionEditorField({ + field: 'accountIds', + value: [...accountIds, item.id], + }), + ); }, - [accountIds, confirmFollowStatus, dispatch], + [accountIds, dispatch], ); const instantRemoveAccountItem = useCallback( @@ -230,15 +193,13 @@ export const CollectionAccounts: React.FC<{ const instantAddAccountItem = useCallback( (item: SuggestionItem) => { - confirmFollowStatus(item.id, () => { - if (id) { - void dispatch( - addCollectionItem({ collectionId: id, accountId: item.id }), - ); - } - }); + if (id) { + void dispatch( + addCollectionItem({ collectionId: id, accountId: item.id }), + ); + } }, - [confirmFollowStatus, dispatch, id], + [dispatch, id], ); const handleRemoveAccountItem = useCallback( @@ -310,6 +271,7 @@ export const CollectionAccounts: React.FC<{ isLoading={isLoadingSuggestions} items={suggestedItems} getItemId={getItemId} + getIsItemDisabled={getIsItemDisabled} renderItem={renderAccountItem} onSelectItem={handleSelectItem} status={ diff --git a/app/javascript/flavours/glitch/features/lists/members.tsx b/app/javascript/flavours/glitch/features/lists/members.tsx index 1a18e8e499..9df96cf7c2 100644 --- a/app/javascript/flavours/glitch/features/lists/members.tsx +++ b/app/javascript/flavours/glitch/features/lists/members.tsx @@ -164,7 +164,7 @@ const ListMembers: React.FC<{ const [mode, setMode] = useState('remove'); const { - accountIds: searchAccountIds, + accounts: accountsFromSearch, isLoading: loadingSearchResults, searchAccounts: handleSearch, } = useSearchAccounts({ @@ -177,6 +177,7 @@ const ListMembers: React.FC<{ } }, }); + const accountIdsFromSearch = accountsFromSearch.map((item) => item.id); useEffect(() => { if (id) { @@ -220,7 +221,7 @@ const ListMembers: React.FC<{ let displayedAccountIds: string[]; if (mode === 'add' && searching) { - displayedAccountIds = searchAccountIds; + displayedAccountIds = accountIdsFromSearch; } else { displayedAccountIds = accountIds; } diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/follow_to_collection.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/follow_to_collection.tsx deleted file mode 100644 index ade85c174b..0000000000 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/follow_to_collection.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; - -import { useAccount } from 'flavours/glitch/hooks/useAccount'; - -import type { BaseConfirmationModalProps } from './confirmation_modal'; -import { ConfirmationModal } from './confirmation_modal'; - -const messages = defineMessages({ - title: { - id: 'confirmations.follow_to_collection.title', - defaultMessage: 'Follow account?', - }, - confirm: { - id: 'confirmations.follow_to_collection.confirm', - defaultMessage: 'Follow and add to collection', - }, -}); - -export const ConfirmFollowToCollectionModal: React.FC< - { - accountId: string; - onConfirm: () => void; - } & BaseConfirmationModalProps -> = ({ accountId, onConfirm, onClose }) => { - const intl = useIntl(); - const account = useAccount(accountId); - - return ( - @{account?.acct} }} - /> - } - confirm={intl.formatMessage(messages.confirm)} - onConfirm={onConfirm} - onClose={onClose} - /> - ); -}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts index 4011b4db06..c27597fb52 100644 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts @@ -13,7 +13,6 @@ export { ConfirmUnblockModal } from './unblock'; export { ConfirmClearNotificationsModal } from './clear_notifications'; export { ConfirmLogOutModal } from './log_out'; export { ConfirmFollowToListModal } from './follow_to_list'; -export { ConfirmFollowToCollectionModal } from './follow_to_collection'; export { ConfirmMissingAltTextModal } from './missing_alt_text'; export { ConfirmRevokeQuoteModal } from './revoke_quote'; export { QuietPostQuoteInfoModal } from './quiet_post_quote_info'; diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx index ce2fd0ed8e..77c559ba0f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx @@ -40,7 +40,6 @@ import { ConfirmClearNotificationsModal, ConfirmLogOutModal, ConfirmFollowToListModal, - ConfirmFollowToCollectionModal, ConfirmMissingAltTextModal, ConfirmRevokeQuoteModal, QuietPostQuoteInfoModal, @@ -75,7 +74,6 @@ export const MODAL_COMPONENTS = { 'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), 'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), 'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }), - 'CONFIRM_FOLLOW_TO_COLLECTION': () => Promise.resolve({ default: ConfirmFollowToCollectionModal }), 'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }), 'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }), 'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }), diff --git a/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts b/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts index e7e091bfcc..f9af2a1eb6 100644 --- a/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts +++ b/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts @@ -21,7 +21,7 @@ export function useSearchAccounts({ } = {}) { const dispatch = useAppDispatch(); - const [accountIds, setAccountIds] = useState([]); + const [accounts, setAccounts] = useState([]); const [loadingState, setLoadingState] = useState< 'idle' | 'loading' | 'error' >('idle'); @@ -37,7 +37,7 @@ export function useSearchAccounts({ if (value.trim().length === 0) { onSettled?.(''); if (resetOnInputClear) { - setAccountIds([]); + setAccounts([]); } return; } @@ -60,7 +60,7 @@ export function useSearchAccounts({ if (withRelationships) { dispatch(fetchRelationships(accountIds)); } - setAccountIds(accountIds); + setAccounts(accounts); setLoadingState('idle'); onSettled?.(value); }) @@ -74,13 +74,13 @@ export function useSearchAccounts({ ); const resetAccounts = useCallback(() => { - setAccountIds([]); + setAccounts([]); }, []); return { searchAccounts, resetAccounts, - accountIds, + accounts, isLoading: loadingState === 'loading', isError: loadingState === 'error', }; From 3916132f2fe01e5c8db500b08fb5e068eb1beff2 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 17 Apr 2026 10:50:35 +0200 Subject: [PATCH 35/37] [Glitch] Remove "View other collections from this user" from collection menu Port e571994b5c6700cd896795f792978a709a09c5ce to glitch-soc Signed-off-by: Claire --- .../components/collection_menu.tsx | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/app/javascript/flavours/glitch/features/collections/components/collection_menu.tsx b/app/javascript/flavours/glitch/features/collections/components/collection_menu.tsx index 37e518b9aa..b4ab4770af 100644 --- a/app/javascript/flavours/glitch/features/collections/components/collection_menu.tsx +++ b/app/javascript/flavours/glitch/features/collections/components/collection_menu.tsx @@ -2,8 +2,6 @@ import { useCallback, useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { matchPath } from 'react-router'; - import { showAlert } from '@/flavours/glitch/actions/alerts'; import { initBlockModal } from '@/flavours/glitch/actions/blocks'; import { useAccount } from '@/flavours/glitch/hooks/useAccount'; @@ -35,10 +33,6 @@ const messages = defineMessages({ id: 'collections.copy_link_confirmation', defaultMessage: 'Copied collection link to clipboard', }, - viewOtherCollections: { - id: 'collections.view_other_collections_by_user', - defaultMessage: 'View other collections by this user', - }, delete: { id: 'collections.delete_collection', defaultMessage: 'Delete collection', @@ -168,25 +162,11 @@ export const CollectionMenu: React.FC<{ return ownerItems; } } else { - const nonOwnerItems: MenuItem[] = [viewCollectionItem, ...shareItems]; - - if (context !== 'notifications' && ownerAccount) { - const featuredCollectionsPath = `/@${ownerAccount.acct}/featured`; - // Don't show menu link to featured collections while on that very page - if ( - !matchPath(location.pathname, { - path: featuredCollectionsPath, - exact: true, - }) - ) { - nonOwnerItems.push({ - text: intl.formatMessage(messages.viewOtherCollections), - to: featuredCollectionsPath, - }); - } - } - - nonOwnerItems.push(null); + const nonOwnerItems: MenuItem[] = [ + viewCollectionItem, + ...shareItems, + null, + ]; // Collection notifications already have a prominent 'Remove me' button if (currentAccountInCollection && context !== 'notifications') { @@ -218,7 +198,6 @@ export const CollectionMenu: React.FC<{ dispatch, openDeleteConfirmation, context, - ownerAccount, currentAccountInCollection, openReportModal, openBlockModal, From fe712fa5afda6d099fd389fa61cd3aaff7ff4e8b Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 17 Apr 2026 15:18:04 +0200 Subject: [PATCH 36/37] [Glitch] Allow grouping items in Combobox component Port 570f2ef4828044312a88ad410fa8e66939e75c56 to glitch-soc Signed-off-by: Claire --- .../form_fields/combobox.module.scss | 12 + .../form_fields/combobox_field.stories.tsx | 104 ++++---- .../components/form_fields/combobox_field.tsx | 223 ++++++++++++------ .../features/collections/editor/accounts.tsx | 39 ++- 4 files changed, 239 insertions(+), 139 deletions(-) diff --git a/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss b/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss index 732893200c..ca7bc2020b 100644 --- a/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss +++ b/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss @@ -38,6 +38,18 @@ overscroll-behavior-y: contain; } +.groupLabel { + padding-block: 12px 4px; + padding-inline: 12px; + font-size: 13px; + font-weight: bold; + text-transform: uppercase; + + ul:first-child > & { + padding-top: 4px; + } +} + .menuItem { display: flex; align-items: center; diff --git a/app/javascript/flavours/glitch/components/form_fields/combobox_field.stories.tsx b/app/javascript/flavours/glitch/components/form_fields/combobox_field.stories.tsx index 2c4b82fdcd..32fc736423 100644 --- a/app/javascript/flavours/glitch/components/form_fields/combobox_field.stories.tsx +++ b/app/javascript/flavours/glitch/components/form_fields/combobox_field.stories.tsx @@ -4,40 +4,46 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { ComboboxField } from './combobox_field'; -const ComboboxDemo: React.FC = () => { +interface Fruit { + id: string; + name: string; + type: 'citrus' | 'berryish' | 'seedy' | 'stony' | 'longish' | 'chonky'; + disabled?: boolean; +} + +const ComboboxDemo: React.FC<{ withGroups?: boolean }> = ({ withGroups }) => { const [searchValue, setSearchValue] = useState(''); - const items = [ - { id: '1', name: 'Apple' }, - { id: '2', name: 'Banana' }, - { id: '3', name: 'Cherry', disabled: true }, - { id: '4', name: 'Date' }, - { id: '5', name: 'Fig', disabled: true }, - { id: '6', name: 'Grape' }, - { id: '7', name: 'Honeydew' }, - { id: '8', name: 'Kiwi' }, - { id: '9', name: 'Lemon' }, - { id: '10', name: 'Mango' }, - { id: '11', name: 'Nectarine' }, - { id: '12', name: 'Orange' }, - { id: '13', name: 'Papaya' }, - { id: '14', name: 'Quince' }, - { id: '15', name: 'Raspberry' }, - { id: '16', name: 'Strawberry' }, - { id: '17', name: 'Tangerine' }, - { id: '19', name: 'Vanilla bean' }, - { id: '20', name: 'Watermelon' }, - { id: '22', name: 'Yellow Passion Fruit' }, - { id: '23', name: 'Zucchini' }, - { id: '24', name: 'Cantaloupe' }, - { id: '25', name: 'Blackberry' }, - { id: '26', name: 'Persimmon' }, - { id: '27', name: 'Lychee' }, - { id: '28', name: 'Dragon Fruit' }, - { id: '29', name: 'Passion Fruit' }, - { id: '30', name: 'Starfruit' }, + const items: Fruit[] = [ + { id: '1', name: 'Apple', type: 'seedy' }, + { id: '2', name: 'Banana', type: 'longish' }, + { id: '3', name: 'Cherry', type: 'berryish', disabled: true }, + { id: '4', name: 'Date', type: 'stony' }, + { id: '5', name: 'Fig', type: 'seedy', disabled: true }, + { id: '6', name: 'Grape', type: 'berryish' }, + { id: '7', name: 'Honeydew', type: 'chonky' }, + { id: '8', name: 'Kiwi', type: 'seedy' }, + { id: '9', name: 'Lemon', type: 'citrus' }, + { id: '10', name: 'Mango', type: 'stony' }, + { id: '11', name: 'Nectarine', type: 'stony' }, + { id: '12', name: 'Orange', type: 'citrus' }, + { id: '13', name: 'Papaya', type: 'seedy' }, + { id: '14', name: 'Quince', type: 'seedy' }, + { id: '15', name: 'Raspberry', type: 'berryish' }, + { id: '16', name: 'Strawberry', type: 'berryish' }, + { id: '17', name: 'Tangerine', type: 'citrus' }, + { id: '19', name: 'Vanilla bean', type: 'longish' }, + { id: '20', name: 'Watermelon', type: 'chonky' }, + { id: '22', name: 'Yellow Passion Fruit', type: 'seedy' }, + { id: '23', name: 'Zucchini', type: 'longish' }, + { id: '24', name: 'Cantaloupe', type: 'chonky' }, + { id: '25', name: 'Blackberry', type: 'berryish' }, + { id: '26', name: 'Persimmon', type: 'seedy' }, + { id: '27', name: 'Lychee', type: 'berryish' }, + { id: '28', name: 'Dragon Fruit', type: 'seedy' }, + { id: '29', name: 'Passion Fruit', type: 'seedy' }, + { id: '30', name: 'Starfruit', type: 'seedy' }, ]; - type Fruit = (typeof items)[number]; const getItemId = useCallback((item: Fruit) => item.id, []); const getIsItemDisabled = useCallback((item: Fruit) => !!item.disabled, []); @@ -66,12 +72,16 @@ const ComboboxDemo: React.FC = () => { ) : items; + const groupedResults = withGroups + ? Object.groupBy(results, (item) => item.type) + : results; + return ( ; -export const Example: Story = { - args: { - // Adding these types to keep TS happy, they're not passed on to `ComboboxDemo` - label: '', - value: '', - onChange: () => undefined, - items: [], - getItemId: () => '', - renderItem: () => <>Nothing, - onSelectItem: () => undefined, - }, +// These args are just used to keep TS happy, +// they're not passed to `ComboboxDemo` +const dummyArgs = { + label: '', + value: '', + onChange: () => undefined, + items: [], + getItemId: () => '', + renderItem: () => <>Nothing, + onSelectItem: () => undefined, +}; + +export const Simple: Story = { + args: dummyArgs, +}; + +export const WithGroups: Story = { + render: () => , + args: dummyArgs, }; diff --git a/app/javascript/flavours/glitch/components/form_fields/combobox_field.tsx b/app/javascript/flavours/glitch/components/form_fields/combobox_field.tsx index bc7bf44f69..3ddd8e559e 100644 --- a/app/javascript/flavours/glitch/components/form_fields/combobox_field.tsx +++ b/app/javascript/flavours/glitch/components/form_fields/combobox_field.tsx @@ -1,4 +1,11 @@ -import { forwardRef, useCallback, useId, useRef, useState } from 'react'; +import { + forwardRef, + useCallback, + useId, + useMemo, + useRef, + useState, +} from 'react'; import { useIntl } from 'react-intl'; @@ -28,10 +35,10 @@ export interface ComboboxItemState { isDisabled: boolean; } -interface ComboboxProps extends Omit< - TextInputProps, - 'icon' -> { +interface ComboboxProps< + Item extends ComboboxItem, + GroupKey extends string, +> extends Omit { /** * The value of the combobox's text input */ @@ -46,34 +53,44 @@ interface ComboboxProps extends Omit< */ isLoading?: boolean; /** - * The set of options/suggestions that should be rendered in the dropdown menu. + * The set of options/suggestions that should be rendered in the dropdown menu, + * optionally separated into groups by providing an object */ - items: T[]; + items: Item[] | Partial>; /** * A function that must return a unique id for each option passed via `items` */ - getItemId?: (item: T) => string; + getItemId?: (item: Item) => string; /** * Providing this function turns the combobox into a multi-select box that assumes * multiple options to be selectable. Single-selection is handled automatically. */ - getIsItemSelected?: (item: T) => boolean; + getIsItemSelected?: (item: Item) => boolean; /** * Use this function to mark items as disabled, if needed */ - getIsItemDisabled?: (item: T) => boolean; + getIsItemDisabled?: (item: Item) => boolean; /** * Customise the rendering of each option. * The rendered content must not contain other interactive content! */ renderItem: ( - item: T, + item: Item, state: ComboboxItemState, ) => React.ReactElement | string; + /** + * Customise the rendering of group titles. + * The `titleId` must be attached to the element that provides the + * accessible name for the group. + */ + renderGroupTitle?: ( + groupKey: GroupKey, + titleId: string, + ) => React.ReactElement | string; /** * The main selection handler, called when an option is selected or deselected. */ - onSelectItem: (item: T) => void; + onSelectItem: (item: Item) => void; /** * Icon to be displayed in the text input */ @@ -88,8 +105,8 @@ interface ComboboxProps extends Omit< suppressMenu?: boolean; } -interface Props - extends ComboboxProps, CommonFieldWrapperProps {} +interface Props + extends ComboboxProps, CommonFieldWrapperProps {} /** * The combobox field allows users to select one or more items @@ -100,8 +117,11 @@ interface Props * [research & implementations](https://sarahmhigley.com/writing/select-your-poison/). */ -export const ComboboxFieldWithRef = ( - { id, label, hint, status, required, ...otherProps }: Props, +export const ComboboxFieldWithRef = < + Item extends ComboboxItem, + GroupKey extends string, +>( + { id, label, hint, status, required, ...otherProps }: Props, ref: React.ForwardedRef, ) => ( ( // Using a type assertion to maintain the full type signature of ComboboxWithRef // (including its generic type) after wrapping it with `forwardRef`. export const ComboboxField = forwardRef(ComboboxFieldWithRef) as { - ( - props: Props & { ref?: React.ForwardedRef }, + ( + props: Props & { + ref?: React.ForwardedRef; + }, ): ReturnType; displayName: string; }; ComboboxField.displayName = 'ComboboxField'; -const ComboboxWithRef = ( +const ComboboxWithRef = ( { value, isLoading = false, @@ -135,6 +157,7 @@ const ComboboxWithRef = ( getIsItemDisabled, getIsItemSelected, disabled, + renderGroupTitle, renderItem, onSelectItem, onChange, @@ -144,7 +167,7 @@ const ComboboxWithRef = ( icon = SearchIcon, className, ...otherProps - }: ComboboxProps, + }: ComboboxProps, ref: React.ForwardedRef, ) => { const intl = useIntl(); @@ -157,15 +180,23 @@ const ComboboxWithRef = ( ); const [shouldMenuOpen, setShouldMenuOpen] = useState(false); + const hasGroups = !Array.isArray(items); + const flatItems = useMemo( + () => (hasGroups ? (Object.values(items).flat() as Item[]) : items), + [hasGroups, items], + ); + const statusMessage = useGetA11yStatusMessage({ value, isLoading, - itemCount: items.length, + itemCount: flatItems.length, }); const showStatusMessageInMenu = - !!statusMessage && value.length > 0 && items.length === 0; + !!statusMessage && value.length > 0 && flatItems.length === 0; const hasMenuContent = - !disabled && !suppressMenu && (items.length > 0 || showStatusMessageInMenu); + !disabled && + !suppressMenu && + (flatItems.length > 0 || showStatusMessageInMenu); const isMenuOpen = shouldMenuOpen && hasMenuContent; const openMenu = useCallback(() => { @@ -178,10 +209,10 @@ const ComboboxWithRef = ( }, []); const resetHighlight = useCallback(() => { - const firstItem = items[0]; + const firstItem = flatItems[0]; const firstItemId = firstItem ? getItemId(firstItem) : null; setHighlightedItemId(firstItemId); - }, [getItemId, items]); + }, [getItemId, flatItems]); const highlightItem = useCallback((id: string | null) => { setHighlightedItemId(id); @@ -216,7 +247,7 @@ const ComboboxWithRef = ( const selectItem = useCallback( (itemId: string | null) => { - const item = items.find((item) => item.id === itemId); + const item = flatItems.find((item) => item.id === itemId); if (item) { const isDisabled = getIsItemDisabled?.(item) ?? false; if (!isDisabled) { @@ -229,7 +260,7 @@ const ComboboxWithRef = ( } inputRef.current?.focus(); }, - [closeMenu, closeOnSelect, getIsItemDisabled, items, onSelectItem], + [closeMenu, closeOnSelect, getIsItemDisabled, flatItems, onSelectItem], ); const handleSelectItem = useCallback( @@ -246,38 +277,38 @@ const ComboboxWithRef = ( const moveHighlight = useCallback( (direction: number) => { - if (items.length === 0) { + if (flatItems.length === 0) { return; } - const highlightedItemIndex = items.findIndex( + const highlightedItemIndex = flatItems.findIndex( (item) => getItemId(item) === highlightedItemId, ); if (highlightedItemIndex === -1) { // If no item is highlighted yet, highlight the first or last if (direction > 0) { - const firstItem = items.at(0); + const firstItem = flatItems.at(0); highlightItem(firstItem ? getItemId(firstItem) : null); } else { - const lastItem = items.at(-1); + const lastItem = flatItems.at(-1); highlightItem(lastItem ? getItemId(lastItem) : null); } } else { // If there is a highlighted item, select the next or previous item // and wrap around at the start or end: let newIndex = highlightedItemIndex + direction; - if (newIndex >= items.length) { + if (newIndex >= flatItems.length) { newIndex = 0; } else if (newIndex < 0) { - newIndex = items.length - 1; + newIndex = flatItems.length - 1; } - const newHighlightedItem = items[newIndex]; + const newHighlightedItem = flatItems[newIndex]; highlightItem( newHighlightedItem ? getItemId(newHighlightedItem) : null, ); } }, - [getItemId, highlightItem, highlightedItemId, items], + [getItemId, highlightItem, highlightedItemId, flatItems], ); useOnClickOutside(wrapperRef, closeMenu); @@ -331,6 +362,38 @@ const ComboboxWithRef = ( ], ); + const renderItems = (items: Item[]) => + items.map((item) => { + const id = getItemId(item); + const isDisabled = getIsItemDisabled?.(item); + const isHighlighted = id === highlightedItemId; + // If `getIsItemSelected` is defined, we assume 'multi-select' + // behaviour and don't set `aria-selected` based on highlight, + // but based on selected item state. + const isSelected = getIsItemSelected + ? getIsItemSelected(item) + : isHighlighted; + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events +
  • + {renderItem(item, { + isSelected, + isDisabled: isDisabled ?? false, + })} +
  • + ); + }); + const mergeRefs = useCallback( (element: HTMLInputElement | null) => { inputRef.current = element; @@ -406,42 +469,42 @@ const ComboboxWithRef = ( > {({ props, placement }) => (
    - {showStatusMessageInMenu ? ( - {statusMessage} - ) : ( -
      - {items.map((item) => { - const id = getItemId(item); - const isDisabled = getIsItemDisabled?.(item); - const isHighlighted = id === highlightedItemId; - // If `getIsItemSelected` is defined, we assume 'multi-select' - // behaviour and don't set `aria-selected` based on highlight, - // but based on selected item state. - const isSelected = getIsItemSelected - ? getIsItemSelected(item) - : isHighlighted; - return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events -
    • - {renderItem(item, { - isSelected, - isDisabled: isDisabled ?? false, - })} -
    • - ); - })} -
    - )} + + {hasGroups ? ( +
    + {(Object.keys(items) as GroupKey[]).map((groupKey) => { + const groupItems = items[groupKey]; + const groupTitleId = `${listId}-group-${groupKey}`; + const groupTitle = renderGroupTitle?.( + groupKey, + groupTitleId, + ) ?? {groupKey}; + + if (!groupItems?.length) return null; + + return ( +
      +
    • + {groupTitle} +
    • + {renderItems(groupItems)} +
    + ); + })} +
    + ) : ( +
      + {renderItems(items)} +
    + )} +
    )} @@ -452,14 +515,28 @@ const ComboboxWithRef = ( // Using a type assertion to maintain the full type signature of ComboboxWithRef // (including its generic type) after wrapping it with `forwardRef`. export const Combobox = forwardRef(ComboboxWithRef) as { - ( - props: ComboboxProps & { ref?: React.ForwardedRef }, + ( + props: ComboboxProps & { + ref?: React.ForwardedRef; + }, ): ReturnType; displayName: string; }; Combobox.displayName = 'Combobox'; +const StatusMessageWrapper: React.FC<{ + showStatus: boolean; + status: string; + children: React.ReactNode; +}> = ({ showStatus, status, children }) => { + if (showStatus) { + return {status}; + } + + return children; +}; + function useGetA11yStatusMessage({ itemCount, value, diff --git a/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx b/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx index 879e46c241..5e512212d5 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx @@ -4,6 +4,7 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; +import type { ApiMutedAccountJSON } from 'flavours/glitch/api_types/accounts'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; import { AccountListItem } from 'flavours/glitch/components/account_list_item'; import { Avatar } from 'flavours/glitch/components/avatar'; @@ -57,13 +58,10 @@ const AddedAccountItem: React.FC<{ return ; }; -interface SuggestionItem { - id: string; - isDisabled?: boolean; -} - -const SuggestedAccountItem: React.FC = ({ id }) => { - const account = useAccount(id); +const SuggestedAccountItem: React.FC<{ account: ApiMutedAccountJSON }> = ( + props, +) => { + const account = useAccount(props.account.id); if (!account) return null; @@ -75,12 +73,15 @@ const SuggestedAccountItem: React.FC = ({ id }) => { ); }; -const renderAccountItem = (item: SuggestionItem) => ( - +const renderAccountItem = (account: ApiMutedAccountJSON) => ( + ); -const getItemId = (item: SuggestionItem) => item.id; -const getIsItemDisabled = (item: SuggestionItem) => item.isDisabled ?? false; +const getItemId = (account: ApiMutedAccountJSON) => account.id; + +// Disable accounts who can't be added to a collection +const getIsItemDisabled = (account: ApiMutedAccountJSON) => + !['automatic', 'manual'].includes(account.feature_approval.current_user); export const CollectionAccounts: React.FC<{ collection?: ApiCollectionJSON | null; @@ -120,14 +121,6 @@ export const CollectionAccounts: React.FC<{ filterResults: (account) => !accountIds.includes(account.id), }); - const suggestedItems = suggestedAccounts.map(({ id, feature_approval }) => ({ - id, - // Disable accounts who can't be added to a collection - isDisabled: !['automatic', 'manual'].includes( - feature_approval.current_user, - ), - })); - const handleSearchValueChange = useCallback( (e: React.ChangeEvent) => { setSearchValue(e.target.value); @@ -158,7 +151,7 @@ export const CollectionAccounts: React.FC<{ ); const addAccountItem = useCallback( - (item: SuggestionItem) => { + (item: ApiMutedAccountJSON) => { dispatch( updateCollectionEditorField({ field: 'accountIds', @@ -192,7 +185,7 @@ export const CollectionAccounts: React.FC<{ ); const instantAddAccountItem = useCallback( - (item: SuggestionItem) => { + (item: ApiMutedAccountJSON) => { if (id) { void dispatch( addCollectionItem({ collectionId: id, accountId: item.id }), @@ -214,7 +207,7 @@ export const CollectionAccounts: React.FC<{ ); const handleSelectItem = useCallback( - (item: SuggestionItem) => { + (item: ApiMutedAccountJSON) => { if (isEditMode) { instantAddAccountItem(item); } else { @@ -269,7 +262,7 @@ export const CollectionAccounts: React.FC<{ onKeyDown={handleSearchKeyDown} disabled={hasMaxAccounts} isLoading={isLoadingSuggestions} - items={suggestedItems} + items={suggestedAccounts} getItemId={getItemId} getIsItemDisabled={getIsItemDisabled} renderItem={renderAccountItem} From 582a8a0880e851abe5f7695a5618c1b19abc1b48 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 17 Apr 2026 17:13:18 +0200 Subject: [PATCH 37/37] [Glitch] Update design of account search dropdown in collection editor Port 05a1c170c285221860e27cb3239092401c3cca8b to glitch-soc Signed-off-by: Claire --- .../form_fields/combobox.module.scss | 10 +- .../components/form_fields/combobox_field.tsx | 30 ++++-- .../glitch/components/list_item/index.tsx | 67 ++++++++------ .../list_item/list_item.stories.tsx | 17 +++- .../glitch/components/media_attachments.jsx | 6 +- .../features/collections/editor/accounts.tsx | 92 +++++++++++++++++-- .../features/collections/editor/details.tsx | 22 ++--- .../collections/editor/styles.module.scss | 16 ++++ 8 files changed, 191 insertions(+), 69 deletions(-) diff --git a/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss b/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss index ca7bc2020b..ef230a77b8 100644 --- a/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss +++ b/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss @@ -25,7 +25,7 @@ .popover { z-index: 9999; box-sizing: border-box; - max-height: max(200px, 30dvh); + max-height: min(320px, 50dvh); padding: 4px; border-radius: 4px; color: var(--color-text-primary); @@ -63,13 +63,7 @@ user-select: none; &[data-highlighted='true'] { - color: var(--color-text-on-brand-base); - background: var(--color-bg-brand-base); - - &[aria-disabled='true'] { - color: var(--color-text-on-disabled); - background: var(--color-bg-disabled); - } + background: var(--color-bg-overlay-highlight); } &[aria-disabled='true'] { diff --git a/app/javascript/flavours/glitch/components/form_fields/combobox_field.tsx b/app/javascript/flavours/glitch/components/form_fields/combobox_field.tsx index 3ddd8e559e..8ad8ea7e96 100644 --- a/app/javascript/flavours/glitch/components/form_fields/combobox_field.tsx +++ b/app/javascript/flavours/glitch/components/form_fields/combobox_field.tsx @@ -82,11 +82,12 @@ interface ComboboxProps< * Customise the rendering of group titles. * The `titleId` must be attached to the element that provides the * accessible name for the group. + * Return `null` to omit rendering the group title. */ renderGroupTitle?: ( groupKey: GroupKey, titleId: string, - ) => React.ReactElement | string; + ) => React.ReactElement | null; /** * The main selection handler, called when an option is selected or deselected. */ @@ -182,7 +183,12 @@ const ComboboxWithRef = ( const hasGroups = !Array.isArray(items); const flatItems = useMemo( - () => (hasGroups ? (Object.values(items).flat() as Item[]) : items), + () => + hasGroups + ? (Object.values(items) + .flat() + .filter((i) => !!i) as Item[]) + : items, [hasGroups, items], ); @@ -478,10 +484,11 @@ const ComboboxWithRef = ( {(Object.keys(items) as GroupKey[]).map((groupKey) => { const groupItems = items[groupKey]; const groupTitleId = `${listId}-group-${groupKey}`; - const groupTitle = renderGroupTitle?.( + const customGroupTitle = renderGroupTitle?.( groupKey, groupTitleId, - ) ?? {groupKey}; + ); + const hasTitle = customGroupTitle !== null; if (!groupItems?.length) return null; @@ -489,11 +496,18 @@ const ComboboxWithRef = (
      -
    • - {groupTitle} -
    • + {hasTitle && ( +
    • + {customGroupTitle ?? ( + {groupKey} + )} +
    • + )} {renderItems(groupItems)}
    ); diff --git a/app/javascript/flavours/glitch/components/list_item/index.tsx b/app/javascript/flavours/glitch/components/list_item/index.tsx index 66758bd4de..263e206fb9 100644 --- a/app/javascript/flavours/glitch/components/list_item/index.tsx +++ b/app/javascript/flavours/glitch/components/list_item/index.tsx @@ -14,8 +14,9 @@ interface WrapperProps extends Omit< /** * A basic list item component that can be used as a base for more bespoke list items. * - * Depending on functionality, use `ListItemButton` or `ListItemLink` as a child of the - * wrapper component. + * Choose the child of the wrapper component based on needed interactivity: + * `ListItemContent` for a non-interactive item, `ListItemButton` or `ListItemLink` + * for interactive items. */ export const ListItemWrapper: React.FC = ({ icon, @@ -33,10 +34,31 @@ export const ListItemWrapper: React.FC = ({ ); }; -interface LinkProps extends React.ComponentPropsWithoutRef { +interface WithSubtitle { subtitle?: React.ReactNode; } +interface ContentProps + extends React.ComponentPropsWithoutRef<'h3'>, WithSubtitle {} + +export const ListItemContent: React.FC = ({ + subtitle, + children, + ...otherProps +}) => { + return ( + <> +

    + {children} +

    + {subtitle &&
    {subtitle}
    } + + ); +}; + +interface LinkProps + extends React.ComponentPropsWithoutRef, WithSubtitle {} + export const ListItemLink: React.FC = ({ subtitle, children, @@ -44,20 +66,16 @@ export const ListItemLink: React.FC = ({ ...otherProps }) => { return ( - <> -

    - - {children} - -

    - {subtitle &&
    {subtitle}
    } - + + + {children} + + ); }; -interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> { - subtitle?: React.ReactNode; -} +interface ButtonProps + extends React.ComponentPropsWithoutRef<'button'>, WithSubtitle {} export const ListItemButton: React.FC = ({ subtitle, @@ -66,17 +84,14 @@ export const ListItemButton: React.FC = ({ ...otherProps }) => { return ( - <> -

    - -

    - {subtitle &&
    {subtitle}
    } - + + + ); }; diff --git a/app/javascript/flavours/glitch/components/list_item/list_item.stories.tsx b/app/javascript/flavours/glitch/components/list_item/list_item.stories.tsx index d46102c1c3..dd066683c9 100644 --- a/app/javascript/flavours/glitch/components/list_item/list_item.stories.tsx +++ b/app/javascript/flavours/glitch/components/list_item/list_item.stories.tsx @@ -7,18 +7,31 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac import { Icon } from '../icon'; -import { ListItemWrapper, ListItemButton, ListItemLink } from './index'; +import { + ListItemWrapper, + ListItemContent, + ListItemButton, + ListItemLink, +} from './index'; const meta = { title: 'Components/ListItem', component: ListItemWrapper, - subcomponents: { ListItemButton, ListItemLink }, + subcomponents: { ListItemContent, ListItemButton, ListItemLink }, } satisfies Meta; export default meta; type Story = StoryObj; +export const NonInteractive: Story = { + render: () => ( + }> + View more + + ), +}; + export const WithButton: Story = { render: () => ( + {Component => ( + {Component => ( + {Component => ( ; }; -const SuggestedAccountItem: React.FC<{ account: ApiMutedAccountJSON }> = ( - props, -) => { - const account = useAccount(props.account.id); +const SuggestedAccountItem: React.FC<{ id: string }> = ({ id }) => { + const account = useAccount(id); + const handle = useAccountHandle(account, domain); if (!account) return null; return ( - <> - - - + } + > + + + + ); }; const renderAccountItem = (account: ApiMutedAccountJSON) => ( - + ); +type GroupKey = 'available' | 'mustFollow' | 'disabled'; + +function groupSuggestions(accounts: ApiMutedAccountJSON[]) { + const { available, disabled } = Object.groupBy(accounts, (account) => { + if (getIsItemDisabled(account)) { + return 'disabled'; + } + // if (account.locked && !relationship?.following) { + // return 'mustFollow'; + // } + return 'available'; + }); + + // Returning a new object ensures a fixed property order + return { available, disabled }; +} + +const renderGroupTitle = (groupKey: GroupKey, titleId: string) => { + if (groupKey === 'available') { + return null; + } + + let title: React.ReactElement; + let description: React.ReactElement; + + if (groupKey === 'mustFollow') { + title = ( + + ); + description = ( + + ); + } else { + title = ( + + ); + description = ( + + ); + } + + return ( + + + {title} + + + ); +}; + const getItemId = (account: ApiMutedAccountJSON) => account.id; // Disable accounts who can't be added to a collection @@ -262,10 +333,11 @@ export const CollectionAccounts: React.FC<{ onKeyDown={handleSearchKeyDown} disabled={hasMaxAccounts} isLoading={isLoadingSuggestions} - items={suggestedAccounts} + items={groupSuggestions(suggestedAccounts)} getItemId={getItemId} getIsItemDisabled={getIsItemDisabled} renderItem={renderAccountItem} + renderGroupTitle={renderGroupTitle} onSelectItem={handleSelectItem} status={ hasMaxAccounts diff --git a/app/javascript/flavours/glitch/features/collections/editor/details.tsx b/app/javascript/flavours/glitch/features/collections/editor/details.tsx index a85d84b118..5518570fe9 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/details.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/details.tsx @@ -276,18 +276,16 @@ export const CollectionDetails: React.FC = () => {
    -
    - -
    +
    ); diff --git a/app/javascript/flavours/glitch/features/collections/editor/styles.module.scss b/app/javascript/flavours/glitch/features/collections/editor/styles.module.scss index 577a6d7d12..1befc06cc1 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/styles.module.scss +++ b/app/javascript/flavours/glitch/features/collections/editor/styles.module.scss @@ -58,3 +58,19 @@ .scrollableWrapper { margin-inline: -16px; } + +.suggestion { + padding: 4px 0; + + [aria-disabled='true'] & { + opacity: 0.6; + } +} + +.suggestionGroup { + padding: 4px 0; + + // Undo default group styles: + font-weight: 400; + text-transform: none; +}