From c6be114cef98b219869cd3181d4eda9243dce5c6 Mon Sep 17 00:00:00 2001 From: Shlee Date: Tue, 13 Jan 2026 22:47:48 +0700 Subject: [PATCH 001/145] Non-ActivityPub Link header alternate blocks HTML ActivityPub discovery in FetchResourceService (#37439) Co-authored-by: Claire --- app/services/fetch_resource_service.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/services/fetch_resource_service.rb b/app/services/fetch_resource_service.rb index 666eccb9ce5..514c838d648 100644 --- a/app/services/fetch_resource_service.rb +++ b/app/services/fetch_resource_service.rb @@ -58,13 +58,7 @@ class FetchResourceService < BaseService [@url, { prefetched_body: body }] elsif !terminal - link_header = response['Link'] && parse_link_header(response) - - if link_header&.find_link(%w(rel alternate)) - process_link_headers(link_header) - elsif response.mime_type == 'text/html' - process_html(response) - end + process_link_headers(response) || process_html(response) end end @@ -73,13 +67,18 @@ class FetchResourceService < BaseService end def process_html(response) + return unless response.mime_type == 'text/html' + page = Nokogiri::HTML5(response.body_with_limit) json_link = page.xpath('//link[nokogiri:link_rel_include(@rel, "alternate")]', NokogiriHandler).find { |link| ACTIVITY_STREAM_LINK_TYPES.include?(link['type']) } process(json_link['href'], terminal: true) unless json_link.nil? end - def process_link_headers(link_header) + def process_link_headers(response) + link_header = response['Link'] && parse_link_header(response) + return if link_header.nil? + json_link = link_header.find_link(%w(rel alternate), %w(type application/activity+json)) || link_header.find_link(%w(rel alternate), ['type', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"']) process(json_link.href, terminal: true) unless json_link.nil? From 92ad380e118ebf12db344786f6862b1eb64365d0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 13 Jan 2026 11:21:25 -0500 Subject: [PATCH 002/145] Update rubocop to version 1.82.1 (#37475) --- Gemfile.lock | 6 ++--- app/workers/move_worker.rb | 36 ++++++++++++++------------ lib/paperclip/attachment_extensions.rb | 3 +-- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2167d017395..e7519ea90bc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -752,7 +752,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.6) - rubocop (1.81.7) + rubocop (1.82.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -760,7 +760,7 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.47.1, < 2.0) + rubocop-ast (>= 1.48.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.49.0) @@ -782,7 +782,7 @@ GEM rack (>= 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.8.0) + rubocop-rspec (3.9.0) lint_roller (~> 1.1) rubocop (~> 1.81) rubocop-rspec_rails (2.32.0) diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb index 1a5745a86ae..43238b72b20 100644 --- a/app/workers/move_worker.rb +++ b/app/workers/move_worker.rb @@ -45,30 +45,34 @@ class MoveWorker end # Then handle accounts that follow both the old and new account - @source_account.passive_relationships - .where(account: Account.local) - .where(account: @target_account.followers.local) - .in_batches do |follows| - ListAccount.where(follow: follows).includes(:list).find_each do |list_account| - list_account.list.accounts << @target_account - rescue ActiveRecord::RecordInvalid - nil - end + source_local_followers + .where(account: @target_account.followers.local) + .in_batches do |follows| + ListAccount.where(follow: follows).includes(:list).find_each do |list_account| + list_account.list.accounts << @target_account + rescue ActiveRecord::RecordInvalid + nil + end end # Finally, handle the common case of accounts not following the new account - @source_account.passive_relationships - .where(account: Account.local) - .where.not(account: @target_account.followers.local) - .where.not(account_id: @target_account.id) - .in_batches do |follows| - ListAccount.where(follow: follows).in_batches.update_all(account_id: @target_account.id) - num_moved += follows.update_all(target_account_id: @target_account.id) + source_local_followers + .where.not(account: @target_account.followers.local) + .where.not(account_id: @target_account.id) + .in_batches do |follows| + ListAccount.where(follow: follows).in_batches.update_all(account_id: @target_account.id) + num_moved += follows.update_all(target_account_id: @target_account.id) end num_moved end + def source_local_followers + @source_account + .passive_relationships + .where(account: Account.local) + end + def queue_follow_unfollows! bypass_locked = @target_account.local? diff --git a/lib/paperclip/attachment_extensions.rb b/lib/paperclip/attachment_extensions.rb index 011e165ed74..7141adc9edc 100644 --- a/lib/paperclip/attachment_extensions.rb +++ b/lib/paperclip/attachment_extensions.rb @@ -16,8 +16,7 @@ module Paperclip # if we're processing the original, close + unlink the source tempfile intermediate_files << original if name == :original - @queued_for_write[name] = style.processors - .inject(original) do |file, processor| + @queued_for_write[name] = style.processors.inject(original) do |file, processor| file = Paperclip.processor(processor).make(file, style.processor_options, self) intermediate_files << file unless file == original file From 19bc3e76ea1d7fa2d9af711613a77fc0f5f0d1b5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 13 Jan 2026 11:21:55 -0500 Subject: [PATCH 003/145] Add spec for quote policy update change (#37474) --- spec/requests/api/v1/statuses_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 5db9889e2d5..3fbf26c54a7 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -508,6 +508,15 @@ RSpec.describe '/api/v1/statuses' do .to start_with('application/json') end end + + context 'when status has non-default quote policy and param is omitted' do + let(:status) { Fabricate(:status, account: user.account, quote_approval_policy: 'nobody') } + + it 'preserves existing quote approval policy' do + expect { subject } + .to_not(change { status.reload.quote_approval_policy }) + end + end end end From ccf3ed0ddf8811d212dd3f94f177eb54471ed609 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:02:07 +0100 Subject: [PATCH 004/145] Update Node.js to 24.13 (#37473) 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 b0195acf781..12fd1fc2777 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.12 +24.13 From 2f91d9755d77fbe5902593ac49da1a3665e7b3a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:02:11 +0100 Subject: [PATCH 005/145] New Crowdin Translations (automated) (#37482) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/gd.json | 51 ++++++++++++++++++++++++- config/locales/activerecord.gd.yml | 6 +++ config/locales/gd.yml | 22 +++++++++++ config/locales/simple_form.gd.yml | 7 ++++ 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 2f4cb44de80..e48c60ff2ce 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -113,11 +113,52 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Mìnich seo dhan fheadhainn air a bheil cion-lèirsinne…", "alt_text_modal.done": "Deiseil", "announcement.announcement": "Brath-fios", + "annual_report.announcement.action_build": "Cruthaich Wrapstodon dhomh", + "annual_report.announcement.action_dismiss": "Na cruthaich", + "annual_report.announcement.action_view": "Seall an Wrapstodon agam", + "annual_report.announcement.description": "Fidir mar a chaidh leat air Mastodon am bliadhna.", + "annual_report.announcement.title": "Tha Wrapstodon {year} air a thighinn", + "annual_report.nav_item.badge": "Ùr", + "annual_report.shared_page.donate": "Thoir tabhartas", + "annual_report.shared_page.footer": "Chaidh a ghintinn le {heart} le sgioba Mhastodon", + "annual_report.shared_page.footer_server_info": "Tha {username} a’ cleachdadh {domain}, sin fear de dh’iomadh coimhearsnachd le cumhachd Mastodon.", + "annual_report.summary.archetype.booster.desc_public": "Bhiodh {name} an toir air postaichean rim brosnachadh, a’ toirt taic do chruthadairean gu sgiobalta.", + "annual_report.summary.archetype.booster.desc_self": "Bhiodh tu an toir air postaichean rim brosnachadh, a’ toirt taic do chruthadairean gu sgiobalta.", + "annual_report.summary.archetype.booster.name": "Am Boghadair", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.archetype.lurker.desc_public": "Tha fios againn gun robh {name} ann am badeigin, a’ gabhail tlachd à Mastodon air an dòigh fhèin.", + "annual_report.summary.archetype.lurker.desc_self": "Tha fios againn gun robh thu ann am badeigin, a’ gabhail tlachd à Mastodon air do dhòigh fhèin.", + "annual_report.summary.archetype.lurker.name": "An Socaireach", + "annual_report.summary.archetype.oracle.desc_public": "Chruthaich {name} barrachd phostaichean ùra na freagairtean, a’ cumail Mastodon ùr ’s beòthail.", + "annual_report.summary.archetype.oracle.desc_self": "Chruthaich thu barrachd phostaichean ùra na freagairtean, a’ cumail Mastodon ùr ’s beòthail.", + "annual_report.summary.archetype.oracle.name": "Coinneach Odhar", + "annual_report.summary.archetype.pollster.desc_public": "Chruthaich {name} barrachd chunntasan-beachd na seòrsaichean eile de phost, a’ cur ris an iongantas air Mastodon.", + "annual_report.summary.archetype.pollster.desc_self": "Chruthaich thu barrachd chunntasan-beachd na seòrsaichean eile de phost, a’ cur ris an iongantas air Mastodon.", + "annual_report.summary.archetype.pollster.name": "An Culaidh-iongantais", + "annual_report.summary.archetype.replier.desc_public": "Fhreagradh {name} gu tric air postaichean càich, a’ cur deasbadan ùra gu dol air Mastodon.", + "annual_report.summary.archetype.replier.desc_self": "Fhreagradh tu gu tric air postaichean càich, a’ cur deasbadan ùra gu dol air Mastodon.", + "annual_report.summary.archetype.replier.name": "An Dealan-dè", + "annual_report.summary.archetype.reveal": "Nochd am prìomh-choltas a th’ orm", + "annual_report.summary.archetype.reveal_description": "Mòran taing airson conaltradh air Mastodon! Thàinig an t-àm a gheibheadh tu a-mach dè am prìomh-choltas a bh’ ort {year}.", + "annual_report.summary.archetype.title_public": "Am prìomh-choltas air {name}", + "annual_report.summary.archetype.title_self": "Am prìomh-choltas a th’ ort", + "annual_report.summary.close": "Dùin", + "annual_report.summary.copy_link": "Dèan lethbhreac dhen cheangal", + "annual_report.summary.followers.new_followers": "{count, plural, one {neach-leantainn ùr} two {neach-leantainn ùra} few {luchd-leantainn ùra} other {luchd-leantainn ùra}}", + "annual_report.summary.highlighted_post.boost_count": "Chaidh am post seo a bhrosnachadh {count, plural, one {# turas} two {# thuras} few {# tursan} other {# turas}}.", + "annual_report.summary.highlighted_post.favourite_count": "Chaidh am post seo a chur ris na h-annsachdan {count, plural, one {# turas} two {# thuras} few {# tursan} other {# turas}}.", + "annual_report.summary.highlighted_post.reply_count": "Fhuair am post seo {count, plural, one {# fhreagairt} two {# fhreagairt} few {# freagairtean} other {# freagairt}}.", + "annual_report.summary.highlighted_post.title": "Am post air an robh fèill as motha", "annual_report.summary.most_used_app.most_used_app": "an aplacaid a chaidh a cleachdadh as trice", "annual_report.summary.most_used_hashtag.most_used_hashtag": "an taga hais a chaidh a cleachdadh as trice", + "annual_report.summary.most_used_hashtag.used_count": "Ghabh thu a-staigh an taga hais seo ann an {count, plural, one {# phost} two {# phost} few {# postaichean} other {# post}}.", + "annual_report.summary.most_used_hashtag.used_count_public": "Ghabh {name} a-staigh an taga hais seo ann an {count, plural, one {# phost} two {# phost} few {# postaichean} other {# post}}.", "annual_report.summary.new_posts.new_posts": "postaichean ùra", "annual_report.summary.percentile.text": "Tha thu am measgdhen luchd-cleachdaidh as cliùitiche air {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Ainmeil ’nad latha ’s ’nad linn.", + "annual_report.summary.share_elsewhere": "Co-roinn am badeigin eile", + "annual_report.summary.share_message": "’S e {archetype} am prìomh-choltas a th’ orm!", + "annual_report.summary.share_on_mastodon": "Co-roinn air Mastodon", "attachments_list.unprocessed": "(gun phròiseasadh)", "audio.hide": "Falaich an fhuaim", "block_modal.remote_users_caveat": "Iarraidh sinn air an fhrithealaiche {domain} gun gèill iad ri do cho-dhùnadh. Gidheadh, chan eil barantas gun gèill iad on a làimhsicheas cuid a fhrithealaichean bacaidhean air dòigh eadar-dhealaichte. Dh’fhaoidte gum faic daoine gun chlàradh a-steach na postaichean poblach agad fhathast.", @@ -162,7 +203,7 @@ "column.favourites": "Annsachdan", "column.firehose": "An saoghal beò", "column.firehose_local": "Loidhne-ama bheò an fhrithealaiche seo", - "column.firehose_singular": "Loidhne-ama bheò beò", + "column.firehose_singular": "Loidhne-ama bheò", "column.follow_requests": "Iarrtasan leantainn", "column.home": "Dachaigh", "column.list_members": "Stiùir buill na liosta", @@ -343,6 +384,7 @@ "empty_column.notification_requests": "Glan! Chan eil dad an-seo. Nuair a gheibh thu brathan ùra, nochdaidh iad an-seo a-rèir nan roghainnean agad.", "empty_column.notifications": "Cha d’ fhuair thu brath sam bith fhathast. Nuair a nì càch conaltradh leat, chì thu an-seo e.", "empty_column.public": "Chan eil dad an-seo! Sgrìobh rudeigin gu poblach no lean càch o fhrithealaichean eile a làimh airson seo a lìonadh", + "error.no_hashtag_feed_access": "Cruthaich cunntas no clàraich a-steach airson an taga hais seo a shealltainn is leantainn.", "error.unexpected_crash.explanation": "Air sàilleibh buga sa chòd againn no duilgheadas co-chòrdalachd leis a’ bhrabhsair, chan urrainn dhuinn an duilleag seo a shealltainn mar bu chòir.", "error.unexpected_crash.explanation_addons": "Cha b’ urrainn dhuinn an duilleag seo a shealltainn mar bu chòir. Tha sinn an dùil gu do dh’adhbharaich tuilleadan a’ bhrabhsair no inneal eadar-theangachaidh fèin-obrachail a’ mhearachd.", "error.unexpected_crash.next_steps": "Feuch an ath-nuadhaich thu an duilleag seo. Mura cuidich sin, dh’fhaoidte gur urrainn dhut Mastodon a chleachdadh fhathast le brabhsair eile no le aplacaid thùsail.", @@ -399,6 +441,8 @@ "follow_suggestions.who_to_follow": "Molaidhean leantainn", "followed_tags": "Tagaichean hais ’gan leantainn", "footer.about": "Mu dhèidhinn", + "footer.about_mastodon": "Mu Mhastodon", + "footer.about_server": "Mu {domain}", "footer.about_this_server": "Mu dhèidhinn", "footer.directory": "Eòlaire nam pròifil", "footer.get_app": "Faigh an aplacaid", @@ -473,6 +517,7 @@ "keyboard_shortcuts.column": "Cuir am fòcas air colbh", "keyboard_shortcuts.compose": "Cuir am fòcas air raon teacsa an sgrìobhaidh", "keyboard_shortcuts.description": "Tuairisgeul", + "keyboard_shortcuts.direct": "Fosgail colbh nan iomraidhean prìobhaideach", "keyboard_shortcuts.down": "Gluais sìos air an liosta", "keyboard_shortcuts.enter": "Fosgail post", "keyboard_shortcuts.favourite": "Cuir am post ris na h-annsachdan", @@ -500,6 +545,7 @@ "keyboard_shortcuts.toggle_hidden": "Seall/Falaich an teacsa fo rabhadh susbainte", "keyboard_shortcuts.toggle_sensitivity": "Seall/Falaich na meadhanan", "keyboard_shortcuts.toot": "Tòisich air post ùr", + "keyboard_shortcuts.top": "Gluais gu bàrr na liosta", "keyboard_shortcuts.translate": "airson post eadar-theangachadh", "keyboard_shortcuts.unfocus": "Thoir am fòcas far raon teacsa an sgrìobhaidh/an luirg", "keyboard_shortcuts.up": "Gluais suas air an liosta", @@ -888,6 +934,7 @@ "status.edited_x_times": "Chaidh a dheasachadh {count, plural, one {{count} turas} two {{count} thuras} few {{count} tursan} other {{count} turas}}", "status.embed": "Faigh còd leabachaidh", "status.favourite": "Cuir ris na h-annsachdan", + "status.favourites_count": "{count, plural, one {{counter} annsachd} two {{counter} annsachd} few {{counter} annsachdan} other {{counter} annsachd}}", "status.filter": "Criathraich am post seo", "status.history.created": "Chruthaich {name} {date} e", "status.history.edited": "Dheasaich {name} {date} e", @@ -922,12 +969,14 @@ "status.quotes.empty": "Chan deach am post seo a luaidh le duine sam bith fhathast. Nuair a luaidheas cuideigin e, nochdaidh iad an-seo.", "status.quotes.local_other_disclaimer": "Cha tèid luaidhean a dhiùilt an ùghdar a shealltainn.", "status.quotes.remote_other_disclaimer": "Cha dèid ach luaidhean o {domain} a shealltainn an-seo le cinnt. Cha dèid luaidhean a dhiùilt an ùghdar a shealltainn.", + "status.quotes_count": "{count, plural, one {{counter} luaidh} two {{counter} luaidh} few {{counter} luaidhean} other {{counter} luaidh}}", "status.read_more": "Leugh an còrr", "status.reblog": "Brosnaich", "status.reblog_or_quote": "Brosnaich no luaidh", "status.reblog_private": "Co-roinn leis an luchd-leantainn agad a-rithist", "status.reblogged_by": "’Ga bhrosnachadh le {name}", "status.reblogs.empty": "Chan deach am post seo a bhrosnachadh le duine sam bith fhathast. Nuair a bhrosnaicheas cuideigin e, nochdaidh iad an-seo.", + "status.reblogs_count": "{count, plural, one {{counter} bhrosnachadh} two {{counter} bhrosnachadh} few {{counter} brosnachaidhean} other {{counter} brosnachadh}}", "status.redraft": "Sguab às ⁊ dèan dreachd ùr", "status.remove_bookmark": "Thoir an comharra-lìn air falbh", "status.remove_favourite": "Thoir air falbh o na h-annsachdan", diff --git a/config/locales/activerecord.gd.yml b/config/locales/activerecord.gd.yml index 6b068d04dc6..572fcc83df5 100644 --- a/config/locales/activerecord.gd.yml +++ b/config/locales/activerecord.gd.yml @@ -32,6 +32,12 @@ gd: attributes: url: invalid: "– chan eil seo ’na URL dligheach" + collection: + attributes: + collection_items: + too_many: "– tha cus dhiubh ann, chan eil còrr is %{count} ceadaichte" + tag: + unusable: "– chan fhaodar seo a chleachdadh" doorkeeper/application: attributes: website: diff --git a/config/locales/gd.yml b/config/locales/gd.yml index b637a9eb71f..8f64ee1984b 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -7,6 +7,8 @@ gd: hosted_on: Mastodon ’ga òstadh air %{domain} title: Mu dhèidhinn accounts: + errors: + cannot_be_added_to_collections: Cha ghabh an cunntas seo a chur ri cruinneachadh. followers: few: Luchd-leantainn one: Neach-leantainn @@ -874,6 +876,7 @@ gd: publish_statistics: Foillsich an stadastaireachd title: Rùrachadh trends: Treandaichean + wrapstodon: Wrapstodon domain_blocks: all: Dhan a h-uile duine disabled: Na seall idir @@ -1354,6 +1357,13 @@ gd: hint_html: "Gliocas: Chan iarr sinn am facal-faire agad ort a-rithist fad uair a thìde." invalid_password: Facal-faire mì-dhligheach prompt: Dearbh am facal-faire airson leantainn air adhart + color_scheme: + auto: Fèin-obrachail + dark: Dorcha + light: Soilleir + contrast: + auto: Fèin-obrachail + high: Àrd crypto: errors: invalid_key: "– chan e iuchair Ed25519 no Curve25519 dhligheach a th’ ann" @@ -1784,16 +1794,22 @@ gd: body: 'Thug %{name} iomradh ort an-seo:' subject: Thug %{name} iomradh ort title: Iomradh ùr + moderation_warning: + subject: Fhuair thu rabhadh on mhaorsainneachd poll: subject: Thàinig cunntas-bheachd le %{name} gu crìoch quote: body: 'Chaidh post a luaidh le %{name}:' subject: Luaidh %{name} am post agad title: Luaidh ùr + quoted_update: + subject: Dheasaich %{name} post a luaidh thu reblog: body: 'Chaidh am post agad a bhrosnachadh le %{name}:' subject: Bhrosnaich %{name} am post agad title: Brosnachadh ùr + severed_relationships: + subject: Chaill thu dàimhean ri linn co-dhùnadh na maorsainneachd status: subject: Tha %{name} air post a sgrìobhadh update: @@ -2043,9 +2059,11 @@ gd: enabled: Sguab às seann-phostaichean gu fèin-obrachail enabled_hint: Sguabaidh seo às na seann-phostaichean agad gu fèin-obrachail nuair a ruigeas iad stairsneach aoise sònraichte ach ma fhreagras iad ri gin dhe na h-eisgeachdan gu h-ìosal exceptions: Eisgeachdan + explanation: Tha prìomhachas ìosal air an sguabadh às fhèin-obrachail. Dh’fhaoidte gum bi dàil eadar ruigsinn stairsneach na h-aoise agus an toirt air falbh. ignore_favs: Leig seachad na h-annsachdan ignore_reblogs: Leig seachad na brosnachaidhean interaction_exceptions: Eisgeachdan stèidhichte air eadar-ghnìomhan + interaction_exceptions_explanation: Faodaidh postaichean mairsinn ma thèid iad thar stairsneach nan annsachdan no brosnachaidhean rè seal fiù ma bhios an cunntas as ìsle an uairsin. keep_direct: Cùm na teachdaireachdan dìreach keep_direct_hint: Cha dèid gin dhe na teachdaireachdan dìreach agad a sguabadh às keep_media: Cùm postaichean le ceanglachan meadhain @@ -2260,8 +2278,12 @@ gd: error: Bha duilgheadas ann le bhith a’ sguabadh às an iuchair tèarainteachd agad. Feuch ris a-rithist. success: Chaidh an iuchair tèarainteachd agad a sguabadh às. invalid_credential: Iuchair tèarainteachd mì-dhligheach + nickname: Far-ainm nickname_hint: Cuir a-steach far-ainm na h-iuchrach tèarainteachd ùir agad not_enabled: Cha do chuir thu WebAuthn an comas fhathast not_supported: Cha chuir am brabhsair seo taic ri iuchraichean tèarainteachd otp_required: Mus cleachd thu iuchraichean tèarainteachd, feumaidh tu an dearbhadh dà-cheumnach a chur an comas. registered_on: Air a chlàradh %{date} + wrapstodon: + description: Seall mar a chleachd %{name} Mastodon am bliadhna! + title: Wrapstodon %{year} dha %{name} diff --git a/config/locales/simple_form.gd.yml b/config/locales/simple_form.gd.yml index e11b300b33a..2673988df10 100644 --- a/config/locales/simple_form.gd.yml +++ b/config/locales/simple_form.gd.yml @@ -88,6 +88,7 @@ gd: activity_api_enabled: Cunntasan nam postaichean a chaidh fhoillseachadh gu h-ionadail, nan cleachdaichean gnìomhach ’s nan clàraidhean ùra an am bucaidean seachdaineil app_icon: WEBP, PNG, GIF no JPG. Tar-àithnidh seo ìomhaigheag bhunaiteach na h-aplacaid air uidheaman mobile le ìomhaigheag ghnàthaichte. backups_retention_period: "’S urrainn do chleachdaichean tasg-lannan dhe na postaichean aca a gintinn airson an luchdadh a-nuas an uairsin. Nuair a bhios luach dearbh air, thèid na tasg-lannan a sguabadh às on stòras agad gu fèin-obrachail às dèidh an àireamh de làithean a shònraich thu." + bootstrap_timeline_accounts: Thèid na cunntasan seo a phrìneachadh air bàrr nam molaidhean leantainn dhan luchd-cleachdaidh ùr. Solar liosta de chunntasan sgaraichte le cromagan. closed_registrations_message: Thèid seo a shealltainn nuair a bhios an clàradh dùinte content_cache_retention_period: Thèid a h-uile post o fhrithealaiche sam bith eile (a’ gabhail a-staigh brosnachaidhean is freagairtean) a sguabadh às às dèidh na h-àireimh de làithean a shònraich thu ’s gun diù a chon air eadar-ghabhail ionadail air na postaichean ud. Gabhaidh seo a-steach na postaichean a chuir cleachdaiche ionadail ris na h-annsachdan aca no comharran-lìn riutha. Thèid iomraidhean prìobhaideach eadar cleachdaichean o ionstansan diofraichte air chall cuideachd agus cha ghabh an aiseag idir. Tha an roghainn seo do dh’ionstansan sònraichte a-mhàin agus briseadh e dùilean an luchd-cleachdaidh nuair a rachadh a chleachdadh gu coitcheann. custom_css: "’S urrainn dhut stoidhlean gnàthaichte a chur an sàs air an tionndadh-lìn de Mhastodon." @@ -110,6 +111,7 @@ gd: thumbnail: Dealbh mu 2:1 a thèid a shealltainn ri taobh fiosrachadh an fhrithealaiche agad. trendable_by_default: Geàrr leum thar lèirmheas a làimh na susbainte a’ treandadh. Gabhaidh nithean fa leth a thoirt far nan treandaichean fhathast an uairsin. trends: Seallaidh na treandaichean na postaichean, tagaichean hais is naidheachdan a tha fèill mhòr orra air an fhrithealaiche agad. + wrapstodon: Tairg gintinn geàrr-chunntais àbhaich air mar a chleachd iad Mastodon rè a’ bhliadhna dhan luchd-cleachdaidh ionadail. Bidh an gleus seo ri fhaighinn eadar an 10mh is 31mh dhen Dùbhlachd gach bliadhna ’s thèid a thairgsinn dhan luchd-cleachdaidh a rinn co-dhiù aon post poblach no sàmhach ’s a chleachd co-dhiù aon taga hais rè a’ bhliadhna. form_challenge: current_password: Tha thu a’ tighinn a-steach gu raon tèarainte imports: @@ -239,6 +241,8 @@ gd: setting_always_send_emails: Cuir brathan puist-d an-còmhnaidh setting_auto_play_gif: Cluich GIFs beòthaichte gu fèin-obrachail setting_boost_modal: Smachd air faicsinneachd nam brosnachaidhean + setting_color_scheme: Modh + setting_contrast: Iomsgaradh setting_default_language: Cànan postaidh setting_default_privacy: Faicsinneachd nam post setting_default_quote_policy: Cò dh’fhaodas luaidh @@ -313,6 +317,7 @@ gd: thumbnail: Dealbhag an fhrithealaiche trendable_by_default: Ceadaich treandaichean gun lèirmheas ro làimh trends: Cuir na treandaichean an comas + wrapstodon: Cuir Wrapstodon an comas interactions: must_be_follower: Bac na brathan nach eil o luchd-leantainn must_be_following: Bac na brathan o dhaoine nach lean thu @@ -373,7 +378,9 @@ gd: jurisdiction: Uachdranas laghail min_age: An aois as lugha user: + date_of_birth_1i: Bliadhna date_of_birth_2i: Mìos + date_of_birth_3i: Latha role: Dreuchd time_zone: Roinn-tìde user_role: From c21884920409cbc691735bc55807507649479275 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 14 Jan 2026 11:08:29 +0100 Subject: [PATCH 006/145] Add collection endpoint (#37468) --- .../activitypub/collections_controller.rb | 2 + app/controllers/collections_controller.rb | 46 ++++++ app/lib/activitypub/tag_manager.rb | 4 +- app/policies/collection_policy.rb | 20 ++- config/routes.rb | 4 +- spec/lib/activitypub/tag_manager_spec.rb | 2 +- spec/policies/collection_policy_spec.rb | 14 +- spec/requests/activitypub/collections_spec.rb | 13 +- spec/requests/collections_spec.rb | 138 ++++++++++++++++++ 9 files changed, 223 insertions(+), 20 deletions(-) create mode 100644 app/controllers/collections_controller.rb create mode 100644 spec/requests/collections_spec.rb diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index c80db3500de..a03f424e0f1 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ActivityPub::CollectionsController < ActivityPub::BaseController + SUPPORTED_COLLECTIONS = %w(featured tags).freeze + vary_by -> { 'Signature' if authorized_fetch_mode? } before_action :require_account_signature!, if: :authorized_fetch_mode? diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb new file mode 100644 index 00000000000..3e2ba714702 --- /dev/null +++ b/app/controllers/collections_controller.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class CollectionsController < ApplicationController + include WebAppControllerConcern + include SignatureAuthentication + include Authorization + include AccountOwnedConcern + + vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' } + + before_action :check_feature_enabled + before_action :require_account_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? } + before_action :set_collection + + skip_around_action :set_locale, if: -> { request.format == :json } + skip_before_action :require_functional!, only: :show, unless: :limited_federation_mode? + + def show + respond_to do |format| + # TODO: format.html + + format.json do + expires_in expiration_duration, public: true if public_fetch_mode? + render_with_cache json: @collection, content_type: 'application/activity+json', serializer: ActivityPub::FeaturedCollectionSerializer, adapter: ActivityPub::Adapter + end + end + end + + private + + def set_collection + @collection = @account.collections.find(params[:id]) + authorize @collection, :show? + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError + not_found + end + + def expiration_duration + recently_updated = @collection.updated_at > 15.minutes.ago + recently_updated ? 30.seconds : 5.minutes + end + + def check_feature_enabled + raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled? + end +end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 9f01b005780..e6714c51abb 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -63,7 +63,7 @@ class ActivityPub::TagManager when :flag target.uri when :featured_collection - ap_account_featured_collection_url(target.account.id, target) + ap_account_collection_url(target.account.id, target) end end @@ -135,7 +135,7 @@ class ActivityPub::TagManager def collection_uri_for(target, ...) raise ArgumentError, 'target must be a local account' unless target.local? - target.numeric_ap_id? ? ap_account_collection_url(target.id, ...) : account_collection_url(target, ...) + target.numeric_ap_id? ? ap_account_actor_collection_url(target.id, ...) : account_actor_collection_url(target, ...) end def inbox_uri_for(target) diff --git a/app/policies/collection_policy.rb b/app/policies/collection_policy.rb index 12adfbcad1a..70a869d16ad 100644 --- a/app/policies/collection_policy.rb +++ b/app/policies/collection_policy.rb @@ -6,7 +6,7 @@ class CollectionPolicy < ApplicationPolicy end def show? - true + current_account.nil? || (!owner_blocking? && !owner_blocking_domain?) end def create? @@ -24,6 +24,22 @@ class CollectionPolicy < ApplicationPolicy private def owner? - current_account == record.account + current_account == owner + end + + def owner_blocking_domain? + return false if current_account.nil? || current_account.domain.nil? + + owner.domain_blocking?(current_account.domain) + end + + def owner_blocking? + return false if current_account.nil? + + current_account.blocked_by?(owner) + end + + def owner + record.account end end diff --git a/config/routes.rb b/config/routes.rb index ff79c758fb3..b3338a725eb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -96,14 +96,14 @@ Rails.application.routes.draw do get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" } concern :account_resources do - resources :featured_collections, only: [:show] + resources :collections, only: [:show], constraints: { id: /\d+/ } resources :followers, only: [:index], controller: :follower_accounts resources :following, only: [:index], controller: :following_accounts scope module: :activitypub do resource :outbox, only: [:show] resource :inbox, only: [:create] - resources :collections, only: [:show] + resources :collections, only: [:show], as: :actor_collections, constraints: { id: Regexp.union(ActivityPub::CollectionsController::SUPPORTED_COLLECTIONS) } resource :followers_synchronization, only: [:show] resources :quote_authorizations, only: [:show] end diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb index bacbb3251cc..55e54ede5e1 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -198,7 +198,7 @@ RSpec.describe ActivityPub::TagManager do it 'returns a string starting with web domain and with the expected path' do expect(subject.uri_for(collection)) - .to eq("#{host_prefix}/ap/users/#{collection.account.id}/featured_collections/#{collection.id}") + .to eq("#{host_prefix}/ap/users/#{collection.account.id}/collections/#{collection.id}") end end diff --git a/spec/policies/collection_policy_spec.rb b/spec/policies/collection_policy_spec.rb index 156e1a76572..ecef6e899d9 100644 --- a/spec/policies/collection_policy_spec.rb +++ b/spec/policies/collection_policy_spec.rb @@ -16,11 +16,23 @@ RSpec.describe CollectionPolicy do end permissions :show? do - it 'permits everyone to show' do + it 'permits when no user is given' do expect(policy).to permit(nil, collection) + end + + it 'permits unblocked users' do expect(policy).to permit(owner, collection) expect(policy).to permit(other_user, collection) end + + it 'denies blocked users' do + domain_blocked_user = Fabricate(:remote_account) + owner.block_domain!(domain_blocked_user.domain) + owner.block!(other_user) + + expect(policy).to_not permit(domain_blocked_user, collection) + expect(policy).to_not permit(other_user, collection) + end end permissions :create? do diff --git a/spec/requests/activitypub/collections_spec.rb b/spec/requests/activitypub/collections_spec.rb index d2761f98ea3..39bd2252e78 100644 --- a/spec/requests/activitypub/collections_spec.rb +++ b/spec/requests/activitypub/collections_spec.rb @@ -14,7 +14,7 @@ RSpec.describe 'ActivityPub Collections' do end describe 'GET #show' do - subject { get account_collection_path(id: id, account_username: account.username), headers: nil, sign_with: remote_account } + subject { get account_actor_collection_path(id: id, account_username: account.username), headers: nil, sign_with: remote_account } context 'when id is "featured"' do let(:id) { 'featured' } @@ -131,16 +131,5 @@ RSpec.describe 'ActivityPub Collections' do end end end - - context 'when id is not "featured"' do - let(:id) { 'hoge' } - - it 'returns http not found' do - subject - - expect(response) - .to have_http_status(404) - end - end end end diff --git a/spec/requests/collections_spec.rb b/spec/requests/collections_spec.rb new file mode 100644 index 00000000000..fece4b62b82 --- /dev/null +++ b/spec/requests/collections_spec.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Collections' do + describe 'GET /@:account_username/collections/:id', feature: :collections do + subject { get account_collection_path(account, collection, format: :json) } + + let(:collection) { Fabricate(:collection) } + let(:account) { collection.account } + + context 'when signed out' do + context 'when account is permanently suspended' do + before do + account.suspend! + account.deletion_request.destroy + end + + it 'returns http gone' do + subject + + expect(response) + .to have_http_status(410) + end + end + + context 'when account is temporarily suspended' do + before { account.suspend! } + + it 'returns http forbidden' do + subject + + expect(response) + .to have_http_status(403) + end + end + + context 'when account is accessible' do + context 'with JSON' do + subject { get ap_account_collection_path(account.id, collection, format: :json) } + + it 'renders ActivityPub FeaturedCollection object successfully', :aggregate_failures do + subject + + expect(response) + .to have_http_status(200) + .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie') + + expect(response.headers).to include( + 'Content-Type' => include('application/activity+json') + ) + expect(response.parsed_body) + .to include({ + 'type' => 'FeaturedCollection', + 'name' => collection.name, + }) + end + end + end + end + + context 'when signed in' do + let(:user) { Fabricate(:user) } + + before do + post user_session_path, params: { user: { email: user.email, password: user.password } } + end + + context 'when account blocks user' do + before { account.block!(user.account) } + + it 'returns http not found' do + subject + + expect(response) + .to have_http_status(404) + end + end + end + + context 'with "HTTP Signature" access signed by a remote account' do + subject do + get account_collection_path(account, collection, format: :json), + headers: nil, + sign_with: remote_account + end + + let(:remote_account) { Fabricate(:account, domain: 'host.example') } + + context 'when account blocks the remote account' do + before { account.block!(remote_account) } + + it 'returns http not found' do + subject + + expect(response) + .to have_http_status(404) + end + end + + context 'when account domain blocks the domain of the remote account' do + before { account.block_domain!(remote_account.domain) } + + it 'returns http not found' do + subject + + expect(response) + .to have_http_status(404) + end + end + + context 'with JSON' do + subject do + get ap_account_collection_path(account.id, collection, format: :json), + headers: nil, + sign_with: remote_account + end + + it 'renders ActivityPub FeaturedCollection object successfully', :aggregate_failures do + subject + + expect(response) + .to have_http_status(200) + .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie') + + expect(response.headers).to include( + 'Content-Type' => include('application/activity+json') + ) + expect(response.parsed_body) + .to include({ + 'type' => 'FeaturedCollection', + 'name' => collection.name, + }) + end + end + end + end +end From 8390f0dbbf5e809ad67a0d41cb2e84e4bb2c4754 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:10:52 +0100 Subject: [PATCH 007/145] Update dependency rqrcode to v3.2.0 (#37431) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e7519ea90bc..d2feffceafc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -720,10 +720,10 @@ GEM rotp (6.3.0) rouge (4.7.0) rpam2 (4.0.2) - rqrcode (3.1.1) + rqrcode (3.2.0) chunky_png (~> 1.0) rqrcode_core (~> 2.0) - rqrcode_core (2.0.1) + rqrcode_core (2.1.0) rspec (3.13.2) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) From d612119b3ec564e0d6c3547d0b2cf07ab08560ea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:17:40 +0100 Subject: [PATCH 008/145] Update dependency thor to v1.5.0 (#37406) 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 d2feffceafc..a54c979764f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -860,7 +860,7 @@ GEM terrapin (1.1.1) climate_control test-prof (1.5.0) - thor (1.4.0) + thor (1.5.0) tilt (2.6.1) timeout (0.6.0) tpm-key_attestation (0.14.1) From 6fdef1191a9e88591dec786e767f800b28df8d02 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:35:40 +0100 Subject: [PATCH 009/145] Update dependency globals to v17 (#37360) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- streaming/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 8a1ecc77374..73eb15b74ee 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-storybook": "^10.0.2", "fake-indexeddb": "^6.0.1", - "globals": "^16.0.0", + "globals": "^17.0.0", "husky": "^9.0.11", "lint-staged": "^16.2.6", "msw": "^2.12.1", diff --git a/streaming/package.json b/streaming/package.json index 0f6651b741a..7684ed7cc85 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -36,7 +36,7 @@ "@types/express": "^5.0.5", "@types/pg": "^8.6.6", "@types/ws": "^8.5.9", - "globals": "^16.0.0", + "globals": "^17.0.0", "pino-pretty": "^13.0.0", "typescript": "~5.9.0", "typescript-eslint": "^8.28.0" diff --git a/yarn.lock b/yarn.lock index 424b0d623fd..9ae3614dee7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3039,7 +3039,7 @@ __metadata: fake-indexeddb: "npm:^6.0.1" fast-glob: "npm:^3.3.3" fuzzysort: "npm:^3.0.0" - globals: "npm:^16.0.0" + globals: "npm:^17.0.0" history: "npm:^4.10.1" hoist-non-react-statics: "npm:^3.3.2" http-link-header: "npm:^1.1.1" @@ -3131,7 +3131,7 @@ __metadata: cors: "npm:^2.8.5" dotenv: "npm:^16.0.3" express: "npm:^5.1.0" - globals: "npm:^16.0.0" + globals: "npm:^17.0.0" ioredis: "npm:^5.3.2" jsdom: "npm:^27.0.0" pg: "npm:^8.5.0" @@ -8275,10 +8275,10 @@ __metadata: languageName: node linkType: hard -"globals@npm:^16.0.0": - version: 16.5.0 - resolution: "globals@npm:16.5.0" - checksum: 10c0/615241dae7851c8012f5aa0223005b1ed6607713d6813de0741768bd4ddc39353117648f1a7086b4b0fa45eae733f1c0a0fe369aa4e543bb63f8de8990178ea9 +"globals@npm:^17.0.0": + version: 17.0.0 + resolution: "globals@npm:17.0.0" + checksum: 10c0/e3c169fdcb0fc6755707b697afb367bea483eb29992cfc0de1637382eb893146e17f8f96db6d7453e3696b478a7863ae2000e6c71cd2f4061410285106d3847a languageName: node linkType: hard From 5d03d5d15c1bf91845241dbe77c8b71d4c1eb2c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:37:05 +0000 Subject: [PATCH 010/145] Update dependency libvips to v8.18.0 (#37282) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 865d14402cd..b9dcbe59fd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -183,7 +183,7 @@ FROM build AS libvips # libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"] # renovate: datasource=github-releases depName=libvips packageName=libvips/libvips -ARG VIPS_VERSION=8.17.3 +ARG VIPS_VERSION=8.18.0 # libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"] ARG VIPS_URL=https://github.com/libvips/libvips/releases/download From 4bdc9d5e1e4af9299867c4c3d700371349181a94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:37:43 +0000 Subject: [PATCH 011/145] Update dependency vite to v7.3.1 (#37248) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 271 +----------------------------------------------------- 1 file changed, 5 insertions(+), 266 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9ae3614dee7..4a8e1fa07f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2095,13 +2095,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/aix-ppc64@npm:0.25.5" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/aix-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/aix-ppc64@npm:0.27.2" @@ -2109,13 +2102,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/android-arm64@npm:0.25.5" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm64@npm:0.27.2" @@ -2123,13 +2109,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/android-arm@npm:0.25.5" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm@npm:0.27.2" @@ -2137,13 +2116,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/android-x64@npm:0.25.5" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-x64@npm:0.27.2" @@ -2151,13 +2123,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/darwin-arm64@npm:0.25.5" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-arm64@npm:0.27.2" @@ -2165,13 +2130,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/darwin-x64@npm:0.25.5" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-x64@npm:0.27.2" @@ -2179,13 +2137,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/freebsd-arm64@npm:0.25.5" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-arm64@npm:0.27.2" @@ -2193,13 +2144,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/freebsd-x64@npm:0.25.5" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-x64@npm:0.27.2" @@ -2207,13 +2151,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-arm64@npm:0.25.5" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm64@npm:0.27.2" @@ -2221,13 +2158,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-arm@npm:0.25.5" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm@npm:0.27.2" @@ -2235,13 +2165,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-ia32@npm:0.25.5" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ia32@npm:0.27.2" @@ -2249,13 +2172,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-loong64@npm:0.25.5" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-loong64@npm:0.27.2" @@ -2263,13 +2179,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-mips64el@npm:0.25.5" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-mips64el@npm:0.27.2" @@ -2277,13 +2186,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-ppc64@npm:0.25.5" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ppc64@npm:0.27.2" @@ -2291,13 +2193,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-riscv64@npm:0.25.5" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-riscv64@npm:0.27.2" @@ -2305,13 +2200,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-s390x@npm:0.25.5" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-s390x@npm:0.27.2" @@ -2319,13 +2207,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/linux-x64@npm:0.25.5" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-x64@npm:0.27.2" @@ -2333,13 +2214,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/netbsd-arm64@npm:0.25.5" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/netbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-arm64@npm:0.27.2" @@ -2347,13 +2221,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/netbsd-x64@npm:0.25.5" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-x64@npm:0.27.2" @@ -2361,13 +2228,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/openbsd-arm64@npm:0.25.5" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-arm64@npm:0.27.2" @@ -2375,13 +2235,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/openbsd-x64@npm:0.25.5" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-x64@npm:0.27.2" @@ -2396,13 +2249,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/sunos-x64@npm:0.25.5" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/sunos-x64@npm:0.27.2" @@ -2410,13 +2256,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/win32-arm64@npm:0.25.5" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-arm64@npm:0.27.2" @@ -2424,13 +2263,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/win32-ia32@npm:0.25.5" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-ia32@npm:0.27.2" @@ -2438,13 +2270,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.5": - version: 0.25.5 - resolution: "@esbuild/win32-x64@npm:0.25.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-x64@npm:0.27.2" @@ -7118,7 +6943,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0": +"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0, esbuild@npm:^0.27.0": version: 0.27.2 resolution: "esbuild@npm:0.27.2" dependencies: @@ -7207,92 +7032,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.25.0": - version: 0.25.5 - resolution: "esbuild@npm:0.25.5" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.5" - "@esbuild/android-arm": "npm:0.25.5" - "@esbuild/android-arm64": "npm:0.25.5" - "@esbuild/android-x64": "npm:0.25.5" - "@esbuild/darwin-arm64": "npm:0.25.5" - "@esbuild/darwin-x64": "npm:0.25.5" - "@esbuild/freebsd-arm64": "npm:0.25.5" - "@esbuild/freebsd-x64": "npm:0.25.5" - "@esbuild/linux-arm": "npm:0.25.5" - "@esbuild/linux-arm64": "npm:0.25.5" - "@esbuild/linux-ia32": "npm:0.25.5" - "@esbuild/linux-loong64": "npm:0.25.5" - "@esbuild/linux-mips64el": "npm:0.25.5" - "@esbuild/linux-ppc64": "npm:0.25.5" - "@esbuild/linux-riscv64": "npm:0.25.5" - "@esbuild/linux-s390x": "npm:0.25.5" - "@esbuild/linux-x64": "npm:0.25.5" - "@esbuild/netbsd-arm64": "npm:0.25.5" - "@esbuild/netbsd-x64": "npm:0.25.5" - "@esbuild/openbsd-arm64": "npm:0.25.5" - "@esbuild/openbsd-x64": "npm:0.25.5" - "@esbuild/sunos-x64": "npm:0.25.5" - "@esbuild/win32-arm64": "npm:0.25.5" - "@esbuild/win32-ia32": "npm:0.25.5" - "@esbuild/win32-x64": "npm:0.25.5" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/aba8cbc11927fa77562722ed5e95541ce2853f67ad7bdc40382b558abc2e0ec57d92ffb820f082ba2047b4ef9f3bc3da068cdebe30dfd3850cfa3827a78d604e - languageName: node - linkType: hard - "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -14449,10 +14188,10 @@ __metadata: linkType: hard "vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.1.1": - version: 7.2.7 - resolution: "vite@npm:7.2.7" + version: 7.3.1 + resolution: "vite@npm:7.3.1" dependencies: - esbuild: "npm:^0.25.0" + esbuild: "npm:^0.27.0" fdir: "npm:^6.5.0" fsevents: "npm:~2.3.3" picomatch: "npm:^4.0.3" @@ -14499,7 +14238,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/0c502d9eb898d9c05061dbd8fd199f280b524bbb4c12ab5f88c7b12779947386684a269e4dd0aa424aa35bcd857f1aa44aadb9ea764702a5043af433052455b5 + checksum: 10c0/5c7548f5f43a23533e53324304db4ad85f1896b1bfd3ee32ae9b866bac2933782c77b350eb2b52a02c625c8ad1ddd4c000df077419410650c982cd97fde8d014 languageName: node linkType: hard From 10de65e41c3b01bd938f97fdb60fc96c26673966 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 14 Jan 2026 11:51:23 +0100 Subject: [PATCH 012/145] Fix `FeedManager#filter_from_home` error when handling a reblog of a deleted status (#37486) --- app/lib/feed_manager.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 9c5c306e966..ab5ee106c7e 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -450,6 +450,7 @@ class FeedManager return :filter if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?) return :skip_home if timeline_type != :list && crutches[:exclusive_list_users][status.account_id].present? return :filter if crutches[:languages][status.account_id].present? && status.language.present? && !crutches[:languages][status.account_id].include?(status.language) + return :filter if status.reblog? && status.reblog.blank? check_for_blocks = crutches[:active_mentions][status.id] || [] check_for_blocks.push(status.account_id) From 8eff59b106e1a760b92d083ba887069f2ba2866d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:13:53 +0100 Subject: [PATCH 013/145] Update dependency httplog to '~> 1.8.0' (#37484) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 3cc9580fea2..c223ca0a52c 100644 --- a/Gemfile +++ b/Gemfile @@ -55,7 +55,7 @@ gem 'hiredis-client' gem 'htmlentities', '~> 4.3' gem 'http', '~> 5.3.0' gem 'http_accept_language', '~> 2.1' -gem 'httplog', '~> 1.7.0', require: false +gem 'httplog', '~> 1.8.0', require: false gem 'i18n' gem 'idn-ruby', require: 'idn' gem 'inline_svg' diff --git a/Gemfile.lock b/Gemfile.lock index a54c979764f..ad61b51752f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -320,7 +320,8 @@ GEM http_accept_language (2.1.1) httpclient (2.9.0) mutex_m - httplog (1.7.3) + httplog (1.8.0) + benchmark rack (>= 2.0) rainbow (>= 2.0.0) i18n (1.14.8) @@ -985,7 +986,7 @@ DEPENDENCIES htmlentities (~> 4.3) http (~> 5.3.0) http_accept_language (~> 2.1) - httplog (~> 1.7.0) + httplog (~> 1.8.0) i18n i18n-tasks (~> 1.0) idn-ruby From dd52e61c24f99a85b1200c2b793af9f10b0fc866 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:13:56 +0100 Subject: [PATCH 014/145] Update opentelemetry-ruby (non-major) (#37480) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 12 ++++++------ Gemfile.lock | 27 ++++++++++++++------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Gemfile b/Gemfile index c223ca0a52c..f5da754b1a7 100644 --- a/Gemfile +++ b/Gemfile @@ -109,12 +109,12 @@ group :opentelemetry do gem 'opentelemetry-instrumentation-active_job', '~> 0.10.0', require: false gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.24.0', require: false gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.24.0', require: false - gem 'opentelemetry-instrumentation-excon', '~> 0.26.0', require: false - gem 'opentelemetry-instrumentation-faraday', '~> 0.30.0', require: false - gem 'opentelemetry-instrumentation-http', '~> 0.27.0', require: false - gem 'opentelemetry-instrumentation-http_client', '~> 0.26.0', require: false - gem 'opentelemetry-instrumentation-net_http', '~> 0.26.0', require: false - gem 'opentelemetry-instrumentation-pg', '~> 0.34.0', require: false + gem 'opentelemetry-instrumentation-excon', '~> 0.27.0', require: false + gem 'opentelemetry-instrumentation-faraday', '~> 0.31.0', require: false + gem 'opentelemetry-instrumentation-http', '~> 0.28.0', require: false + gem 'opentelemetry-instrumentation-http_client', '~> 0.27.0', require: false + gem 'opentelemetry-instrumentation-net_http', '~> 0.27.0', require: false + gem 'opentelemetry-instrumentation-pg', '~> 0.35.0', require: false gem 'opentelemetry-instrumentation-rack', '~> 0.29.0', require: false gem 'opentelemetry-instrumentation-rails', '~> 0.39.0', require: false gem 'opentelemetry-instrumentation-redis', '~> 0.28.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index ad61b51752f..f920acecbf8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -521,7 +521,8 @@ GEM opentelemetry-semantic_conventions opentelemetry-helpers-sql (0.3.0) opentelemetry-api (~> 1.7) - opentelemetry-helpers-sql-processor (0.3.1) + opentelemetry-helpers-sql-processor (0.4.0) + opentelemetry-api (~> 1.0) opentelemetry-common (~> 0.21) opentelemetry-instrumentation-action_mailer (0.6.1) opentelemetry-instrumentation-active_support (~> 0.10) @@ -545,17 +546,17 @@ GEM opentelemetry-registry (~> 0.1) opentelemetry-instrumentation-concurrent_ruby (0.24.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-excon (0.26.1) + opentelemetry-instrumentation-excon (0.27.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-faraday (0.30.1) + opentelemetry-instrumentation-faraday (0.31.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-http (0.27.1) + opentelemetry-instrumentation-http (0.28.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-http_client (0.26.1) + opentelemetry-instrumentation-http_client (0.27.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-net_http (0.26.1) + opentelemetry-instrumentation-net_http (0.27.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-pg (0.34.1) + opentelemetry-instrumentation-pg (0.35.0) opentelemetry-helpers-sql opentelemetry-helpers-sql-processor opentelemetry-instrumentation-base (~> 0.25) @@ -1022,12 +1023,12 @@ DEPENDENCIES opentelemetry-instrumentation-active_job (~> 0.10.0) opentelemetry-instrumentation-active_model_serializers (~> 0.24.0) opentelemetry-instrumentation-concurrent_ruby (~> 0.24.0) - opentelemetry-instrumentation-excon (~> 0.26.0) - opentelemetry-instrumentation-faraday (~> 0.30.0) - opentelemetry-instrumentation-http (~> 0.27.0) - opentelemetry-instrumentation-http_client (~> 0.26.0) - opentelemetry-instrumentation-net_http (~> 0.26.0) - opentelemetry-instrumentation-pg (~> 0.34.0) + opentelemetry-instrumentation-excon (~> 0.27.0) + opentelemetry-instrumentation-faraday (~> 0.31.0) + opentelemetry-instrumentation-http (~> 0.28.0) + opentelemetry-instrumentation-http_client (~> 0.27.0) + opentelemetry-instrumentation-net_http (~> 0.27.0) + opentelemetry-instrumentation-pg (~> 0.35.0) opentelemetry-instrumentation-rack (~> 0.29.0) opentelemetry-instrumentation-rails (~> 0.39.0) opentelemetry-instrumentation-redis (~> 0.28.0) From 1482a7bf201103b10fe9c38b0ee5e82a69eba27c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:14:00 +0100 Subject: [PATCH 015/145] Update dependency postcss-preset-env to v11 (#37485) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 1463 ++++++++++++++++++++++++++------------------------ 2 files changed, 765 insertions(+), 700 deletions(-) diff --git a/package.json b/package.json index 73eb15b74ee..701719d7cff 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "lodash": "^4.17.21", "marky": "^1.2.5", "path-complete-extname": "^1.0.0", - "postcss-preset-env": "^10.1.5", + "postcss-preset-env": "^11.0.0", "prop-types": "^15.8.1", "punycode": "^2.3.0", "react": "^18.2.0", diff --git a/yarn.lock b/yarn.lock index 4a8e1fa07f6..c93adccaa13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1248,13 +1248,13 @@ __metadata: languageName: node linkType: hard -"@csstools/cascade-layer-name-parser@npm:^2.0.5": - version: 2.0.5 - resolution: "@csstools/cascade-layer-name-parser@npm:2.0.5" +"@csstools/cascade-layer-name-parser@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/cascade-layer-name-parser@npm:3.0.0" peerDependencies: - "@csstools/css-parser-algorithms": ^3.0.5 - "@csstools/css-tokenizer": ^3.0.4 - checksum: 10c0/b6c73d5c8132f922edc88b9df5272c93c9753945f1e1077b80d03b314076ffe03c2cc9bf6cbc85501ee7c7f27e477263df96997c9125fd2fd0cfe82fe2d7c141 + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/657b325261dfa567e26b56a1a6a00fcfc877b9e95ce8ae3595ec95c89323fc2123dad7a4ca79bede8dec475ccc41d9d528470b0be01c4f22104f6962f2bfb40c languageName: node linkType: hard @@ -1265,6 +1265,13 @@ __metadata: languageName: node linkType: hard +"@csstools/color-helpers@npm:^6.0.0": + version: 6.0.0 + resolution: "@csstools/color-helpers@npm:6.0.0" + checksum: 10c0/784447fa6ba2f5fec30f8676c48f9f66bff30a7a16582e3c3b3e9aa2574df1ac4e5f8e7455dfa6d991fd84ceceb22ff4016e2ea3a8116e76772b3972dbab7bec + languageName: node + linkType: hard + "@csstools/css-calc@npm:^2.1.4": version: 2.1.4 resolution: "@csstools/css-calc@npm:2.1.4" @@ -1275,6 +1282,16 @@ __metadata: languageName: node linkType: hard +"@csstools/css-calc@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/css-calc@npm:3.0.0" + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/2f062db206dcdcb561a802d791aaf8b410f0e4d91ded89eb4075f75f1eafe8f5392c431a8d602d6ae660fca6299e02fcc555efdb235f3403ffc56ac6f14a1c2b + languageName: node + linkType: hard + "@csstools/css-color-parser@npm:^3.1.0": version: 3.1.0 resolution: "@csstools/css-color-parser@npm:3.1.0" @@ -1288,6 +1305,19 @@ __metadata: languageName: node linkType: hard +"@csstools/css-color-parser@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/css-color-parser@npm:4.0.0" + dependencies: + "@csstools/color-helpers": "npm:^6.0.0" + "@csstools/css-calc": "npm:^3.0.0" + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/bbfa3855bd53d5f31e2e9d40d8bc7b1143c7efd3dd6fa43c0ef2222a66bdc94f7ffedc8932a4eb4f10c5cbc38d1ed110fda99240037e0a2dae61c6b25527b0a2 + languageName: node + linkType: hard + "@csstools/css-parser-algorithms@npm:^3.0.5": version: 3.0.5 resolution: "@csstools/css-parser-algorithms@npm:3.0.5" @@ -1297,6 +1327,15 @@ __metadata: languageName: node linkType: hard +"@csstools/css-parser-algorithms@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/css-parser-algorithms@npm:4.0.0" + peerDependencies: + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/94558c2428d6ef0ddef542e86e0a8376aa1263a12a59770abb13ba50d7b83086822c75433f32aa2e7fef00555e1cc88292f9ca5bce79aed232bb3fed73b1528d + languageName: node + linkType: hard + "@csstools/css-syntax-patches-for-csstree@npm:1.0.14": version: 1.0.14 resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.14" @@ -1320,6 +1359,13 @@ __metadata: languageName: node linkType: hard +"@csstools/css-tokenizer@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/css-tokenizer@npm:4.0.0" + checksum: 10c0/669cf3d0f9c8e1ffdf8c9955ad8beba0c8cfe03197fe29a4fcbd9ee6f7a18856cfa42c62670021a75183d9ab37f5d14a866e6a9df753a6c07f59e36797a9ea9f + languageName: node + linkType: hard + "@csstools/media-query-list-parser@npm:^4.0.3": version: 4.0.3 resolution: "@csstools/media-query-list-parser@npm:4.0.3" @@ -1330,513 +1376,523 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-alpha-function@npm:^1.0.1": - version: 1.0.1 - resolution: "@csstools/postcss-alpha-function@npm:1.0.1" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" +"@csstools/media-query-list-parser@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/media-query-list-parser@npm:5.0.0" peerDependencies: - postcss: ^8.4 - checksum: 10c0/35ca209e572534ade21ac5c18aad702aa492eb39e2d0e475f441371063418fe9650554e6a59b1318d3a615da83ef54d9a588faa27063ecc0a568ef7290a6b488 + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/dbc22654769eca02c182f3a57be02cd5b8d0b958adc8397e66770b64b0e8fcd32faa93a3f6a99e1457bde11862485de3cd83a31dac7b03925d32f9891b31ccfd languageName: node linkType: hard -"@csstools/postcss-cascade-layers@npm:^5.0.2": - version: 5.0.2 - resolution: "@csstools/postcss-cascade-layers@npm:5.0.2" - dependencies: - "@csstools/selector-specificity": "npm:^5.0.0" - postcss-selector-parser: "npm:^7.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/dd8e29cfd3a93932fa35e3a59aa62fd2e720772d450f40f38f65ce1e736e2fe839635eb6f033abcc8ee8bc2856161a297f4458b352b26d2216856feb03176612 - languageName: node - linkType: hard - -"@csstools/postcss-color-function-display-p3-linear@npm:^1.0.1": - version: 1.0.1 - resolution: "@csstools/postcss-color-function-display-p3-linear@npm:1.0.1" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/d02d45410c9257f5620c766f861f8fa3762b74ef01fdba8060b33a4c98f929e2219cd476b25bd4181ac186158a4d99a0da555c0b6ba45a7ac4a3a5885baad1f5 - languageName: node - linkType: hard - -"@csstools/postcss-color-function@npm:^4.0.12": - version: 4.0.12 - resolution: "@csstools/postcss-color-function@npm:4.0.12" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/a355b04d90f89c8e37a4a23543151558060acc68fb2e7d1c3549bebeeae2b147eec26af1fbc6ee690f0ba4830263f2d181f5331d16d3483b5542be46996fa755 - languageName: node - linkType: hard - -"@csstools/postcss-color-mix-function@npm:^3.0.12": - version: 3.0.12 - resolution: "@csstools/postcss-color-mix-function@npm:3.0.12" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/3e98a5118852083d1f87a3f842f78088192b1f9f08fdf1f3b3ef1e8969e18fdadc1e3bcac3d113a07c8917a7e8fa65fdec55a31df9a1b726c8d7ae89db86e8e5 - languageName: node - linkType: hard - -"@csstools/postcss-color-mix-variadic-function-arguments@npm:^1.0.2": - version: 1.0.2 - resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:1.0.2" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/34073f0f0d33e4958f90763e692955a8e8c678b74284234497c4aa0d2143756e1b3616e0c09832caad498870e227ca0a681316afe3a71224fc40ade0ead1bdd9 - languageName: node - linkType: hard - -"@csstools/postcss-content-alt-text@npm:^2.0.8": - version: 2.0.8 - resolution: "@csstools/postcss-content-alt-text@npm:2.0.8" - dependencies: - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/4c330cc2a1e434688a62613ecceb1434cd725ce024c1ad8d4a4c76b9839d1f3ea8566a8c6494921e2b46ec7feef6af8ed6548c216dcb8f0feab4b1d52c96228e - languageName: node - linkType: hard - -"@csstools/postcss-contrast-color-function@npm:^2.0.12": - version: 2.0.12 - resolution: "@csstools/postcss-contrast-color-function@npm:2.0.12" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/b783ce948cdf1513ee238e9115b42881a8d3e5d13c16038601b1c470d661cfaeeece4eea29904fb9fcae878bad86f766810fa798a703ab9ad4b0cf276b173f8f - languageName: node - linkType: hard - -"@csstools/postcss-exponential-functions@npm:^2.0.9": - version: 2.0.9 - resolution: "@csstools/postcss-exponential-functions@npm:2.0.9" - dependencies: - "@csstools/css-calc": "npm:^2.1.4" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/78ea627a87fb23e12616c4e54150363b0e8793064634983dbe0368a0aca1ff73206c2d1f29845773daaf42787e7d1f180ce1b57c43e2b0d10da450101f9f34b6 - languageName: node - linkType: hard - -"@csstools/postcss-font-format-keywords@npm:^4.0.0": - version: 4.0.0 - resolution: "@csstools/postcss-font-format-keywords@npm:4.0.0" - dependencies: - "@csstools/utilities": "npm:^2.0.0" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/eb794fb95fefcac75e606d185255e601636af177866a317b0c6b6c375055e7240be53918229fd8d4bba00df01bedd2256bdac2b0ad4a4c2ec64f9d27cd6ff639 - languageName: node - linkType: hard - -"@csstools/postcss-gamut-mapping@npm:^2.0.11": - version: 2.0.11 - resolution: "@csstools/postcss-gamut-mapping@npm:2.0.11" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/490b8ccf10e30879a4415afbdd3646e1cdac3671586b7916855cf47a536f3be75eed014396056bde6528e0cb76d904e79bad78afc0b499e837264cf22519d145 - languageName: node - linkType: hard - -"@csstools/postcss-gradients-interpolation-method@npm:^5.0.12": - version: 5.0.12 - resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.12" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/70b3d6c7050ce882ed2281e71eb4493531ae8d55d21899920eeeb6c205d90aaf430419a66235484ccce3a1a1891367dfc0ef772f3866ae3a9d8ec5ddd0cfe894 - languageName: node - linkType: hard - -"@csstools/postcss-hwb-function@npm:^4.0.12": - version: 4.0.12 - resolution: "@csstools/postcss-hwb-function@npm:4.0.12" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/d0dac34da9d7ac654060b6b27690a419718e990b21ff3e63266ea59934a865bc6aeae8eb8e1ca3e227a8b2a208657e3ab70ccdf0437f1f09d21ab848bbffcaa2 - languageName: node - linkType: hard - -"@csstools/postcss-ic-unit@npm:^4.0.4": - version: 4.0.4 - resolution: "@csstools/postcss-ic-unit@npm:4.0.4" - dependencies: - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/20168e70ecb4abf7a69e407d653b6c7c9c82f2c7b1da0920e1d035f62b5ef8552cc7f1b62e0dca318df13c348e79fba862e1a4bb0e9432119a82b10aeb511752 - languageName: node - linkType: hard - -"@csstools/postcss-initial@npm:^2.0.1": - version: 2.0.1 - resolution: "@csstools/postcss-initial@npm:2.0.1" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/dbff7084ef4f1c4647efe2b147001daf172003c15b5e22689f0540d03c8d362f2a332cd9cf136e6c8dcda7564ee30492a4267ea188f72cb9c1000fb9bcfbfef8 - languageName: node - linkType: hard - -"@csstools/postcss-is-pseudo-class@npm:^5.0.3": - version: 5.0.3 - resolution: "@csstools/postcss-is-pseudo-class@npm:5.0.3" - dependencies: - "@csstools/selector-specificity": "npm:^5.0.0" - postcss-selector-parser: "npm:^7.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/7980f1cabf32850bac72552e4e9de47412359e36e259a92b9b9af25dae4cce42bbcc5fdca8f384a589565bf383ecb23dec3af9f084d8df18b82552318b2841b6 - languageName: node - linkType: hard - -"@csstools/postcss-light-dark-function@npm:^2.0.11": - version: 2.0.11 - resolution: "@csstools/postcss-light-dark-function@npm:2.0.11" - dependencies: - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/0175be41bb0044a48bc98d5c55cce41ed6b9ada88253c5f20d0ca17287cba4b429742b458ac5744675b9a286109e13ac51d64e226ab16040d7b051ba64c0c77b - languageName: node - linkType: hard - -"@csstools/postcss-logical-float-and-clear@npm:^3.0.0": - version: 3.0.0 - resolution: "@csstools/postcss-logical-float-and-clear@npm:3.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/71a20e8c37877bf68ae615d7bb93fc11b4f8da8be8b1dc1a6e0fc69e27f189712ed71436b8ed51fa69fdb98b8e6718df2b5f42f246c4d39badaf0e43020fcfd4 - languageName: node - linkType: hard - -"@csstools/postcss-logical-overflow@npm:^2.0.0": +"@csstools/postcss-alpha-function@npm:^2.0.0": version: 2.0.0 - resolution: "@csstools/postcss-logical-overflow@npm:2.0.0" + resolution: "@csstools/postcss-alpha-function@npm:2.0.0" + dependencies: + "@csstools/css-color-parser": "npm:^4.0.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/0e103343d3ff8b34eef01b02355c5e010d272fd12d149a242026bb13ab1577b7f3a11fd4514be9342d96f73d61dac1f093a9bd36ece591753ed09a84eb7fca0a + checksum: 10c0/b267a4d48c132a2e0a9160a17811bf6bda09e4ff4b393d6d7643cdd8c9335a2db37d956abe8960545139cf503ed5c1c57e433456a19d7361a0f206b96291b7f7 languageName: node linkType: hard -"@csstools/postcss-logical-overscroll-behavior@npm:^2.0.0": +"@csstools/postcss-cascade-layers@npm:^6.0.0": + version: 6.0.0 + resolution: "@csstools/postcss-cascade-layers@npm:6.0.0" + dependencies: + "@csstools/selector-specificity": "npm:^6.0.0" + postcss-selector-parser: "npm:^7.1.1" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/f9cf3fa52d0f2615a69ad52067cc2229573151525792272eb82a28f0feca64f9ca54e8459c0ae5c254807507e4630aa9866690a4b22e57250c353ee28db86a50 + languageName: node + linkType: hard + +"@csstools/postcss-color-function-display-p3-linear@npm:^2.0.0": version: 2.0.0 - resolution: "@csstools/postcss-logical-overscroll-behavior@npm:2.0.0" + resolution: "@csstools/postcss-color-function-display-p3-linear@npm:2.0.0" + dependencies: + "@csstools/css-color-parser": "npm:^4.0.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/1649601bb26f04d760fb5ebc42cdf414fa2a380b8ec22fe1c117f664c286665a786bd7bbda01b7e7567eaf3cc018a4f36a5c9805f6751cc497da223e0ffe9524 + checksum: 10c0/c2d007dfa7500b6b54bca3a43f92f272cbdd8acaa9d4924eafa320c448f1cee77e7c00a229d5a6ffcc5211664b1dcfd7d54bb6622f2e7956e21ac51ea883165c languageName: node linkType: hard -"@csstools/postcss-logical-resize@npm:^3.0.0": +"@csstools/postcss-color-function@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-color-function@npm:5.0.0" + dependencies: + "@csstools/css-color-parser": "npm:^4.0.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/b01b0e86df5cca222b9859601a4da18ee7a3bade52d6c7fada739cc6c6a4741173f0dccce05f4b307776bc8c0e156923930ce869faae9231e59aa2abab522581 + languageName: node + linkType: hard + +"@csstools/postcss-color-mix-function@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-color-mix-function@npm:4.0.0" + dependencies: + "@csstools/css-color-parser": "npm:^4.0.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/60fa7fbc5e97dc295fd01a90e866d3923bfe47a6e2e3317f01ec8ab0aca687901ef569cdda867780b3aa37a02efa44531bfa0d0d602b90803333baa4cfbf6063 + languageName: node + linkType: hard + +"@csstools/postcss-color-mix-variadic-function-arguments@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:2.0.0" + dependencies: + "@csstools/css-color-parser": "npm:^4.0.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/1206543ec6472f9dd7b67d3311128b66e3be2c6f1bdf9da1b6e4b4f9d69e9388e2896128bb3d1834d825ae4de9455524d7749501a60be740c52c0f7b67287263 + languageName: node + linkType: hard + +"@csstools/postcss-content-alt-text@npm:^3.0.0": version: 3.0.0 - resolution: "@csstools/postcss-logical-resize@npm:3.0.0" + resolution: "@csstools/postcss-content-alt-text@npm:3.0.0" dependencies: + "@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/88f62ade9fa8af8b3292d9437e4df2002d79c139fc63e3030a72276a56cc5a13904fb18d20f07b09de45904da1a37eee4b448cdad07487fc28f96a0e3209bb9d + languageName: node + linkType: hard + +"@csstools/postcss-contrast-color-function@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-contrast-color-function@npm:3.0.0" + dependencies: + "@csstools/css-color-parser": "npm:^4.0.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/7dec6e3bac9d45ae22b5dfcaec9ce8894a1ad80d1e62eb58634eebaa4a02a4f43f5b795a65224442b90b2285c0a0e1d7e80d9528fc1480348ab9a03bf9be2fe0 + languageName: node + linkType: hard + +"@csstools/postcss-exponential-functions@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-exponential-functions@npm:3.0.0" + dependencies: + "@csstools/css-calc": "npm:^3.0.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/911101ec98ac89f56cb7ea52ab2d836646b48e13163d46e1a1f70b680dcc903d314ddf80eeeecfa7b561a99fad2f4b6ce2b0702fbcaa6d379b2c047bde20d398 + languageName: node + linkType: hard + +"@csstools/postcss-font-format-keywords@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-font-format-keywords@npm:5.0.0" + dependencies: + "@csstools/utilities": "npm:^3.0.0" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/4f12efcaf5468ff359bb3f32f0f66034b9acc9b3ac21fcd2f30a1c8998fc653ebac0091f35c8b7e8dbfe6ccf595aee67f9b06a67adf45a8844e49a82d98b4386 + checksum: 10c0/ae776eb164a4501549924a0b153c47cac31b4d288867c71775ff34941653bfe38d3d5a27888ab76fbef0289163e1687daae74e0b317547b7bd281e63b75afa98 languageName: node linkType: hard -"@csstools/postcss-logical-viewport-units@npm:^3.0.4": - version: 3.0.4 - resolution: "@csstools/postcss-logical-viewport-units@npm:3.0.4" +"@csstools/postcss-gamut-mapping@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-gamut-mapping@npm:3.0.0" dependencies: - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/utilities": "npm:^2.0.0" + "@csstools/css-color-parser": "npm:^4.0.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/f0b5ba38acde3bf0ca880c6e0a883950c99fa9919b0e6290c894d5716569663590f26aa1170fd9483ce14544e46afac006ab3b02781410d5e7c8dd1467c674ce + checksum: 10c0/da138bee13c3af9e25962a425c70e242581722881fa52faa090dbb62d22adeced112d3589304651c759bae5fc0060eefb7eb62114798e96d3925bb01febc5a99 languageName: node linkType: hard -"@csstools/postcss-media-minmax@npm:^2.0.9": - version: 2.0.9 - resolution: "@csstools/postcss-media-minmax@npm:2.0.9" +"@csstools/postcss-gradients-interpolation-method@npm:^6.0.0": + version: 6.0.0 + resolution: "@csstools/postcss-gradients-interpolation-method@npm:6.0.0" dependencies: - "@csstools/css-calc": "npm:^2.1.4" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/media-query-list-parser": "npm:^4.0.3" + "@csstools/css-color-parser": "npm:^4.0.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/d82622ee9de6eacba1abbf31718cd58759d158ed8a575f36f08e982d07a7d83e51fb184178b96c6f7b76cb333bb33cac04d06a750b6b9c5c43ae1c56232880f9 + checksum: 10c0/33da282f65480d1a4e8a2ce336bf5e0948d47e9fdab0847d3c9e839ae640cb4af96defdcf4cb3d59e43019dec83a5e48fcd0fb96be927c43c7369e8be316fc79 languageName: node linkType: hard -"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^3.0.5": - version: 3.0.5 - resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:3.0.5" +"@csstools/postcss-hwb-function@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-hwb-function@npm:5.0.0" dependencies: - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/media-query-list-parser": "npm:^4.0.3" + "@csstools/css-color-parser": "npm:^4.0.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/a47abdaa7f4b26596bd9d6bb77aed872a232fc12bd144d2c062d9da626e8dfd8336e2fff67617dba61a1666c2b8027145b390d70d5cd4d4f608604e077cfb04e + checksum: 10c0/7f3e6d46531334621cc2c525df5c76cf916a53d0d9c82008b97b4b6b42f1b07b5729b4c3a8e5ba7b77c51ca44bbd216f83549e978f1aaf75e060335f754aacb2 languageName: node linkType: hard -"@csstools/postcss-nested-calc@npm:^4.0.0": +"@csstools/postcss-ic-unit@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-ic-unit@npm:5.0.0" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" + "@csstools/utilities": "npm:^3.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/04148390bcdd0722af7bab6e05dbc573d8d1b5a216ca1f1021ea0cec955a8c3489e4788b3649f3a7be4a9267e0b3a6012f5e9d80d6f3ac3f1f52e585e5ce0c6e + languageName: node + linkType: hard + +"@csstools/postcss-initial@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-initial@npm:3.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/4c59994c1ff6443f69ba54d2177adf3441756f067876500f281bd3021da1d9a18ae36cf7264a7ef7ca720508cac936f1120dc872f57fb3f6f28ac523c111a890 + languageName: node + linkType: hard + +"@csstools/postcss-is-pseudo-class@npm:^6.0.0": + version: 6.0.0 + resolution: "@csstools/postcss-is-pseudo-class@npm:6.0.0" + dependencies: + "@csstools/selector-specificity": "npm:^6.0.0" + postcss-selector-parser: "npm:^7.1.1" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/55dfb215843258167426eecf7cb8dd2361c2502722469ffc4f3aa7bad6987a08c5ff819ef6bca427c59ee9ad7b3942b89b17bde1d0288ece03f8760c56df7542 + languageName: node + linkType: hard + +"@csstools/postcss-light-dark-function@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-light-dark-function@npm:3.0.0" + dependencies: + "@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/769324f8acda2ce759cad959f43d49ccbc578ccb326362260019a7f7a126408c925d49f437c85d28fb2696cfe1405740a271c250184d9f34ea5603a0b8220936 + languageName: node + linkType: hard + +"@csstools/postcss-logical-float-and-clear@npm:^4.0.0": version: 4.0.0 - resolution: "@csstools/postcss-nested-calc@npm:4.0.0" - dependencies: - "@csstools/utilities": "npm:^2.0.0" - postcss-value-parser: "npm:^4.2.0" + resolution: "@csstools/postcss-logical-float-and-clear@npm:4.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/fb61512fa4909bdf0ee32a23e771145086c445f2208a737b52093c8adfab7362c56d3aeaf2a6e33ffcec067e99a07219775465d2fbb1a3ac30cdcfb278b218b7 + checksum: 10c0/bbb2e69878965943fc9686c4827a2f363f7a42c051e6df288e68d68741b424fb3cbdc2aa3e2d2169bcf1a4cfd6a24f0aa06c3061ea02ac03e0ad0bfae8fdc999 languageName: node linkType: hard -"@csstools/postcss-normalize-display-values@npm:^4.0.1": - version: 4.0.1 - resolution: "@csstools/postcss-normalize-display-values@npm:4.0.1" - dependencies: - postcss-value-parser: "npm:^4.2.0" +"@csstools/postcss-logical-overflow@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-logical-overflow@npm:3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/5d19364bad8554b047cebd94ad7e203723ed76abaf690e4b92c74e6fc7c3642cb8858ade3263da61aff26d97bb258af567b1036e97865b7aa3b17522241fd1e1 + checksum: 10c0/cca3397de39eb858a216d0566598ff24a8c94d20c12630568437d8145d78c0d5f36533c72dab72f50702dabbcada87fd34b1129aa4d9c24b427aaa211df124c1 languageName: node linkType: hard -"@csstools/postcss-oklab-function@npm:^4.0.12": - version: 4.0.12 - resolution: "@csstools/postcss-oklab-function@npm:4.0.12" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" +"@csstools/postcss-logical-overscroll-behavior@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-logical-overscroll-behavior@npm:3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/40d4f51b568c8299c054f8971d0e85fa7da609ba23ce6c84dc17e16bc3838640ed6da75c3886dc9a96a11005773c6e23cba13a5510c781b2d633d07ad7bda6b7 + checksum: 10c0/0138d89c739dae70b3496d68e08109d97c82d0038fe9bcc5b40f6efb136428def62e81c316f1902d2e4e5611c90c66f70e2008e99fdd0c7358dcfdffb4e326cc languageName: node linkType: hard -"@csstools/postcss-position-area-property@npm:^1.0.0": - version: 1.0.0 - resolution: "@csstools/postcss-position-area-property@npm:1.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/38f770454d46bfed01d43a3f5e7ac07d3111399b374a7198ae6503cdb6288e410c7b4199f5a7af8f16aeb688216445ade97be417c084313d6c56f55e50d34559 - languageName: node - linkType: hard - -"@csstools/postcss-progressive-custom-properties@npm:^4.2.1": - version: 4.2.1 - resolution: "@csstools/postcss-progressive-custom-properties@npm:4.2.1" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/56e9a147799719fd5c550c035437693dd50cdfef46d66a4f2ce8f196e1006a096aa47d412710a89c3dc9808068a0a101c7f607a507ed68e925580c6f921e84d5 - languageName: node - linkType: hard - -"@csstools/postcss-property-rule-prelude-list@npm:^1.0.0": - version: 1.0.0 - resolution: "@csstools/postcss-property-rule-prelude-list@npm:1.0.0" - dependencies: - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/ae8bbca3a77ca59c21c11899a904f9d9417a19a3359d01dee042e0489b7ddfe7cea13ae275b7e7936d9b0b99c0a13f7f685f962cd63ca3d3d2b6e5eacc293a0d - languageName: node - linkType: hard - -"@csstools/postcss-random-function@npm:^2.0.1": - version: 2.0.1 - resolution: "@csstools/postcss-random-function@npm:2.0.1" - dependencies: - "@csstools/css-calc": "npm:^2.1.4" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/475bacf685b8bb82942d388e9e3b95f4156800f370299f19f5acc490475dc2813100de81a5a6bf48b696b4d83247622005b616af3166a668556b4b1aceded70d - languageName: node - linkType: hard - -"@csstools/postcss-relative-color-syntax@npm:^3.0.12": - version: 3.0.12 - resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.12" - dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/11af386c8193e22c148ac034eee94c56da3060bdbde3196d2d641b088e12de35bef187bcd7d421f9e4d49c4f1cfc28b24e136e62107e02ed7007a3a28f635d06 - languageName: node - linkType: hard - -"@csstools/postcss-scope-pseudo-class@npm:^4.0.1": - version: 4.0.1 - resolution: "@csstools/postcss-scope-pseudo-class@npm:4.0.1" - dependencies: - postcss-selector-parser: "npm:^7.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/6a0ca50fae655f4498200d1ce298ca794c85fbe2e3fd5d6419843254f055df5007a973e09b5f1e78e376c02b54278e411516c8d824300c68b265d3e5b311d7ee - languageName: node - linkType: hard - -"@csstools/postcss-sign-functions@npm:^1.1.4": - version: 1.1.4 - resolution: "@csstools/postcss-sign-functions@npm:1.1.4" - dependencies: - "@csstools/css-calc": "npm:^2.1.4" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/ff58108b2527832a84c571a1f40224b5c8d2afa8db2fe3b1e3599ff6f3469d9f4c528a70eb3c25c5d7801e30474fabfec04e7c23bfdad8572ad492053cd4f899 - languageName: node - linkType: hard - -"@csstools/postcss-stepped-value-functions@npm:^4.0.9": - version: 4.0.9 - resolution: "@csstools/postcss-stepped-value-functions@npm:4.0.9" - dependencies: - "@csstools/css-calc": "npm:^2.1.4" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/f143ca06338c30abb2aa37adc3d7e43a78f3b4493093160cb5babe3ec8cf6b86d83876746ee8e162db87b5e9af6e0066958d89fe8b4a503a29568e5c57c1bf8a - languageName: node - linkType: hard - -"@csstools/postcss-syntax-descriptor-syntax-production@npm:^1.0.1": - version: 1.0.1 - resolution: "@csstools/postcss-syntax-descriptor-syntax-production@npm:1.0.1" - dependencies: - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/b9b3d84a50b86b1af1b8b7e56a64d5eebc1c89c323a5263306c5c69ddb05a4d468d7072a7786b0ea6601629035df0089565e9d98d55d0f4eb7201cf7ed1bb3e9 - languageName: node - linkType: hard - -"@csstools/postcss-system-ui-font-family@npm:^1.0.0": - version: 1.0.0 - resolution: "@csstools/postcss-system-ui-font-family@npm:1.0.0" - dependencies: - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/6a81761ae3cae643659b1416a7a892cf1505474896193b8abc26cff319cb6b1a20b64c5330d64019fba458e058da3abc9407d0ebf0c102289c0b79ef99b4c6d6 - languageName: node - linkType: hard - -"@csstools/postcss-text-decoration-shorthand@npm:^4.0.3": - version: 4.0.3 - resolution: "@csstools/postcss-text-decoration-shorthand@npm:4.0.3" - dependencies: - "@csstools/color-helpers": "npm:^5.1.0" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/f6af7d5dcf599edcf76c5e396ef2d372bbe1c1f3fbaaccd91e91049e64b6ff68b44f459277aef0a8110baca3eaa21275012adc52ccb8c0fc526a4c35577f8fce - languageName: node - linkType: hard - -"@csstools/postcss-trigonometric-functions@npm:^4.0.9": - version: 4.0.9 - resolution: "@csstools/postcss-trigonometric-functions@npm:4.0.9" - dependencies: - "@csstools/css-calc": "npm:^2.1.4" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/6ba3d381c977c224f01d47a36f78c9b99d3b89d060a357a9f8840537fdf497d9587a28165dc74e96abdf02f8db0a277d3558646355085a74c8915ee73c6780d1 - languageName: node - linkType: hard - -"@csstools/postcss-unset-value@npm:^4.0.0": +"@csstools/postcss-logical-resize@npm:^4.0.0": version: 4.0.0 - resolution: "@csstools/postcss-unset-value@npm:4.0.0" + resolution: "@csstools/postcss-logical-resize@npm:4.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/8424ac700ded5bf59d49310335896f10c069e2c3fc6a676b5d13ca5a6fb78689b948f50494df875da284c4c76651deb005eafba70d87e693274628c5a685abfa + checksum: 10c0/b9a7eb1ecb1637dac76c957e05f3579e390514d3e93245b5e0a3764542e7875c89811c3526548d466a2fdf76f1cd1a8fadb62c536b3d2521d4310a2f143e405e languageName: node linkType: hard -"@csstools/selector-resolve-nested@npm:^3.1.0": - version: 3.1.0 - resolution: "@csstools/selector-resolve-nested@npm:3.1.0" +"@csstools/postcss-logical-viewport-units@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-logical-viewport-units@npm:4.0.0" + dependencies: + "@csstools/css-tokenizer": "npm:^4.0.0" + "@csstools/utilities": "npm:^3.0.0" peerDependencies: - postcss-selector-parser: ^7.0.0 - checksum: 10c0/c2b1a930ad03c1427ab90b28c4940424fb39e8175130148f16209be3a3937f7a146d5483ca1da1dfc100aa7ae86df713f0ee82d4bbaa9b986e7f47f35cb67cca + postcss: ^8.4 + checksum: 10c0/1213ee92b5d9aad68c65478d4243dbd1bd75b88090edec18e1ebaf0aa38459a53609b9aa0e8c48070d546a1c873b1de94a5449fa173a3440334a3ba366c14549 + languageName: node + linkType: hard + +"@csstools/postcss-media-minmax@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-media-minmax@npm:3.0.0" + dependencies: + "@csstools/css-calc": "npm:^3.0.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/0b03ab1ad162d53eef69470246025d01390cc84d2cb0983ffaf09c47bb3d27951e4794638430e778c737cf57c891f834adf3be08fcd5a84c4f6698a03ee9be4a + languageName: node + linkType: hard + +"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:4.0.0" + dependencies: + "@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/80397e21d16f443445d03b79171c5c5444ef0e921a6eb1e0419eec24150bd9112469e2e9825aaf0612fa98d05fb0a50cf09f09c2b86d01a238229f205c15d2b2 + languageName: node + linkType: hard + +"@csstools/postcss-nested-calc@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-nested-calc@npm:5.0.0" + dependencies: + "@csstools/utilities": "npm:^3.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/57764637a4855e941ab9360e1b4603e1fa0a29043148b67b80629d12ae4c93b4d2f156069826c4b8100ad3a90552744424b9a1e05710fe9b6958e1a817e6e07e + languageName: node + linkType: hard + +"@csstools/postcss-normalize-display-values@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-normalize-display-values@npm:5.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/d43a2658604bdd037ab1516bffadea4db3224390838f5096fccdfbae12a2db08e56ca576724a0279968e6af39419e5fe2963632754dd0956fda2d01b848cf97e + languageName: node + linkType: hard + +"@csstools/postcss-oklab-function@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-oklab-function@npm:5.0.0" + dependencies: + "@csstools/css-color-parser": "npm:^4.0.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/af3413b1667be101f39b15b9528a57ff764e5abcf80578590b95c27c42a493ed9dcd2927834371d211b025c5d5df103cded6f2def98c9c79301f76ca379d7dbf + languageName: node + linkType: hard + +"@csstools/postcss-position-area-property@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-position-area-property@npm:2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/54b9a97a9ef636dfc8b62cdabb7d194438c6f510effdff4339073626446c078033d475dfec8c82fdbcf25745fb05caafffc9512e3be7ff53ced55f1d38d2da6a + languageName: node + linkType: hard + +"@csstools/postcss-progressive-custom-properties@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-progressive-custom-properties@npm:5.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/8476a58b777e7015f40fea53829d31429c8bab114b884b87c1706b5df095102232fb46b275a76c719f73399d3dbdfec2c172fb68c2fb362dbe68569446366a1a + languageName: node + linkType: hard + +"@csstools/postcss-property-rule-prelude-list@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-property-rule-prelude-list@npm:2.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/6ce0c962391b27966fba1623bca2dacc61fb7b1c9916f08d17256a800b0f8476d34835d50cb2ed77bdc4e920e4be5dddc1569c7e5140895e7b0076694d769182 + languageName: node + linkType: hard + +"@csstools/postcss-random-function@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-random-function@npm:3.0.0" + dependencies: + "@csstools/css-calc": "npm:^3.0.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/bf9b8836ce3eafd670bfb9fa16d3befde8827ad220edc80eb4301fa716c1a4ff444254ab7612a6f6f952d16d3ac6d6e594448dcdd860f6ca8b575ffe59946864 + languageName: node + linkType: hard + +"@csstools/postcss-relative-color-syntax@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-relative-color-syntax@npm:4.0.0" + dependencies: + "@csstools/css-color-parser": "npm:^4.0.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/52f21acc66a0aa591d99173b9813f0b9f375de462bc657e909916ed176590b8d530a2eff83e71da488e2c8f476f0397d4a79bb7b7ef13664176d9e744a49510a + languageName: node + linkType: hard + +"@csstools/postcss-scope-pseudo-class@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-scope-pseudo-class@npm:5.0.0" + dependencies: + postcss-selector-parser: "npm:^7.1.1" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/2b67bd6af9a1175ca47c6593ee73564d70cb39c1adc9ffc5099ba513b49055994786c60687b44d88beccd09f1a6196e5e9157fd439632ae34381a60adaef5246 + languageName: node + linkType: hard + +"@csstools/postcss-sign-functions@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-sign-functions@npm:2.0.0" + dependencies: + "@csstools/css-calc": "npm:^3.0.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/e5239e012e78cfcb244e8599d93700bbbcd15fd78e4a71f8bd5296022bd1d1ead750689bdacc35a47d2061729dd727e2fb1a185bee0620a62b64e4ee3434861e + languageName: node + linkType: hard + +"@csstools/postcss-stepped-value-functions@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-stepped-value-functions@npm:5.0.0" + dependencies: + "@csstools/css-calc": "npm:^3.0.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/a8354fa0f4b74def1c258bf267c00d029f65618ccebb9c14837522f11eeb6b673717b56720588d5fec7bd07036ed84826dea01c44e9a085ae7af78a2b4ea7ec0 + languageName: node + linkType: hard + +"@csstools/postcss-syntax-descriptor-syntax-production@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-syntax-descriptor-syntax-production@npm:2.0.0" + dependencies: + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/0c9ef2f5cd21079db1df497adf99a3d66c912f69c471dd89283a1b8ed3a9c4e7670bfa439b615ac27acd86ce2e1adf4a5eb7b551113ecf5e95ee77269446bbbe + languageName: node + linkType: hard + +"@csstools/postcss-system-ui-font-family@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-system-ui-font-family@npm:2.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/e4f59c093565ed3753f3561a754fe3c0c8152a3b25f184cb370fa5cb1e9b54ad62fe53f7886ed840d445201120197ae6d42c6266ef4d5cfd063226f95c433c06 + languageName: node + linkType: hard + +"@csstools/postcss-text-decoration-shorthand@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-text-decoration-shorthand@npm:5.0.0" + dependencies: + "@csstools/color-helpers": "npm:^6.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/0d1dc47da13adc1f0863017a3448f3a79774f00f1952fc2bfc09404b00b03d125b54dedce036e0e5c73a6f2a64cdabe702e9bc03dc6b88d3b71a6424cd0cec80 + languageName: node + linkType: hard + +"@csstools/postcss-trigonometric-functions@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-trigonometric-functions@npm:5.0.0" + dependencies: + "@csstools/css-calc": "npm:^3.0.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/8b2e149f4fb8ac1bc7a642c99265df7f54729fbace29cde24f65078c0d97e9050e75576ef55b869ab78fbb94a2408adae2bc6f3ced759fca96519c6ceb92cc20 + languageName: node + linkType: hard + +"@csstools/postcss-unset-value@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/postcss-unset-value@npm:5.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/21e26cccf7824e4366378a20bc043034105db654632b941f77a0cbbf4fe0de291d1ae94845280c08c8d851ae14ce6d07bc09a33c5fbbcb7ce1f21b10212df1f1 + languageName: node + linkType: hard + +"@csstools/selector-resolve-nested@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/selector-resolve-nested@npm:4.0.0" + peerDependencies: + postcss-selector-parser: ^7.1.1 + checksum: 10c0/f6bccfe24a47c3e55710d421740550b868bbe7f0820f32d14d1eb737eefe7ec26261fb726cc5cfdf8236b3173d0a657ebdc9602e0c53824603f719b14a76bcaf languageName: node linkType: hard @@ -1849,6 +1905,15 @@ __metadata: languageName: node linkType: hard +"@csstools/selector-specificity@npm:^6.0.0": + version: 6.0.0 + resolution: "@csstools/selector-specificity@npm:6.0.0" + peerDependencies: + postcss-selector-parser: ^7.1.1 + checksum: 10c0/7a93973f9054f2e1f03c8543cde68e0b0c65e5e72da6e4e959974d28fe809e11bd2afa1ff2ca11a1690a4c9a2f2bbe00d00e2b07fb2108bf89c5e48fe441c432 + languageName: node + linkType: hard + "@csstools/stylelint-formatter-github@npm:^1.0.0": version: 1.0.0 resolution: "@csstools/stylelint-formatter-github@npm:1.0.0" @@ -1858,12 +1923,12 @@ __metadata: languageName: node linkType: hard -"@csstools/utilities@npm:^2.0.0": - version: 2.0.0 - resolution: "@csstools/utilities@npm:2.0.0" +"@csstools/utilities@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/utilities@npm:3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/be5c31437b726928f64cd4bb3e47f5b90bfd2e2a69a8eaabd8e89cc6c0977e4f0f7ee48de50c8ed8b07e04e3956a02293247e0da3236d521fb2e836f88f65822 + checksum: 10c0/65b6f6aa4ea899777bea710aab5f17028aa5e1aa41f7fe892c2e916b188da2b929c76be643d4a5d8d9ca5a7853df94f05c0247dd6b63540b360055d36db3104f languageName: node linkType: hard @@ -2881,7 +2946,7 @@ __metadata: msw-storybook-addon: "npm:^2.0.6" path-complete-extname: "npm:^1.0.0" playwright: "npm:^1.57.0" - postcss-preset-env: "npm:^10.1.5" + postcss-preset-env: "npm:^11.0.0" prettier: "npm:^3.3.3" prop-types: "npm:^15.8.1" punycode: "npm:^2.3.0" @@ -6219,14 +6284,14 @@ __metadata: languageName: node linkType: hard -"css-blank-pseudo@npm:^7.0.1": - version: 7.0.1 - resolution: "css-blank-pseudo@npm:7.0.1" +"css-blank-pseudo@npm:^8.0.1": + version: 8.0.1 + resolution: "css-blank-pseudo@npm:8.0.1" dependencies: - postcss-selector-parser: "npm:^7.0.0" + postcss-selector-parser: "npm:^7.1.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/46c3d3a611972fdb0c264db7c0b34fe437bc4300961d11945145cf04962f52a545a6ef55bc8ff4afd82b605bd692b4970f2b54582616dea00441105e725d4618 + checksum: 10c0/851d38cb3e11a63db4a0f44751cde99d6825bb11adc78418e8e83f32f0a3dcc3245e6e4b5053e796635560a1224af8ae63dfe330f87175fcc7bf34a1af3ecddc languageName: node linkType: hard @@ -6237,25 +6302,25 @@ __metadata: languageName: node linkType: hard -"css-has-pseudo@npm:^7.0.3": - version: 7.0.3 - resolution: "css-has-pseudo@npm:7.0.3" +"css-has-pseudo@npm:^8.0.0": + version: 8.0.0 + resolution: "css-has-pseudo@npm:8.0.0" dependencies: - "@csstools/selector-specificity": "npm:^5.0.0" - postcss-selector-parser: "npm:^7.0.0" + "@csstools/selector-specificity": "npm:^6.0.0" + postcss-selector-parser: "npm:^7.1.1" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/c89f68e17bed229e9a3e98da5032e1360c83d45d974bc3fb8d6b5358399bca80cce7929e4a621a516a75536edb78678dc486eb41841eeed28cca79e3be4bdc27 + checksum: 10c0/fb623c09d3cb1d5ec029c61b5a24484ee712fbd20bffae37a20a2843917706cadb1c4d695585382e4839dde136922ffee6924ec83f633dde2e2afc6d77a26be8 languageName: node linkType: hard -"css-prefers-color-scheme@npm:^10.0.0": - version: 10.0.0 - resolution: "css-prefers-color-scheme@npm:10.0.0" +"css-prefers-color-scheme@npm:^11.0.0": + version: 11.0.0 + resolution: "css-prefers-color-scheme@npm:11.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/a66c727bb2455328b18862f720819fc98ff5c1486b69f758bdb5c66f46cc6d484f9fc0bfa4f00f2693c5da6707ad136ca789496982f713ade693f08af624930e + checksum: 10c0/dea9f17cc33b5bedb660ed3925a53c9034bd5f4f94222d3b6df1dffd011061c124800dfc3bbeb1136fd69e84be577c63ac3da9220e175ea10b49275d7d0ada40 languageName: node linkType: hard @@ -10673,14 +10738,14 @@ __metadata: languageName: node linkType: hard -"postcss-attribute-case-insensitive@npm:^7.0.1": - version: 7.0.1 - resolution: "postcss-attribute-case-insensitive@npm:7.0.1" +"postcss-attribute-case-insensitive@npm:^8.0.0": + version: 8.0.0 + resolution: "postcss-attribute-case-insensitive@npm:8.0.0" dependencies: - postcss-selector-parser: "npm:^7.0.0" + postcss-selector-parser: "npm:^7.1.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/48945abe2024e2d2e4c37d30b8c1aaf37af720f24f6a996f7ea7e7ed33621f5c22cf247ed22028c0c922de040c58c0802729bc39b903cb1693f4b63c0b49da34 + checksum: 10c0/d6442d1580a3ec5083ff41435e8423dfe9c5b3b65b610e082769d27f11b98e0699d5361f7e9a9e499805c0a78d83a8aec3dd9f9d9280636cca82ec1aea79e928 languageName: node linkType: hard @@ -10695,131 +10760,131 @@ __metadata: languageName: node linkType: hard -"postcss-color-functional-notation@npm:^7.0.12": - version: 7.0.12 - resolution: "postcss-color-functional-notation@npm:7.0.12" +"postcss-color-functional-notation@npm:^8.0.0": + version: 8.0.0 + resolution: "postcss-color-functional-notation@npm:8.0.0" dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" + "@csstools/css-color-parser": "npm:^4.0.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/dc80ba1a956ae9b396596bda72d9bdb92de96874378a38ba4e2177ffa35339dc76d894920bb013b6f10c9b75cfb41778e09956a438c2e9ea41b684f766c55f4a + checksum: 10c0/4b23633475b57f5d0076340ab4f434506891b5a25254d077215c08d08bbdab369484488bbc249595618730afea3546329e5633cc9e119f31c756b1c224dd0300 languageName: node linkType: hard -"postcss-color-hex-alpha@npm:^10.0.0": +"postcss-color-hex-alpha@npm:^11.0.0": + version: 11.0.0 + resolution: "postcss-color-hex-alpha@npm:11.0.0" + dependencies: + "@csstools/utilities": "npm:^3.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/375509c404b0ac496f66bf0910d12e5aabca058c0d9de9d10719e9a0729013d5f4fafd06d8c8073b2d68613b3346e8ff79ef223a5ce4ddf0d4c4f645fd7a5436 + languageName: node + linkType: hard + +"postcss-color-rebeccapurple@npm:^11.0.0": + version: 11.0.0 + resolution: "postcss-color-rebeccapurple@npm:11.0.0" + dependencies: + "@csstools/utilities": "npm:^3.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/9f22fff4ce2f0d7ce07be2ba2d07697e24f7db83a6252f02f36aa3a556cf69e1346a3cbab3908264e267306cc5a79b5d95c21e6fa4257a68c13c7411224543b9 + languageName: node + linkType: hard + +"postcss-custom-media@npm:^12.0.0": + version: 12.0.0 + resolution: "postcss-custom-media@npm:12.0.0" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^3.0.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/bf1f3939d83a24eb7ef86f9fc2cb799026d4dc4620d3769eef9234d23fd528c73e907c2b88cb239290d169e5ddf8f0624cf8b22084874953c224e6000d55bf50 + languageName: node + linkType: hard + +"postcss-custom-properties@npm:^15.0.0": + version: 15.0.0 + resolution: "postcss-custom-properties@npm:15.0.0" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^3.0.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + "@csstools/utilities": "npm:^3.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/d766f1632075f8db2cd5075967cc4397c46b59699dd7a70762d9bd3e1ff91d3ada167b4f79e7e254518cdaae8d71645cd92faeb89559175ed50337c0acf5b0b4 + languageName: node + linkType: hard + +"postcss-custom-selectors@npm:^9.0.0": + version: 9.0.0 + resolution: "postcss-custom-selectors@npm:9.0.0" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^3.0.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + postcss-selector-parser: "npm:^7.1.1" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/a99d87639e8bca0a3eebd910356cecd67a4153789a77e15604db68caee415e64bc90d3e58a8322ed3d37addb80574a1180e58d603ac6541364b373b322e7de9f + languageName: node + linkType: hard + +"postcss-dir-pseudo-class@npm:^10.0.0": version: 10.0.0 - resolution: "postcss-color-hex-alpha@npm:10.0.0" + resolution: "postcss-dir-pseudo-class@npm:10.0.0" dependencies: - "@csstools/utilities": "npm:^2.0.0" - postcss-value-parser: "npm:^4.2.0" + postcss-selector-parser: "npm:^7.1.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/8a6dcb27403d04b55d6de88bf3074622bcea537fc4436bbcb346e92289c4d17059444e2e6c3554c325e7a777bb4cdc711e764a83123b4000aec211052e957d5b + checksum: 10c0/c7cae0b4e96bf0e51845c20b6fae7eec7b428b569b4fbae639b4f1f3b16a0ced4f5610ffb0209484ba4dfc8858abc64a9577cc8d2e3f1dbf3154f4ed0eb07669 languageName: node linkType: hard -"postcss-color-rebeccapurple@npm:^10.0.0": +"postcss-double-position-gradients@npm:^7.0.0": + version: 7.0.0 + resolution: "postcss-double-position-gradients@npm:7.0.0" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^5.0.0" + "@csstools/utilities": "npm:^3.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/0a9a1c567c79ddbe7bbeafe9770f2393d4d0eb344a79a2823dc5944690d74c311841ff6fd533b823d282029eff07418eeb671fabc85db6414ace48cce37223e8 + languageName: node + linkType: hard + +"postcss-focus-visible@npm:^11.0.0": + version: 11.0.0 + resolution: "postcss-focus-visible@npm:11.0.0" + dependencies: + postcss-selector-parser: "npm:^7.1.1" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/001cdfb200fe83b25a0fdbb7315b70fcf91c71d0134e16d48334fc7eab3d41c7cd3b8fe7bb8201a0ef9f242afb10f71aed238715a1d6923f68a02e6dc4219916 + languageName: node + linkType: hard + +"postcss-focus-within@npm:^10.0.0": version: 10.0.0 - resolution: "postcss-color-rebeccapurple@npm:10.0.0" + resolution: "postcss-focus-within@npm:10.0.0" dependencies: - "@csstools/utilities": "npm:^2.0.0" - postcss-value-parser: "npm:^4.2.0" + postcss-selector-parser: "npm:^7.1.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/308e33f76f2b48c1c2121d4502fc053e869f3415898de7d30314353df680e79b37497e7b628e3447edc1049091da3672f7d891e45604f238598e846e06b893ed - languageName: node - linkType: hard - -"postcss-custom-media@npm:^11.0.6": - version: 11.0.6 - resolution: "postcss-custom-media@npm:11.0.6" - dependencies: - "@csstools/cascade-layer-name-parser": "npm:^2.0.5" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/media-query-list-parser": "npm:^4.0.3" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/62dcb2858fd490d90aab32062621d58892a7b2a54948ee63af81a2cd61807a11815d28d4ef6bc800c5e142ac73098f7e56822c7cc63192eb20d5b16071543a73 - languageName: node - linkType: hard - -"postcss-custom-properties@npm:^14.0.6": - version: 14.0.6 - resolution: "postcss-custom-properties@npm:14.0.6" - dependencies: - "@csstools/cascade-layer-name-parser": "npm:^2.0.5" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/utilities": "npm:^2.0.0" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/0eeef77bc713551f5cb8fa5982d24da4e854075f3af020f1c94366c47a23a4cc225ebfecc978bdb17f00ee0bdee9d2c784e0d01adc64a447321e408abbe2c83b - languageName: node - linkType: hard - -"postcss-custom-selectors@npm:^8.0.5": - version: 8.0.5 - resolution: "postcss-custom-selectors@npm:8.0.5" - dependencies: - "@csstools/cascade-layer-name-parser": "npm:^2.0.5" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - postcss-selector-parser: "npm:^7.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/bd8f2f85bbec4bd56ff408cb699d9fe649e2af0db82d5752eee05481ae522f06f5a47950ca22fcb4c8601071c03346df67cf20b0b0bcade32ce58d07ebaf9b32 - languageName: node - linkType: hard - -"postcss-dir-pseudo-class@npm:^9.0.1": - version: 9.0.1 - resolution: "postcss-dir-pseudo-class@npm:9.0.1" - dependencies: - postcss-selector-parser: "npm:^7.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/da9d3387648c5c3161a653d354c8f3e70a299108df3977e8aa65cf10793e4dd58a2711b3426cd63716245b13584ca8d95adcd6e10e3c9adbc61d08743e2d8690 - languageName: node - linkType: hard - -"postcss-double-position-gradients@npm:^6.0.4": - version: 6.0.4 - resolution: "postcss-double-position-gradients@npm:6.0.4" - dependencies: - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/6dbbe7a3855e84a9319df434e210225f6dfa7262e5959611355f1769c2c9d30d37a19737712f20eac6354876fff4ba556d8d0b12a90c78d8ab97c9a8da534a7c - languageName: node - linkType: hard - -"postcss-focus-visible@npm:^10.0.1": - version: 10.0.1 - resolution: "postcss-focus-visible@npm:10.0.1" - dependencies: - postcss-selector-parser: "npm:^7.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/c5ecc8536a708a49a99d0abd68a88a160664e6c832c808db8edd9f0221e7017a258daa87e49daf2cb098cb037005d46cf492403c8c9c92ad8835d30adaccf665 - languageName: node - linkType: hard - -"postcss-focus-within@npm:^9.0.1": - version: 9.0.1 - resolution: "postcss-focus-within@npm:9.0.1" - dependencies: - postcss-selector-parser: "npm:^7.0.0" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/d6ab49d2a7f33485a9e137dc77ec92c5619a3ec92e1e672734fc604853ff1f3c0c189085c12461614be4fcb03ea0347d91791a45986a18d50b5228d161eda57a + checksum: 10c0/297ba52c07ba9284e7bf862e23ce90312bc2068d005a3c95833d832462691fac276850dd5508228fc808ec5f2963cb61a8b510645e08b78a74a9aaff9a79b02b languageName: node linkType: hard @@ -10832,39 +10897,39 @@ __metadata: languageName: node linkType: hard -"postcss-gap-properties@npm:^6.0.0": - version: 6.0.0 - resolution: "postcss-gap-properties@npm:6.0.0" +"postcss-gap-properties@npm:^7.0.0": + version: 7.0.0 + resolution: "postcss-gap-properties@npm:7.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/4e07e0d3927d0e65d67eaf047ac39e08d39cb1bf74e16e10c7df7f0d01b184a77ea59f63fd5691b5ed6df159970b972db28cb784d883e26e981137696460897d + checksum: 10c0/c93a0d15d40a7ae474a1bf05bd8f77410c6a353c7ea3bbad3aa61c38f6471d9bf23bac738aa1b3ca58805a33a730a4e6d15b7d03e0d2f1a29ef70969c14b483d languageName: node linkType: hard -"postcss-image-set-function@npm:^7.0.0": - version: 7.0.0 - resolution: "postcss-image-set-function@npm:7.0.0" +"postcss-image-set-function@npm:^8.0.0": + version: 8.0.0 + resolution: "postcss-image-set-function@npm:8.0.0" dependencies: - "@csstools/utilities": "npm:^2.0.0" + "@csstools/utilities": "npm:^3.0.0" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/913fd9492f00122aa0c2550fb0d72130428cbe1e6465bc65e8fe71e9deb10ac0c01d7caceb68b560da759139e8cbc6c90ed22dfe6cf34949af49bb86bcbf4d3a + checksum: 10c0/b2c527d8731c97f09e79ab477069138eba994a656473f57e1afd2480b18cd54cdb6600888e44d208b11efae01c123bc81629ecb77d8ed19397bc2a4b8da6da52 languageName: node linkType: hard -"postcss-lab-function@npm:^7.0.12": - version: 7.0.12 - resolution: "postcss-lab-function@npm:7.0.12" +"postcss-lab-function@npm:^8.0.0": + version: 8.0.0 + resolution: "postcss-lab-function@npm:8.0.0" dependencies: - "@csstools/css-color-parser": "npm:^3.1.0" - "@csstools/css-parser-algorithms": "npm:^3.0.5" - "@csstools/css-tokenizer": "npm:^3.0.4" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/utilities": "npm:^2.0.0" + "@csstools/css-color-parser": "npm:^4.0.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/de39b59da3b97c18d055d81fba68993e93253184ed76f103c888273584f868c551d047814dd54445980a1bdc5987e8f8af141383d84ecc641e5a6ee7bd901095 + checksum: 10c0/8805a853016c5919afd95f7cbee9ba624f398d25c81a2a7987edc0d0031fee08732b02f64cb51f537f364b0de07fc837bb4bd375e0ea96a489860c59a94c0590 languageName: node linkType: hard @@ -10886,14 +10951,14 @@ __metadata: languageName: node linkType: hard -"postcss-logical@npm:^8.1.0": - version: 8.1.0 - resolution: "postcss-logical@npm:8.1.0" +"postcss-logical@npm:^9.0.0": + version: 9.0.0 + resolution: "postcss-logical@npm:9.0.0" dependencies: postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/0e2e9e901d8a550db7f682d46b1f7e4f363c1ada061dc8e4548e2b563c5e39f3684a2d7c3f11fe061188782bca37874e34967fc6179fa6d98a49ff66a0076d27 + checksum: 10c0/058394f2234464b043249317b2c0a2271fa5194ada0f39afdb0f5b7e2ad172b1f24da7ae2641dfbd44d1ee98f5438844bc85d6005b881f691630381673707c32 languageName: node linkType: hard @@ -10937,16 +11002,16 @@ __metadata: languageName: node linkType: hard -"postcss-nesting@npm:^13.0.2": - version: 13.0.2 - resolution: "postcss-nesting@npm:13.0.2" +"postcss-nesting@npm:^14.0.0": + version: 14.0.0 + resolution: "postcss-nesting@npm:14.0.0" dependencies: - "@csstools/selector-resolve-nested": "npm:^3.1.0" - "@csstools/selector-specificity": "npm:^5.0.0" - postcss-selector-parser: "npm:^7.0.0" + "@csstools/selector-resolve-nested": "npm:^4.0.0" + "@csstools/selector-specificity": "npm:^6.0.0" + postcss-selector-parser: "npm:^7.1.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/bfa0578b3b686c6374f5a7b2f6ef955cb7e13400de95a919975a982ae43c1e25db37385618f210715ff15393dc7ff8c26c7b156f06b8fb3118a426099cf7f1f2 + checksum: 10c0/ffa1799ed49759be7f09940614a8cfd06f15bd13e01a7eaabf788939c23a6b617ef281ae383f5b3b3078ddd6d78fd90624af164ea5a421698a1b0972cf14253c languageName: node linkType: hard @@ -10959,14 +11024,14 @@ __metadata: languageName: node linkType: hard -"postcss-overflow-shorthand@npm:^6.0.0": - version: 6.0.0 - resolution: "postcss-overflow-shorthand@npm:6.0.0" +"postcss-overflow-shorthand@npm:^7.0.0": + version: 7.0.0 + resolution: "postcss-overflow-shorthand@npm:7.0.0" dependencies: postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/6598321b2ed0b68461135395bba9c7f76a4672617770df1e8487f459bc975f4ded6c3d37b6f72a44f4f77f7b6789e0c6f927e66dbbf1bcde1537167dbea39968 + checksum: 10c0/706430a2b1c28f361aad1d90e618c04c7ad7861971f2447fbd0e34c1d095bec0e5f4ed2857c98e05f5a7bcc694b7b763d08d5f515d5d45dd942f4b046593d638 languageName: node linkType: hard @@ -10979,106 +11044,106 @@ __metadata: languageName: node linkType: hard -"postcss-place@npm:^10.0.0": - version: 10.0.0 - resolution: "postcss-place@npm:10.0.0" +"postcss-place@npm:^11.0.0": + version: 11.0.0 + resolution: "postcss-place@npm:11.0.0" dependencies: postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/ebb13deaac7648ba6042622375a31f78fbcc5209b7d196e478debbdf94525963fe621c932f4737a5b6b3d487af3b5ed6d059ed6193fdcbff6d3d5b150886ccc1 + checksum: 10c0/0b6b27426b198507583388a0e4f27552a136e5b9ccd1fc356dbd9a04c91e2bf6dcd680c3ad540ffc5aa1a63779467b9c7dc43d2164f83a0658505ca2408d79a9 languageName: node linkType: hard -"postcss-preset-env@npm:^10.1.5": - version: 10.6.1 - resolution: "postcss-preset-env@npm:10.6.1" +"postcss-preset-env@npm:^11.0.0": + version: 11.0.1 + resolution: "postcss-preset-env@npm:11.0.1" dependencies: - "@csstools/postcss-alpha-function": "npm:^1.0.1" - "@csstools/postcss-cascade-layers": "npm:^5.0.2" - "@csstools/postcss-color-function": "npm:^4.0.12" - "@csstools/postcss-color-function-display-p3-linear": "npm:^1.0.1" - "@csstools/postcss-color-mix-function": "npm:^3.0.12" - "@csstools/postcss-color-mix-variadic-function-arguments": "npm:^1.0.2" - "@csstools/postcss-content-alt-text": "npm:^2.0.8" - "@csstools/postcss-contrast-color-function": "npm:^2.0.12" - "@csstools/postcss-exponential-functions": "npm:^2.0.9" - "@csstools/postcss-font-format-keywords": "npm:^4.0.0" - "@csstools/postcss-gamut-mapping": "npm:^2.0.11" - "@csstools/postcss-gradients-interpolation-method": "npm:^5.0.12" - "@csstools/postcss-hwb-function": "npm:^4.0.12" - "@csstools/postcss-ic-unit": "npm:^4.0.4" - "@csstools/postcss-initial": "npm:^2.0.1" - "@csstools/postcss-is-pseudo-class": "npm:^5.0.3" - "@csstools/postcss-light-dark-function": "npm:^2.0.11" - "@csstools/postcss-logical-float-and-clear": "npm:^3.0.0" - "@csstools/postcss-logical-overflow": "npm:^2.0.0" - "@csstools/postcss-logical-overscroll-behavior": "npm:^2.0.0" - "@csstools/postcss-logical-resize": "npm:^3.0.0" - "@csstools/postcss-logical-viewport-units": "npm:^3.0.4" - "@csstools/postcss-media-minmax": "npm:^2.0.9" - "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^3.0.5" - "@csstools/postcss-nested-calc": "npm:^4.0.0" - "@csstools/postcss-normalize-display-values": "npm:^4.0.1" - "@csstools/postcss-oklab-function": "npm:^4.0.12" - "@csstools/postcss-position-area-property": "npm:^1.0.0" - "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" - "@csstools/postcss-property-rule-prelude-list": "npm:^1.0.0" - "@csstools/postcss-random-function": "npm:^2.0.1" - "@csstools/postcss-relative-color-syntax": "npm:^3.0.12" - "@csstools/postcss-scope-pseudo-class": "npm:^4.0.1" - "@csstools/postcss-sign-functions": "npm:^1.1.4" - "@csstools/postcss-stepped-value-functions": "npm:^4.0.9" - "@csstools/postcss-syntax-descriptor-syntax-production": "npm:^1.0.1" - "@csstools/postcss-system-ui-font-family": "npm:^1.0.0" - "@csstools/postcss-text-decoration-shorthand": "npm:^4.0.3" - "@csstools/postcss-trigonometric-functions": "npm:^4.0.9" - "@csstools/postcss-unset-value": "npm:^4.0.0" + "@csstools/postcss-alpha-function": "npm:^2.0.0" + "@csstools/postcss-cascade-layers": "npm:^6.0.0" + "@csstools/postcss-color-function": "npm:^5.0.0" + "@csstools/postcss-color-function-display-p3-linear": "npm:^2.0.0" + "@csstools/postcss-color-mix-function": "npm:^4.0.0" + "@csstools/postcss-color-mix-variadic-function-arguments": "npm:^2.0.0" + "@csstools/postcss-content-alt-text": "npm:^3.0.0" + "@csstools/postcss-contrast-color-function": "npm:^3.0.0" + "@csstools/postcss-exponential-functions": "npm:^3.0.0" + "@csstools/postcss-font-format-keywords": "npm:^5.0.0" + "@csstools/postcss-gamut-mapping": "npm:^3.0.0" + "@csstools/postcss-gradients-interpolation-method": "npm:^6.0.0" + "@csstools/postcss-hwb-function": "npm:^5.0.0" + "@csstools/postcss-ic-unit": "npm:^5.0.0" + "@csstools/postcss-initial": "npm:^3.0.0" + "@csstools/postcss-is-pseudo-class": "npm:^6.0.0" + "@csstools/postcss-light-dark-function": "npm:^3.0.0" + "@csstools/postcss-logical-float-and-clear": "npm:^4.0.0" + "@csstools/postcss-logical-overflow": "npm:^3.0.0" + "@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.0" + "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^4.0.0" + "@csstools/postcss-nested-calc": "npm:^5.0.0" + "@csstools/postcss-normalize-display-values": "npm:^5.0.0" + "@csstools/postcss-oklab-function": "npm:^5.0.0" + "@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.0" + "@csstools/postcss-relative-color-syntax": "npm:^4.0.0" + "@csstools/postcss-scope-pseudo-class": "npm:^5.0.0" + "@csstools/postcss-sign-functions": "npm:^2.0.0" + "@csstools/postcss-stepped-value-functions": "npm:^5.0.0" + "@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.0" + "@csstools/postcss-trigonometric-functions": "npm:^5.0.0" + "@csstools/postcss-unset-value": "npm:^5.0.0" autoprefixer: "npm:^10.4.23" browserslist: "npm:^4.28.1" - css-blank-pseudo: "npm:^7.0.1" - css-has-pseudo: "npm:^7.0.3" - css-prefers-color-scheme: "npm:^10.0.0" + css-blank-pseudo: "npm:^8.0.1" + css-has-pseudo: "npm:^8.0.0" + css-prefers-color-scheme: "npm:^11.0.0" cssdb: "npm:^8.6.0" - postcss-attribute-case-insensitive: "npm:^7.0.1" + postcss-attribute-case-insensitive: "npm:^8.0.0" postcss-clamp: "npm:^4.1.0" - postcss-color-functional-notation: "npm:^7.0.12" - postcss-color-hex-alpha: "npm:^10.0.0" - postcss-color-rebeccapurple: "npm:^10.0.0" - postcss-custom-media: "npm:^11.0.6" - postcss-custom-properties: "npm:^14.0.6" - postcss-custom-selectors: "npm:^8.0.5" - postcss-dir-pseudo-class: "npm:^9.0.1" - postcss-double-position-gradients: "npm:^6.0.4" - postcss-focus-visible: "npm:^10.0.1" - postcss-focus-within: "npm:^9.0.1" + postcss-color-functional-notation: "npm:^8.0.0" + postcss-color-hex-alpha: "npm:^11.0.0" + postcss-color-rebeccapurple: "npm:^11.0.0" + postcss-custom-media: "npm:^12.0.0" + postcss-custom-properties: "npm:^15.0.0" + postcss-custom-selectors: "npm:^9.0.0" + postcss-dir-pseudo-class: "npm:^10.0.0" + postcss-double-position-gradients: "npm:^7.0.0" + postcss-focus-visible: "npm:^11.0.0" + postcss-focus-within: "npm:^10.0.0" postcss-font-variant: "npm:^5.0.0" - postcss-gap-properties: "npm:^6.0.0" - postcss-image-set-function: "npm:^7.0.0" - postcss-lab-function: "npm:^7.0.12" - postcss-logical: "npm:^8.1.0" - postcss-nesting: "npm:^13.0.2" + postcss-gap-properties: "npm:^7.0.0" + postcss-image-set-function: "npm:^8.0.0" + postcss-lab-function: "npm:^8.0.0" + postcss-logical: "npm:^9.0.0" + postcss-nesting: "npm:^14.0.0" postcss-opacity-percentage: "npm:^3.0.0" - postcss-overflow-shorthand: "npm:^6.0.0" + postcss-overflow-shorthand: "npm:^7.0.0" postcss-page-break: "npm:^3.0.4" - postcss-place: "npm:^10.0.0" - postcss-pseudo-class-any-link: "npm:^10.0.1" + postcss-place: "npm:^11.0.0" + postcss-pseudo-class-any-link: "npm:^11.0.0" postcss-replace-overflow-wrap: "npm:^4.0.0" - postcss-selector-not: "npm:^8.0.1" + postcss-selector-not: "npm:^9.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/e8da96f208918ebc0dc9acc8ba8961a92569f1d130b29abe25adaf7dbd56ef29fc6f778b75964c80fe7f3469012c763ea9447e5c2f559a002a155bc0462cce35 + checksum: 10c0/f4f3a01b1e20fd784c9e4233ed84718293adbf040db78f6af6f703fce0c320c921bd4131657df01099e37bd7082a815b9387ca92c4406dccd4808ceb0c7ab9ca languageName: node linkType: hard -"postcss-pseudo-class-any-link@npm:^10.0.1": - version: 10.0.1 - resolution: "postcss-pseudo-class-any-link@npm:10.0.1" +"postcss-pseudo-class-any-link@npm:^11.0.0": + version: 11.0.0 + resolution: "postcss-pseudo-class-any-link@npm:11.0.0" dependencies: - postcss-selector-parser: "npm:^7.0.0" + postcss-selector-parser: "npm:^7.1.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/95e883996e87baf14fc09d25f9a763a2e9d599eb3b9c6b736e83a8c3d0b55841bcb886bccdf51b5b7fefc128cbd0187ad8841f59878f85bd1613642e592d7673 + checksum: 10c0/bd7e1daa582bc19fe0e30c3ab8b13f8cf75ae2f116124960e9d28092255ede77455cd982bef8010d7ea64fdffc66a14624e63fe438e6d957415f5a4bc16b59a4 languageName: node linkType: hard @@ -11116,24 +11181,24 @@ __metadata: languageName: node linkType: hard -"postcss-selector-not@npm:^8.0.1": - version: 8.0.1 - resolution: "postcss-selector-not@npm:8.0.1" +"postcss-selector-not@npm:^9.0.0": + version: 9.0.0 + resolution: "postcss-selector-not@npm:9.0.0" dependencies: - postcss-selector-parser: "npm:^7.0.0" + postcss-selector-parser: "npm:^7.1.1" peerDependencies: postcss: ^8.4 - checksum: 10c0/491ea3dcc421cd90135be786078521605e2062fb93624ea8813cfd5ba0d35143f931e2e608d5f20effd5ea7d3f4786d2afea2afa42d117779a0288e135f132b6 + checksum: 10c0/090ce6d65667b8b6b9c1c26b7bd7740a31f3e7f36c29574e622d5fac5ab2ce291ac11622914745f22dda531d75e36fcdd395339fbe8fac0fc04fa7d750bb5248 languageName: node linkType: hard -"postcss-selector-parser@npm:^7.0.0, postcss-selector-parser@npm:^7.1.0": - version: 7.1.0 - resolution: "postcss-selector-parser@npm:7.1.0" +"postcss-selector-parser@npm:^7.0.0, postcss-selector-parser@npm:^7.1.0, postcss-selector-parser@npm:^7.1.1": + version: 7.1.1 + resolution: "postcss-selector-parser@npm:7.1.1" dependencies: cssesc: "npm:^3.0.0" util-deprecate: "npm:^1.0.2" - checksum: 10c0/0fef257cfd1c0fe93c18a3f8a6e739b4438b527054fd77e9a62730a89b2d0ded1b59314a7e4aaa55bc256204f40830fecd2eb50f20f8cb7ab3a10b52aa06c8aa + checksum: 10c0/02d3b1589ddcddceed4b583b098b95a7266dacd5135f041e5d913ebb48e874fd333a36e564cc9a2ec426a464cb18db11cb192ac76247aced5eba8c951bf59507 languageName: node linkType: hard From 47557f5b9ecfe6126eb938ce4e911344f4b8c752 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:41:11 +0100 Subject: [PATCH 016/145] New Crowdin Translations (automated) (#37492) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/gd.json | 4 ++-- config/locales/gd.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index e48c60ff2ce..50db4dfb4bf 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -283,7 +283,7 @@ "confirmations.private_quote_notify.title": "A bheil thu airson a cho-roinneadh leis an luchd-leantainn ’s na cleachdaichean le iomradh orra?", "confirmations.quiet_post_quote_info.dismiss": "Na cuiribh seo ’nam chuimhne a-rithist", "confirmations.quiet_post_quote_info.got_it": "Tha mi agaibh", - "confirmations.quiet_post_quote_info.message": "Nuair a luaidheas tu post a tha poblach ach sàmhach, thèid am post agad fhalach o loidhnichean-ama nan treandaichean.", + "confirmations.quiet_post_quote_info.message": "Nuair a luaidheas tu post sàmhach, thèid am post agad fhalach o loidhnichean-ama nan treandaichean.", "confirmations.quiet_post_quote_info.title": "Luaidh air postaichean sàmhach", "confirmations.redraft.confirm": "Sguab às ⁊ dèan dreachd ùr", "confirmations.redraft.message": "A bheil thu cinnteach gu bheil thu airson am post seo a sguabadh às agus dreachd ùr a thòiseachadh? Caillidh tu gach annsachd is brosnachadh air agus thèid freagairtean dhan phost thùsail ’nan dìlleachdanan.", @@ -790,7 +790,7 @@ "privacy.quote.disabled": "{visibility}, luaidh à comas", "privacy.quote.limited": "{visibility}, luaidh cuingichte", "privacy.unlisted.additional": "Tha seo coltach ris an fhaicsinneachd phoblach ach cha nochd am post air loidhnichean-ama an t-saoghail phoblaich, nan tagaichean hais no an rùrachaidh no ann an toraidhean luirg Mhastodon fiù ’s ma thug thu ro-aonta airson sin seachad.", - "privacy.unlisted.long": "Poblach ach falaichte o na toraidhean-luirg, na treandaichean ’s na loichnichean-ama poblach", + "privacy.unlisted.long": "Falaichte o na toraidhean-luirg, na treandaichean ’s na loidhnichean-ama poblach", "privacy.unlisted.short": "Sàmhach", "privacy_policy.last_updated": "An t-ùrachadh mu dheireadh {date}", "privacy_policy.title": "Poileasaidh prìobhaideachd", diff --git a/config/locales/gd.yml b/config/locales/gd.yml index 8f64ee1984b..543cd0cb460 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -2054,7 +2054,7 @@ gd: public: Poblach public_long: Neach sam bith taobh a-staigh no a-muigh Mhastodon unlisted: Sàmhach - unlisted_long: Falaichte o na toraidhean-luirg, na treandaichean ’s na loichnichean-ama poblach + unlisted_long: Falaichte o na toraidhean-luirg, na treandaichean ’s na loidhnichean-ama poblach statuses_cleanup: enabled: Sguab às seann-phostaichean gu fèin-obrachail enabled_hint: Sguabaidh seo às na seann-phostaichean agad gu fèin-obrachail nuair a ruigeas iad stairsneach aoise sònraichte ach ma fhreagras iad ri gin dhe na h-eisgeachdan gu h-ìosal From 2a6c084aa126c7171fb8a1042b9d1b35a1c3236d Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Jan 2026 14:17:41 +0100 Subject: [PATCH 017/145] Update SECURITY.md (#37506) --- SECURITY.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 12052652e6c..e5790a66fa2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -18,5 +18,4 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through | 4.5.x | Yes | | 4.4.x | Yes | | 4.3.x | Until 2026-05-06 | -| 4.2.x | Until 2026-01-08 | -| < 4.2 | No | +| < 4.3 | No | From f2fb232e37d382308c4afa69fb189585306ec4c4 Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 15 Jan 2026 14:45:30 +0100 Subject: [PATCH 018/145] Account header split up (#37490) --- .../components/account_header.tsx | 809 +----------------- .../account_timeline/components/badges.tsx | 43 + .../account_timeline/components/buttons.tsx | 134 +++ .../account_timeline/components/info.tsx | 68 ++ .../account_timeline/components/links.tsx | 58 ++ .../account_timeline/components/menu.tsx | 373 ++++++++ .../account_timeline/components/tabs.tsx | 27 + app/javascript/mastodon/hooks/useAccount.ts | 26 + 8 files changed, 776 insertions(+), 762 deletions(-) create mode 100644 app/javascript/mastodon/features/account_timeline/components/badges.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/buttons.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/info.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/links.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/menu.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/tabs.tsx create mode 100644 app/javascript/mastodon/hooks/useAccount.ts diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index 040ca16c720..4244d7f7026 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -1,177 +1,35 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; -import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; +import { useIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; -import { NavLink } from 'react-router-dom'; import { AccountBio } from '@/mastodon/components/account_bio'; import { AccountFields } from '@/mastodon/components/account_fields'; import { DisplayName } from '@/mastodon/components/display_name'; import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; import LockIcon from '@/material-icons/400-24px/lock.svg?react'; -import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; -import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; -import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react'; -import ShareIcon from '@/material-icons/400-24px/share.svg?react'; -import { - followAccount, - unblockAccount, - unmuteAccount, - pinAccount, - unpinAccount, - removeAccountFromFollowers, -} from 'mastodon/actions/accounts'; -import { initBlockModal } from 'mastodon/actions/blocks'; -import { mentionCompose, directCompose } from 'mastodon/actions/compose'; -import { - initDomainBlockModal, - unblockDomain, -} from 'mastodon/actions/domain_blocks'; import { openModal } from 'mastodon/actions/modal'; -import { initMuteModal } from 'mastodon/actions/mutes'; -import { initReport } from 'mastodon/actions/reports'; import { Avatar } from 'mastodon/components/avatar'; -import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge'; -import { CopyIconButton } from 'mastodon/components/copy_icon_button'; -import { - FollowersCounter, - FollowingCounter, - StatusesCounter, -} from 'mastodon/components/counters'; -import { Dropdown } from 'mastodon/components/dropdown_menu'; -import { FollowButton } from 'mastodon/components/follow_button'; import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; import { Icon } from 'mastodon/components/icon'; -import { IconButton } from 'mastodon/components/icon_button'; -import { ShortNumber } from 'mastodon/components/short_number'; import { AccountNote } from 'mastodon/features/account/components/account_note'; import { DomainPill } from 'mastodon/features/account/components/domain_pill'; import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container'; -import { useIdentity } from 'mastodon/identity_context'; import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state'; import type { Account } from 'mastodon/models/account'; -import type { MenuItem } from 'mastodon/models/dropdown_menu'; -import { - PERMISSION_MANAGE_USERS, - PERMISSION_MANAGE_FEDERATION, -} from 'mastodon/permissions'; import { getAccountHidden } from 'mastodon/selectors/accounts'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; +import { AccountBadges } from './badges'; +import { AccountButtons } from './buttons'; import { FamiliarFollowers } from './familiar_followers'; +import { AccountInfo } from './info'; +import { AccountLinks } from './links'; import { MemorialNote } from './memorial_note'; import { MovedNote } from './moved_note'; - -const messages = defineMessages({ - unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, - edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, - linkVerifiedOn: { - id: 'account.link_verified_on', - defaultMessage: 'Ownership of this link was checked on {date}', - }, - account_locked: { - id: 'account.locked_info', - defaultMessage: - 'This account privacy status is set to locked. The owner manually reviews who can follow them.', - }, - mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, - direct: { id: 'account.direct', defaultMessage: 'Privately mention @{name}' }, - unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, - block: { id: 'account.block', defaultMessage: 'Block @{name}' }, - mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, - report: { id: 'account.report', defaultMessage: 'Report @{name}' }, - share: { id: 'account.share', defaultMessage: "Share @{name}'s profile" }, - copy: { id: 'account.copy', defaultMessage: 'Copy link to profile' }, - media: { id: 'account.media', defaultMessage: 'Media' }, - blockDomain: { - id: 'account.block_domain', - defaultMessage: 'Block domain {domain}', - }, - unblockDomain: { - id: 'account.unblock_domain', - defaultMessage: 'Unblock domain {domain}', - }, - hideReblogs: { - id: 'account.hide_reblogs', - defaultMessage: 'Hide boosts from @{name}', - }, - showReblogs: { - id: 'account.show_reblogs', - defaultMessage: 'Show boosts from @{name}', - }, - enableNotifications: { - id: 'account.enable_notifications', - defaultMessage: 'Notify me when @{name} posts', - }, - disableNotifications: { - id: 'account.disable_notifications', - defaultMessage: 'Stop notifying me when @{name} posts', - }, - preferences: { - id: 'navigation_bar.preferences', - defaultMessage: 'Preferences', - }, - follow_requests: { - id: 'navigation_bar.follow_requests', - defaultMessage: 'Follow requests', - }, - favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' }, - lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, - followed_tags: { - id: 'navigation_bar.followed_tags', - defaultMessage: 'Followed hashtags', - }, - blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, - domain_blocks: { - id: 'navigation_bar.domain_blocks', - defaultMessage: 'Blocked domains', - }, - mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, - endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, - unendorse: { - id: 'account.unendorse', - defaultMessage: "Don't feature on profile", - }, - add_or_remove_from_list: { - id: 'account.add_or_remove_from_list', - defaultMessage: 'Add or Remove from lists', - }, - admin_account: { - id: 'status.admin_account', - defaultMessage: 'Open moderation interface for @{name}', - }, - admin_domain: { - id: 'status.admin_domain', - defaultMessage: 'Open moderation interface for {domain}', - }, - languages: { - id: 'account.languages', - defaultMessage: 'Change subscribed languages', - }, - openOriginalPage: { - id: 'account.open_original_page', - defaultMessage: 'Open original page', - }, - removeFromFollowers: { - id: 'account.remove_from_followers', - defaultMessage: 'Remove {name} from followers', - }, - confirmRemoveFromFollowersTitle: { - id: 'confirmations.remove_from_followers.title', - defaultMessage: 'Remove follower?', - }, - confirmRemoveFromFollowersMessage: { - id: 'confirmations.remove_from_followers.message', - defaultMessage: - '{name} will stop following you. Are you sure you want to proceed?', - }, - confirmRemoveFromFollowersButton: { - id: 'confirmations.remove_from_followers.confirm', - defaultMessage: 'Remove follower', - }, -}); +import { AccountTabs } from './tabs'; const titleFromAccount = (account: Account) => { const displayName = account.display_name; @@ -191,149 +49,12 @@ export const AccountHeader: React.FC<{ }> = ({ accountId, hideTabs }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const { signedIn, permissions } = useIdentity(); const account = useAppSelector((state) => state.accounts.get(accountId)); const relationship = useAppSelector((state) => state.relationships.get(accountId), ); const hidden = useAppSelector((state) => getAccountHidden(state, accountId)); - const handleBlock = useCallback(() => { - if (!account) { - return; - } - - if (relationship?.blocking) { - dispatch(unblockAccount(account.id)); - } else { - dispatch(initBlockModal(account)); - } - }, [dispatch, account, relationship]); - - const handleMention = useCallback(() => { - if (!account) { - return; - } - - dispatch(mentionCompose(account)); - }, [dispatch, account]); - - const handleDirect = useCallback(() => { - if (!account) { - return; - } - - dispatch(directCompose(account)); - }, [dispatch, account]); - - const handleReport = useCallback(() => { - if (!account) { - return; - } - - dispatch(initReport(account)); - }, [dispatch, account]); - - const handleReblogToggle = useCallback(() => { - if (!account) { - return; - } - - if (relationship?.showing_reblogs) { - dispatch(followAccount(account.id, { reblogs: false })); - } else { - dispatch(followAccount(account.id, { reblogs: true })); - } - }, [dispatch, account, relationship]); - - const handleNotifyToggle = useCallback(() => { - if (!account) { - return; - } - - if (relationship?.notifying) { - dispatch(followAccount(account.id, { notify: false })); - } else { - dispatch(followAccount(account.id, { notify: true })); - } - }, [dispatch, account, relationship]); - - const handleMute = useCallback(() => { - if (!account) { - return; - } - - if (relationship?.muting) { - dispatch(unmuteAccount(account.id)); - } else { - dispatch(initMuteModal(account)); - } - }, [dispatch, account, relationship]); - - const handleBlockDomain = useCallback(() => { - if (!account) { - return; - } - - dispatch(initDomainBlockModal(account)); - }, [dispatch, account]); - - const handleUnblockDomain = useCallback(() => { - if (!account) { - return; - } - - const domain = account.acct.split('@')[1]; - - if (!domain) { - return; - } - - dispatch(unblockDomain(domain)); - }, [dispatch, account]); - - const handleEndorseToggle = useCallback(() => { - if (!account) { - return; - } - - if (relationship?.endorsed) { - dispatch(unpinAccount(account.id)); - } else { - dispatch(pinAccount(account.id)); - } - }, [dispatch, account, relationship]); - - const handleAddToList = useCallback(() => { - if (!account) { - return; - } - - dispatch( - openModal({ - modalType: 'LIST_ADDER', - modalProps: { - accountId: account.id, - }, - }), - ); - }, [dispatch, account]); - - const handleChangeLanguages = useCallback(() => { - if (!account) { - return; - } - - dispatch( - openModal({ - modalType: 'SUBSCRIBED_LANGUAGES', - modalProps: { - accountId: account.id, - }, - }), - ); - }, [dispatch, account]); - const handleOpenAvatar = useCallback( (e: React.MouseEvent) => { if (e.button !== 0 || e.ctrlKey || e.metaKey) { @@ -359,410 +80,14 @@ export const AccountHeader: React.FC<{ [dispatch, account], ); - const handleShare = useCallback(() => { - if (!account) { - return; - } - - void navigator.share({ - url: account.url, - }); - }, [account]); - - const suspended = account?.suspended; - const isRemote = account?.acct !== account?.username; - const remoteDomain = isRemote ? account?.acct.split('@')[1] : null; - - const menuItems = useMemo(() => { - const arr: MenuItem[] = []; - - if (!account) { - return arr; - } - - if (signedIn && !account.suspended) { - arr.push({ - text: intl.formatMessage(messages.mention, { - name: account.username, - }), - action: handleMention, - }); - arr.push({ - text: intl.formatMessage(messages.direct, { - name: account.username, - }), - action: handleDirect, - }); - arr.push(null); - } - - if (isRemote) { - arr.push({ - text: intl.formatMessage(messages.openOriginalPage), - href: account.url, - }); - arr.push(null); - } - - if (signedIn) { - if (relationship?.following) { - if (!relationship.muting) { - if (relationship.showing_reblogs) { - arr.push({ - text: intl.formatMessage(messages.hideReblogs, { - name: account.username, - }), - action: handleReblogToggle, - }); - } else { - arr.push({ - text: intl.formatMessage(messages.showReblogs, { - name: account.username, - }), - action: handleReblogToggle, - }); - } - - arr.push({ - text: intl.formatMessage(messages.languages), - action: handleChangeLanguages, - }); - arr.push(null); - } - - arr.push({ - text: intl.formatMessage( - relationship.endorsed ? messages.unendorse : messages.endorse, - ), - action: handleEndorseToggle, - }); - arr.push({ - text: intl.formatMessage(messages.add_or_remove_from_list), - action: handleAddToList, - }); - arr.push(null); - } - - if (relationship?.followed_by) { - const handleRemoveFromFollowers = () => { - dispatch( - openModal({ - modalType: 'CONFIRM', - modalProps: { - title: intl.formatMessage( - messages.confirmRemoveFromFollowersTitle, - ), - message: intl.formatMessage( - messages.confirmRemoveFromFollowersMessage, - { name: {account.acct} }, - ), - confirm: intl.formatMessage( - messages.confirmRemoveFromFollowersButton, - ), - onConfirm: () => { - void dispatch(removeAccountFromFollowers({ accountId })); - }, - }, - }), - ); - }; - - arr.push({ - text: intl.formatMessage(messages.removeFromFollowers, { - name: account.username, - }), - action: handleRemoveFromFollowers, - dangerous: true, - }); - } - - if (relationship?.muting) { - arr.push({ - text: intl.formatMessage(messages.unmute, { - name: account.username, - }), - action: handleMute, - }); - } else { - arr.push({ - text: intl.formatMessage(messages.mute, { - name: account.username, - }), - action: handleMute, - dangerous: true, - }); - } - - if (relationship?.blocking) { - arr.push({ - text: intl.formatMessage(messages.unblock, { - name: account.username, - }), - action: handleBlock, - }); - } else { - arr.push({ - text: intl.formatMessage(messages.block, { - name: account.username, - }), - action: handleBlock, - dangerous: true, - }); - } - - if (!account.suspended) { - arr.push({ - text: intl.formatMessage(messages.report, { - name: account.username, - }), - action: handleReport, - dangerous: true, - }); - } - } - - if (signedIn && isRemote) { - arr.push(null); - - if (relationship?.domain_blocking) { - arr.push({ - text: intl.formatMessage(messages.unblockDomain, { - domain: remoteDomain, - }), - action: handleUnblockDomain, - }); - } else { - arr.push({ - text: intl.formatMessage(messages.blockDomain, { - domain: remoteDomain, - }), - action: handleBlockDomain, - dangerous: true, - }); - } - } - - if ( - (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || - (isRemote && - (permissions & PERMISSION_MANAGE_FEDERATION) === - PERMISSION_MANAGE_FEDERATION) - ) { - arr.push(null); - if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { - arr.push({ - text: intl.formatMessage(messages.admin_account, { - name: account.username, - }), - href: `/admin/accounts/${account.id}`, - }); - } - if ( - isRemote && - (permissions & PERMISSION_MANAGE_FEDERATION) === - PERMISSION_MANAGE_FEDERATION - ) { - arr.push({ - text: intl.formatMessage(messages.admin_domain, { - domain: remoteDomain, - }), - href: `/admin/instances/${remoteDomain}`, - }); - } - } - - return arr; - }, [ - dispatch, - accountId, - account, - relationship, - permissions, - isRemote, - remoteDomain, - intl, - signedIn, - handleAddToList, - handleBlock, - handleBlockDomain, - handleChangeLanguages, - handleDirect, - handleEndorseToggle, - handleMention, - handleMute, - handleReblogToggle, - handleReport, - handleUnblockDomain, - ]); - - const menu = accountId !== me && ( - - ); - if (!account) { return null; } - let actionBtn: React.ReactNode, - bellBtn: React.ReactNode, - lockedIcon: React.ReactNode, - shareBtn: React.ReactNode; - - const info: React.ReactNode[] = []; - - if (me !== account.id && relationship) { - if ( - relationship.followed_by && - (relationship.following || relationship.requested) - ) { - info.push( - - - , - ); - } else if (relationship.followed_by) { - info.push( - - - , - ); - } else if (relationship.requested_by) { - info.push( - - - , - ); - } - - if (relationship.blocking) { - info.push( - - - , - ); - } - - if (relationship.muting) { - info.push( - - - , - ); - } - - if (relationship.domain_blocking) { - info.push( - - - , - ); - } - } - - if (relationship?.requested || relationship?.following) { - bellBtn = ( - - ); - } - - if ('share' in navigator) { - shareBtn = ( - - ); - } else { - shareBtn = ( - - ); - } - - const isMovedAndUnfollowedAccount = account.moved && !relationship?.following; - - if (!isMovedAndUnfollowedAccount) { - actionBtn = ( - - ); - } - - if (account.locked) { - lockedIcon = ( - - ); - } - - const fields = account.fields; + const suspendedOrHidden = hidden || account.suspended; const isLocal = !account.acct.includes('@'); const username = account.acct.split('@')[0]; const domain = isLocal ? localDomain : account.acct.split('@')[1]; - const isIndexable = !account.noindex; - - const badges = []; - - if (account.bot) { - badges.push(); - } else if (account.group) { - badges.push(); - } - - account.roles.forEach((role) => { - badges.push( - {role.get('name')}} - domain={domain} - roleId={role.get('id')} - />, - ); - }); return (
@@ -776,15 +101,16 @@ export const AccountHeader: React.FC<{ inactive: !!account.moved, })} > - {!(suspended || hidden || account.moved) && - relationship?.requested_by && ( - - )} + {!suspendedOrHidden && !account.moved && relationship?.requested_by && ( + + )}
-
{info}
+ {me !== account.id && relationship && ( + + )} - {!(suspended || hidden) && ( + {!suspendedOrHidden && (
@@ -829,29 +153,37 @@ export const AccountHeader: React.FC<{ domain={domain ?? ''} isSelf={me === account.id} /> - {lockedIcon} + {account.locked && ( + + )}
- {badges.length > 0 && ( -
{badges}
- )} + - {account.id !== me && signedIn && !(suspended || hidden) && ( + {me && account.id !== me && !suspendedOrHidden && ( )} -
- {!hidden && actionBtn} - {!hidden && bellBtn} - {menu} -
+ - {!(suspended || hidden) && ( + {!suspendedOrHidden && (
- {account.id !== me && signedIn && ( + {me && account.id !== me && ( )} @@ -878,73 +210,26 @@ export const AccountHeader: React.FC<{ - +
-
- - - - - - - - - - - -
+
)} - {!(hideTabs || hidden) && ( -
- - - - - - - - - - - - -
- )} + {!hideTabs && !hidden && } {titleFromAccount(account)} diff --git a/app/javascript/mastodon/features/account_timeline/components/badges.tsx b/app/javascript/mastodon/features/account_timeline/components/badges.tsx new file mode 100644 index 00000000000..cc6c456e9c1 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/badges.tsx @@ -0,0 +1,43 @@ +import type { FC } from 'react'; + +import { AutomatedBadge, Badge, GroupBadge } from '@/mastodon/components/badge'; +import { useAccount } from '@/mastodon/hooks/useAccount'; +import { useAppSelector } from '@/mastodon/store'; + +export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { + const account = useAccount(accountId); + const localDomain = useAppSelector( + (state) => state.meta.get('domain') as string, + ); + const badges = []; + + if (!account) { + return null; + } + + if (account.bot) { + badges.push(); + } else if (account.group) { + badges.push(); + } + + const domain = account.acct.includes('@') + ? account.acct.split('@')[1] + : localDomain; + account.roles.forEach((role) => { + badges.push( + {role.get('name')}} + domain={domain} + roleId={role.get('id')} + />, + ); + }); + + if (!badges.length) { + return null; + } + + return
{badges}
; +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/buttons.tsx b/app/javascript/mastodon/features/account_timeline/components/buttons.tsx new file mode 100644 index 00000000000..c998d1472ce --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/buttons.tsx @@ -0,0 +1,134 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { followAccount } from '@/mastodon/actions/accounts'; +import { CopyIconButton } from '@/mastodon/components/copy_icon_button'; +import { FollowButton } from '@/mastodon/components/follow_button'; +import { IconButton } from '@/mastodon/components/icon_button'; +import { useAccount } from '@/mastodon/hooks/useAccount'; +import { getAccountHidden } from '@/mastodon/selectors/accounts'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; +import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; +import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react'; +import ShareIcon from '@/material-icons/400-24px/share.svg?react'; + +import { AccountMenu } from './menu'; + +const messages = defineMessages({ + enableNotifications: { + id: 'account.enable_notifications', + defaultMessage: 'Notify me when @{name} posts', + }, + disableNotifications: { + id: 'account.disable_notifications', + defaultMessage: 'Stop notifying me when @{name} posts', + }, + share: { id: 'account.share', defaultMessage: "Share @{name}'s profile" }, + copy: { id: 'account.copy', defaultMessage: 'Copy link to profile' }, +}); + +interface AccountButtonsProps { + accountId: string; + className?: string; + noShare?: boolean; +} + +export const AccountButtons: FC = ({ + accountId, + className, + noShare, +}) => { + const hidden = useAppSelector((state) => getAccountHidden(state, accountId)); + const me = useAppSelector((state) => state.meta.get('me') as string); + + return ( +
+ {!hidden && ( + + )} + {accountId !== me && } +
+ ); +}; + +const AccountButtonsOther: FC< + Pick +> = ({ accountId, noShare }) => { + const intl = useIntl(); + const account = useAccount(accountId); + const relationship = useAppSelector((state) => + state.relationships.get(accountId), + ); + + const dispatch = useAppDispatch(); + const handleNotifyToggle = useCallback(() => { + if (account) { + dispatch(followAccount(account.id, { notify: !relationship?.notifying })); + } + }, [dispatch, account, relationship]); + const accountUrl = account?.url; + const handleShare = useCallback(() => { + if (accountUrl) { + void navigator.share({ + url: accountUrl, + }); + } + }, [accountUrl]); + + if (!account) { + return null; + } + + const isMovedAndUnfollowedAccount = account.moved && !relationship?.following; + const isFollowing = relationship?.requested || relationship?.following; + + return ( + <> + {!isMovedAndUnfollowedAccount && ( + + )} + {isFollowing && ( + + )} + {!noShare && + ('share' in navigator ? ( + + ) : ( + + ))} + + ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/info.tsx b/app/javascript/mastodon/features/account_timeline/components/info.tsx new file mode 100644 index 00000000000..bb99999c41e --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/info.tsx @@ -0,0 +1,68 @@ +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import type { Relationship } from '@/mastodon/models/relationship'; + +export const AccountInfo: FC<{ relationship?: Relationship }> = ({ + relationship, +}) => { + if (!relationship) { + return null; + } + return ( +
+ {(relationship.followed_by || relationship.requested_by) && ( + + + + )} + {relationship.blocking && ( + + + + )} + {relationship.muting && ( + + + + )} + {relationship.domain_blocking && ( + + + + )} +
+ ); +}; + +const AccountInfoFollower: FC<{ relationship: Relationship }> = ({ + relationship, +}) => { + if ( + relationship.followed_by && + (relationship.following || relationship.requested) + ) { + return ( + + ); + } else if (relationship.followed_by) { + return ( + + ); + } else if (relationship.requested_by) { + return ( + + ); + } + return null; +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/links.tsx b/app/javascript/mastodon/features/account_timeline/components/links.tsx new file mode 100644 index 00000000000..2e056e4e577 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/links.tsx @@ -0,0 +1,58 @@ +import type { FC } from 'react'; + +import { useIntl } from 'react-intl'; + +import { NavLink } from 'react-router-dom'; + +import { + FollowersCounter, + FollowingCounter, + StatusesCounter, +} from '@/mastodon/components/counters'; +import { ShortNumber } from '@/mastodon/components/short_number'; +import { useAccount } from '@/mastodon/hooks/useAccount'; + +export const AccountLinks: FC<{ accountId: string }> = ({ accountId }) => { + const intl = useIntl(); + const account = useAccount(accountId); + + if (!account) { + return null; + } + + return ( +
+ + + + + + + + + + + +
+ ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/menu.tsx b/app/javascript/mastodon/features/account_timeline/components/menu.tsx new file mode 100644 index 00000000000..ce98c61f76a --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/menu.tsx @@ -0,0 +1,373 @@ +import { useMemo } from 'react'; +import type { FC } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { + blockAccount, + followAccount, + pinAccount, + unblockAccount, + unmuteAccount, + unpinAccount, +} from '@/mastodon/actions/accounts'; +import { removeAccountFromFollowers } from '@/mastodon/actions/accounts_typed'; +import { directCompose, mentionCompose } from '@/mastodon/actions/compose'; +import { + initDomainBlockModal, + unblockDomain, +} from '@/mastodon/actions/domain_blocks'; +import { openModal } from '@/mastodon/actions/modal'; +import { initMuteModal } from '@/mastodon/actions/mutes'; +import { initReport } from '@/mastodon/actions/reports'; +import { Dropdown } from '@/mastodon/components/dropdown_menu'; +import { useAccount } from '@/mastodon/hooks/useAccount'; +import { useIdentity } from '@/mastodon/identity_context'; +import type { MenuItem } from '@/mastodon/models/dropdown_menu'; +import { + PERMISSION_MANAGE_FEDERATION, + PERMISSION_MANAGE_USERS, +} from '@/mastodon/permissions'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; + +const messages = defineMessages({ + unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, + mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, + direct: { id: 'account.direct', defaultMessage: 'Privately mention @{name}' }, + unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, + block: { id: 'account.block', defaultMessage: 'Block @{name}' }, + mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, + report: { id: 'account.report', defaultMessage: 'Report @{name}' }, + blockDomain: { + id: 'account.block_domain', + defaultMessage: 'Block domain {domain}', + }, + unblockDomain: { + id: 'account.unblock_domain', + defaultMessage: 'Unblock domain {domain}', + }, + hideReblogs: { + id: 'account.hide_reblogs', + defaultMessage: 'Hide boosts from @{name}', + }, + showReblogs: { + id: 'account.show_reblogs', + defaultMessage: 'Show boosts from @{name}', + }, + endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, + unendorse: { + id: 'account.unendorse', + defaultMessage: "Don't feature on profile", + }, + add_or_remove_from_list: { + id: 'account.add_or_remove_from_list', + defaultMessage: 'Add or Remove from lists', + }, + admin_account: { + id: 'status.admin_account', + defaultMessage: 'Open moderation interface for @{name}', + }, + admin_domain: { + id: 'status.admin_domain', + defaultMessage: 'Open moderation interface for {domain}', + }, + languages: { + id: 'account.languages', + defaultMessage: 'Change subscribed languages', + }, + openOriginalPage: { + id: 'account.open_original_page', + defaultMessage: 'Open original page', + }, + removeFromFollowers: { + id: 'account.remove_from_followers', + defaultMessage: 'Remove {name} from followers', + }, + confirmRemoveFromFollowersTitle: { + id: 'confirmations.remove_from_followers.title', + defaultMessage: 'Remove follower?', + }, + confirmRemoveFromFollowersMessage: { + id: 'confirmations.remove_from_followers.message', + defaultMessage: + '{name} will stop following you. Are you sure you want to proceed?', + }, + confirmRemoveFromFollowersButton: { + id: 'confirmations.remove_from_followers.confirm', + defaultMessage: 'Remove follower', + }, +}); + +export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { + const intl = useIntl(); + const { signedIn, permissions } = useIdentity(); + + const account = useAccount(accountId); + const relationship = useAppSelector((state) => + state.relationships.get(accountId), + ); + + const dispatch = useAppDispatch(); + const menuItems = useMemo(() => { + const arr: MenuItem[] = []; + + if (!account) { + return arr; + } + + const isRemote = account.acct !== account.username; + + if (signedIn && !account.suspended) { + arr.push({ + text: intl.formatMessage(messages.mention, { + name: account.username, + }), + action: () => { + dispatch(mentionCompose(account)); + }, + }); + arr.push({ + text: intl.formatMessage(messages.direct, { + name: account.username, + }), + action: () => { + dispatch(directCompose(account)); + }, + }); + arr.push(null); + } + + if (isRemote) { + arr.push({ + text: intl.formatMessage(messages.openOriginalPage), + href: account.url, + }); + arr.push(null); + } + + if (!signedIn) { + return arr; + } + + if (relationship?.following) { + if (!relationship.muting) { + if (relationship.showing_reblogs) { + arr.push({ + text: intl.formatMessage(messages.hideReblogs, { + name: account.username, + }), + action: () => { + dispatch(followAccount(account.id, { reblogs: false })); + }, + }); + } else { + arr.push({ + text: intl.formatMessage(messages.showReblogs, { + name: account.username, + }), + action: () => { + dispatch(followAccount(account.id, { reblogs: true })); + }, + }); + } + + arr.push({ + text: intl.formatMessage(messages.languages), + action: () => { + dispatch( + openModal({ + modalType: 'SUBSCRIBED_LANGUAGES', + modalProps: { + accountId: account.id, + }, + }), + ); + }, + }); + arr.push(null); + } + + arr.push({ + text: intl.formatMessage( + relationship.endorsed ? messages.unendorse : messages.endorse, + ), + action: () => { + if (relationship.endorsed) { + dispatch(unpinAccount(account.id)); + } else { + dispatch(pinAccount(account.id)); + } + }, + }); + arr.push({ + text: intl.formatMessage(messages.add_or_remove_from_list), + action: () => { + dispatch( + openModal({ + modalType: 'LIST_ADDER', + modalProps: { + accountId: account.id, + }, + }), + ); + }, + }); + arr.push(null); + } + + if (relationship?.followed_by) { + const handleRemoveFromFollowers = () => { + dispatch( + openModal({ + modalType: 'CONFIRM', + modalProps: { + title: intl.formatMessage( + messages.confirmRemoveFromFollowersTitle, + ), + message: intl.formatMessage( + messages.confirmRemoveFromFollowersMessage, + { name: {account.acct} }, + ), + confirm: intl.formatMessage( + messages.confirmRemoveFromFollowersButton, + ), + onConfirm: () => { + void dispatch( + removeAccountFromFollowers({ accountId: account.id }), + ); + }, + }, + }), + ); + }; + + arr.push({ + text: intl.formatMessage(messages.removeFromFollowers, { + name: account.username, + }), + action: handleRemoveFromFollowers, + dangerous: true, + }); + } + + if (relationship?.muting) { + arr.push({ + text: intl.formatMessage(messages.unmute, { + name: account.username, + }), + action: () => { + dispatch(unmuteAccount(account.id)); + }, + }); + } else { + arr.push({ + text: intl.formatMessage(messages.mute, { + name: account.username, + }), + action: () => { + dispatch(initMuteModal(account)); + }, + dangerous: true, + }); + } + + if (relationship?.blocking) { + arr.push({ + text: intl.formatMessage(messages.unblock, { + name: account.username, + }), + action: () => { + dispatch(unblockAccount(account.id)); + }, + }); + } else { + arr.push({ + text: intl.formatMessage(messages.block, { + name: account.username, + }), + action: () => { + dispatch(blockAccount(account.id)); + }, + dangerous: true, + }); + } + + if (!account.suspended) { + arr.push({ + text: intl.formatMessage(messages.report, { + name: account.username, + }), + action: () => { + dispatch(initReport(account)); + }, + dangerous: true, + }); + } + + const remoteDomain = isRemote ? account.acct.split('@')[1] : null; + if (remoteDomain) { + arr.push(null); + + if (relationship?.domain_blocking) { + arr.push({ + text: intl.formatMessage(messages.unblockDomain, { + domain: remoteDomain, + }), + action: () => { + dispatch(unblockDomain(remoteDomain)); + }, + }); + } else { + arr.push({ + text: intl.formatMessage(messages.blockDomain, { + domain: remoteDomain, + }), + action: () => { + dispatch(initDomainBlockModal(account)); + }, + dangerous: true, + }); + } + } + + if ( + (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || + (isRemote && + (permissions & PERMISSION_MANAGE_FEDERATION) === + PERMISSION_MANAGE_FEDERATION) + ) { + arr.push(null); + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + arr.push({ + text: intl.formatMessage(messages.admin_account, { + name: account.username, + }), + href: `/admin/accounts/${account.id}`, + }); + } + if ( + isRemote && + (permissions & PERMISSION_MANAGE_FEDERATION) === + PERMISSION_MANAGE_FEDERATION + ) { + arr.push({ + text: intl.formatMessage(messages.admin_domain, { + domain: remoteDomain, + }), + href: `/admin/instances/${remoteDomain}`, + }); + } + } + + return arr; + }, [account, signedIn, permissions, intl, relationship, dispatch]); + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/tabs.tsx b/app/javascript/mastodon/features/account_timeline/components/tabs.tsx new file mode 100644 index 00000000000..c08de1390ec --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/tabs.tsx @@ -0,0 +1,27 @@ +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { NavLink } from 'react-router-dom'; + +export const AccountTabs: FC<{ acct: string }> = ({ acct }) => { + return ( +
+ + + + + + + + + + + + +
+ ); +}; diff --git a/app/javascript/mastodon/hooks/useAccount.ts b/app/javascript/mastodon/hooks/useAccount.ts new file mode 100644 index 00000000000..277fa16931d --- /dev/null +++ b/app/javascript/mastodon/hooks/useAccount.ts @@ -0,0 +1,26 @@ +import { useEffect } from 'react'; + +import { fetchAccount } from '../actions/accounts'; +import { createAppSelector, useAppDispatch, useAppSelector } from '../store'; + +export const accountSelector = createAppSelector( + [ + (state) => state.accounts, + (_, accountId: string | null | undefined) => accountId, + ], + (accounts, accountId) => (accountId ? accounts.get(accountId) : undefined), +); + +export function useAccount(accountId: string | null | undefined) { + const account = useAppSelector((state) => accountSelector(state, accountId)); + + const dispatch = useAppDispatch(); + const accountInStore = !!account; + useEffect(() => { + if (accountId && !accountInStore) { + dispatch(fetchAccount(accountId)); + } + }, [accountId, accountInStore, dispatch]); + + return account; +} From c09fbeb32fddad4ba131a438fa730cdf7cb5ed41 Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 15 Jan 2026 14:53:42 +0100 Subject: [PATCH 019/145] MiniCard and MiniCardList components (#37479) --- .../mastodon/components/mini_card/index.tsx | 33 +++ .../mastodon/components/mini_card/list.tsx | 208 ++++++++++++++++++ .../mini_card/mini_card.stories.tsx | 62 ++++++ .../components/mini_card/styles.module.css | 55 +++++ app/javascript/mastodon/locales/en.json | 1 + 5 files changed, 359 insertions(+) create mode 100644 app/javascript/mastodon/components/mini_card/index.tsx create mode 100644 app/javascript/mastodon/components/mini_card/list.tsx create mode 100644 app/javascript/mastodon/components/mini_card/mini_card.stories.tsx create mode 100644 app/javascript/mastodon/components/mini_card/styles.module.css diff --git a/app/javascript/mastodon/components/mini_card/index.tsx b/app/javascript/mastodon/components/mini_card/index.tsx new file mode 100644 index 00000000000..a619bb214a0 --- /dev/null +++ b/app/javascript/mastodon/components/mini_card/index.tsx @@ -0,0 +1,33 @@ +import type { FC, ReactNode } from 'react'; + +import classNames from 'classnames'; + +import classes from './styles.module.css'; + +export interface MiniCardProps { + label: ReactNode; + value: ReactNode; + className?: string; + hidden?: boolean; +} + +export const MiniCard: FC = ({ + label, + value, + className, + hidden, +}) => { + if (!label) { + return null; + } + + return ( +
+
{label}
+
{value}
+
+ ); +}; diff --git a/app/javascript/mastodon/components/mini_card/list.tsx b/app/javascript/mastodon/components/mini_card/list.tsx new file mode 100644 index 00000000000..b5b8fbc2c80 --- /dev/null +++ b/app/javascript/mastodon/components/mini_card/list.tsx @@ -0,0 +1,208 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import type { FC, Key, MouseEventHandler } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import { MiniCard } from '.'; +import type { MiniCardProps } from '.'; +import classes from './styles.module.css'; + +interface MiniCardListProps { + cards?: (Pick & { key?: Key })[]; + onOverflowClick?: MouseEventHandler; +} + +export const MiniCardList: FC = ({ + cards = [], + onOverflowClick, +}) => { + const { + wrapperRef, + listRef, + hiddenCount, + hasOverflow, + hiddenIndex, + maxWidth, + } = useOverflow(); + + return ( +
+
+ {cards.map((card, index) => ( +
+ +
+ ); +}; + +function useOverflow() { + const [hiddenIndex, setHiddenIndex] = useState(-1); + const [hiddenCount, setHiddenCount] = useState(0); + const [maxWidth, setMaxWidth] = useState('none'); + + // This is the item container element. + const listRef = useRef(null); + + // The main recalculation function. + const handleRecalculate = useCallback(() => { + const listEle = listRef.current; + if (!listEle) return; + + const reset = () => { + setHiddenIndex(-1); + setHiddenCount(0); + setMaxWidth('none'); + }; + + // Calculate the width via the parent element, minus the more button, minus the padding. + const maxWidth = + (listEle.parentElement?.offsetWidth ?? 0) - + (listEle.nextElementSibling?.scrollWidth ?? 0) - + 4; + if (maxWidth <= 0) { + reset(); + return; + } + + // Iterate through children until we exceed max width. + let visible = 0; + let index = 0; + let totalWidth = 0; + for (const child of listEle.children) { + if (child instanceof HTMLElement) { + const rightOffset = child.offsetLeft + child.offsetWidth; + if (rightOffset <= maxWidth) { + visible += 1; + totalWidth = rightOffset; + } else { + break; + } + } + index++; + } + + // All are visible, so remove max-width restriction. + if (visible === listEle.children.length) { + reset(); + return; + } + + // Set the width to avoid wrapping, and set hidden count. + setHiddenIndex(index); + setHiddenCount(listEle.children.length - visible); + setMaxWidth(totalWidth); + }, []); + + // Set up observers to watch for size and content changes. + const resizeObserverRef = useRef(null); + const mutationObserverRef = useRef(null); + + // Helper to get or create the resize observer. + const resizeObserver = useCallback(() => { + const observer = (resizeObserverRef.current ??= new ResizeObserver( + handleRecalculate, + )); + return observer; + }, [handleRecalculate]); + + // Iterate through children and observe them for size changes. + const handleChildrenChange = useCallback(() => { + const listEle = listRef.current; + const observer = resizeObserver(); + + if (listEle) { + for (const child of listEle.children) { + if (child instanceof HTMLElement) { + observer.observe(child); + } + } + } + handleRecalculate(); + }, [handleRecalculate, resizeObserver]); + + // Helper to get or create the mutation observer. + const mutationObserver = useCallback(() => { + const observer = (mutationObserverRef.current ??= new MutationObserver( + handleChildrenChange, + )); + return observer; + }, [handleChildrenChange]); + + // Set up observers. + const handleObserve = useCallback(() => { + if (wrapperRef.current) { + resizeObserver().observe(wrapperRef.current); + } + if (listRef.current) { + mutationObserver().observe(listRef.current, { childList: true }); + handleChildrenChange(); + } + }, [handleChildrenChange, mutationObserver, resizeObserver]); + + // Watch the wrapper for size changes, and recalculate when it resizes. + const wrapperRef = useRef(null); + const wrapperRefCallback = useCallback( + (node: HTMLElement | null) => { + if (node) { + wrapperRef.current = node; + handleObserve(); + } + }, + [handleObserve], + ); + + // If there are changes to the children, recalculate which are visible. + const listRefCallback = useCallback( + (node: HTMLElement | null) => { + if (node) { + listRef.current = node; + handleObserve(); + } + }, + [handleObserve], + ); + + useEffect(() => { + handleObserve(); + + return () => { + if (resizeObserverRef.current) { + resizeObserverRef.current.disconnect(); + resizeObserverRef.current = null; + } + if (mutationObserverRef.current) { + mutationObserverRef.current.disconnect(); + mutationObserverRef.current = null; + } + }; + }, [handleObserve]); + + return { + hiddenCount, + hasOverflow: hiddenCount > 0, + wrapperRef: wrapperRefCallback, + hiddenIndex, + maxWidth, + listRef: listRefCallback, + recalculate: handleRecalculate, + }; +} diff --git a/app/javascript/mastodon/components/mini_card/mini_card.stories.tsx b/app/javascript/mastodon/components/mini_card/mini_card.stories.tsx new file mode 100644 index 00000000000..ada76011b27 --- /dev/null +++ b/app/javascript/mastodon/components/mini_card/mini_card.stories.tsx @@ -0,0 +1,62 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { action } from 'storybook/actions'; + +import { MiniCardList } from './list'; + +const meta = { + title: 'Components/MiniCard', + component: MiniCardList, + args: { + cards: [ + { label: 'Pronouns', value: 'they/them' }, + { + label: 'Website', + value: bowie-the-db.meow, + }, + { + label: 'Free playlists', + value: soundcloud.com, + }, + { label: 'Location', value: 'Purris, France' }, + ], + onOverflowClick: action('Overflow clicked'), + }, + render(args) { + return ( +
+ +
+ ); + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const LongValue: Story = { + args: { + cards: [ + { + label: 'Username', + value: 'bowie-the-dj', + }, + { + label: 'Bio', + value: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + }, + ], + }, +}; diff --git a/app/javascript/mastodon/components/mini_card/styles.module.css b/app/javascript/mastodon/components/mini_card/styles.module.css new file mode 100644 index 00000000000..d912f1e5cf5 --- /dev/null +++ b/app/javascript/mastodon/components/mini_card/styles.module.css @@ -0,0 +1,55 @@ +.wrapper { + display: flex; + flex-wrap: nowrap; + justify-content: flex-start; + gap: 4px; +} + +.list { + min-width: 0; + display: flex; + gap: 4px; + overflow: hidden; + position: relative; +} + +.card, +.more { + border: 1px solid var(--color-border-primary); + padding: 8px; + border-radius: 8px; + flex-shrink: 0; +} + +.card { + max-width: 20vw; + overflow: hidden; +} + +.more { + color: var(--color-text-secondary); + font-weight: 600; + appearance: none; + background: none; +} + +.hidden { + display: none; +} + +.label { + color: var(--color-text-secondary); + margin-bottom: 2px; +} + +.value { + color: var(--color-text-primary); + font-weight: 600; +} + +.label, +.value { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index f1ab23570af..77dd9538871 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# new item} other {# new items}}", "loading_indicator.label": "Loading…", "media_gallery.hide": "Hide", + "minicard.more_items": "+ {count} more", "moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.", "mute_modal.hide_from_notifications": "Hide from notifications", "mute_modal.hide_options": "Hide options", From bc2f8a358f96a9540e6f39bb1c58273deb4545de Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 15 Jan 2026 17:04:27 +0100 Subject: [PATCH 020/145] Enable theming via new HTML element attributes (#37477) --- .storybook/preview-body.html | 2 +- app/helpers/application_helper.rb | 19 ++++++++ app/javascript/inline/theme-selection.js | 9 ++-- .../annual_report/announcement/index.tsx | 4 +- .../mastodon/features/annual_report/index.tsx | 2 +- .../mastodon/features/annual_report/modal.tsx | 7 +-- .../features/emoji/__tests__/emoji-test.js | 2 +- .../mastodon/features/emoji/emoji.js | 4 +- app/javascript/mastodon/utils/theme.ts | 12 ++--- .../styles/mastodon/theme/index.scss | 46 ++++++------------- app/views/layouts/application.html.haml | 2 +- app/views/wrapstodon/show.html.haml | 2 +- spec/requests/content_security_policy_spec.rb | 2 +- 13 files changed, 53 insertions(+), 60 deletions(-) diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index 7a92b6f95ff..7c078c0b3b7 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1076d9ced84..b23968e3731 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -89,6 +89,12 @@ module ApplicationHelper Rails.env.production? ? site_title : "#{site_title} (Dev)" end + def page_color_scheme + return content_for(:force_color_scheme) if content_for(:force_color_scheme) + + color_scheme + end + def label_for_scope(scope) safe_join [ tag.samp(scope, class: { 'scope-danger' => SessionActivation::DEFAULT_SCOPES.include?(scope.to_s) }), @@ -153,6 +159,19 @@ module ApplicationHelper tag.meta(content: content, property: property) end + def html_attributes + base = { + lang: I18n.locale, + class: html_classes, + 'data-contrast': contrast.parameterize, + 'data-color-scheme': page_color_scheme.parameterize, + } + + base[:'data-system-theme'] = 'true' if page_color_scheme == 'auto' + + base + end + def html_classes output = [] output << content_for(:html_classes) diff --git a/app/javascript/inline/theme-selection.js b/app/javascript/inline/theme-selection.js index b3a2b03163e..680fbb23ec2 100644 --- a/app/javascript/inline/theme-selection.js +++ b/app/javascript/inline/theme-selection.js @@ -1,16 +1,17 @@ (function (element) { - const {userTheme} = element.dataset; + const {colorScheme, contrast} = element.dataset; const colorSchemeMediaWatcher = window.matchMedia('(prefers-color-scheme: dark)'); const contrastMediaWatcher = window.matchMedia('(prefers-contrast: more)'); const updateColorScheme = () => { - const useDarkMode = userTheme === 'system' ? colorSchemeMediaWatcher.matches : userTheme !== 'mastodon-light'; - element.dataset.mode = useDarkMode ? 'dark' : 'light'; + const useDarkMode = colorScheme === 'auto' ? colorSchemeMediaWatcher.matches : colorScheme === 'dark'; + + element.dataset.colorScheme = useDarkMode ? 'dark' : 'light'; }; const updateContrast = () => { - const useHighContrast = userTheme === 'contrast' || contrastMediaWatcher.matches; + const useHighContrast = contrast === 'high' || contrastMediaWatcher.matches; element.dataset.contrast = useHighContrast ? 'high' : 'default'; } diff --git a/app/javascript/mastodon/features/annual_report/announcement/index.tsx b/app/javascript/mastodon/features/annual_report/announcement/index.tsx index 283e95f5940..d96b1092715 100644 --- a/app/javascript/mastodon/features/annual_report/announcement/index.tsx +++ b/app/javascript/mastodon/features/annual_report/announcement/index.tsx @@ -1,7 +1,5 @@ import { FormattedMessage } from 'react-intl'; -import classNames from 'classnames'; - import type { ApiAnnualReportState } from '@/mastodon/api/annual_report'; import { Button } from '@/mastodon/components/button'; @@ -19,7 +17,7 @@ export const AnnualReportAnnouncement: React.FC< AnnualReportAnnouncementProps > = ({ year, state, onRequestBuild, onOpen, onDismiss }) => { return ( -
+
= ({ const topHashtag = report.data.top_hashtags[0]; return ( -
+

Wrapstodon {report.year}

{account &&

@{account.acct}

} diff --git a/app/javascript/mastodon/features/annual_report/modal.tsx b/app/javascript/mastodon/features/annual_report/modal.tsx index 01d7c4bbdbf..d591954bbf3 100644 --- a/app/javascript/mastodon/features/annual_report/modal.tsx +++ b/app/javascript/mastodon/features/annual_report/modal.tsx @@ -60,11 +60,8 @@ const AnnualReportModal: React.FC<{ // default modal backdrop, preventing clicks to pass through. // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
{!showAnnouncement ? ( diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js index 35804de82ae..5d0683dace8 100644 --- a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js +++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js @@ -86,7 +86,7 @@ describe('emoji', () => { it('does an emoji containing ZWJ properly', () => { expect(emojify('💂‍♀️💂‍♂️')) - .toEqual('💂\u200D♀️💂\u200D♂️'); + .toEqual('💂‍♀️💂‍♂️'); }); it('keeps ordering as expected (issue fixed by PR 20677)', () => { diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index f8fa0ae1923..859665a5312 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -1,6 +1,6 @@ import Trie from 'substring-trie'; -import { getUserTheme, isDarkMode } from '@/mastodon/utils/theme'; +import { getIsSystemTheme, isDarkMode } from '@/mastodon/utils/theme'; import { assetHost } from 'mastodon/utils/config'; import { autoPlayGif } from '../../initial_state'; @@ -98,7 +98,7 @@ const emojifyTextNode = (node, customEmojis) => { const { filename, shortCode } = unicodeMapping[unicode_emoji]; const title = shortCode ? `:${shortCode}:` : ''; - const isSystemTheme = getUserTheme() === 'system'; + const isSystemTheme = getIsSystemTheme(); const theme = (isSystemTheme || !isDarkMode()) ? 'light' : 'dark'; diff --git a/app/javascript/mastodon/utils/theme.ts b/app/javascript/mastodon/utils/theme.ts index 921787a6c43..494ee3cb53b 100644 --- a/app/javascript/mastodon/utils/theme.ts +++ b/app/javascript/mastodon/utils/theme.ts @@ -1,11 +1,9 @@ -export function getUserTheme() { - const { userTheme } = document.documentElement.dataset; - return userTheme; +export function getIsSystemTheme() { + const { systemTheme } = document.documentElement.dataset; + return systemTheme === 'true'; } export function isDarkMode() { - const { userTheme } = document.documentElement.dataset; - return userTheme === 'system' - ? window.matchMedia('(prefers-color-scheme: dark)').matches - : userTheme !== 'mastodon-light'; + const { colorScheme } = document.documentElement.dataset; + return colorScheme === 'dark'; } diff --git a/app/javascript/styles/mastodon/theme/index.scss b/app/javascript/styles/mastodon/theme/index.scss index a907299887d..a84b8b80da2 100644 --- a/app/javascript/styles/mastodon/theme/index.scss +++ b/app/javascript/styles/mastodon/theme/index.scss @@ -5,49 +5,29 @@ html { @include base.palette; - - &:where([data-user-theme='system']) { - color-scheme: dark light; - - @media (prefers-color-scheme: dark) { - @include dark.tokens; - @include utils.invert-on-dark; - - @media (prefers-contrast: more) { - @include dark.contrast-overrides; - } - } - - @media (prefers-color-scheme: light) { - @include light.tokens; - @include utils.invert-on-light; - - @media (prefers-contrast: more) { - @include light.contrast-overrides; - } - } - } } -.theme-dark, -html:where( - :not([data-user-theme='mastodon-light'], [data-user-theme='system']) -) { +[data-color-scheme='dark'], +html:not([data-color-scheme]) { color-scheme: dark; @include dark.tokens; @include utils.invert-on-dark; + + &[data-contrast='high'], + [data-contrast='high'] & { + @include dark.contrast-overrides; + } } -html[data-user-theme='contrast'], -html[data-user-theme='contrast'] .theme-dark { - @include dark.contrast-overrides; -} - -.theme-light, -html:where([data-user-theme='mastodon-light']) { +[data-color-scheme='light'] { color-scheme: light; @include light.tokens; @include utils.invert-on-light; + + &[data-contrast='high'], + [data-contrast='high'] & { + @include light.contrast-overrides; + } } diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index dccb6035cae..0cbd51c85b0 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,5 +1,5 @@ !!! 5 -%html{ lang: I18n.locale, class: html_classes, 'data-user-theme': current_theme.parameterize, 'data-contrast': contrast.parameterize, 'data-mode': color_scheme.parameterize } +%html{ html_attributes } %head %meta{ charset: 'utf-8' }/ %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover' }/ diff --git a/app/views/wrapstodon/show.html.haml b/app/views/wrapstodon/show.html.haml index ed1e64d4665..8a20001ad55 100644 --- a/app/views/wrapstodon/show.html.haml +++ b/app/views/wrapstodon/show.html.haml @@ -13,7 +13,7 @@ = vite_typescript_tag 'wrapstodon.tsx', crossorigin: 'anonymous' -- content_for :html_classes, 'theme-dark' +- content_for :force_color_scheme, 'dark' #wrapstodon = render_wrapstodon_share_data @generated_annual_report diff --git a/spec/requests/content_security_policy_spec.rb b/spec/requests/content_security_policy_spec.rb index 0aa4494ef0b..c84c3802f24 100644 --- a/spec/requests/content_security_policy_spec.rb +++ b/spec/requests/content_security_policy_spec.rb @@ -32,7 +32,7 @@ RSpec.describe 'Content-Security-Policy' do img-src 'self' data: blob: #{local_domain} manifest-src 'self' #{local_domain} media-src 'self' data: #{local_domain} - script-src 'self' #{local_domain} 'wasm-unsafe-eval' 'sha256-Q/2Cjx8v06hAdOF8/DeBUpsmBcSj7sLN3I/WpTF8T8c=' + script-src 'self' #{local_domain} 'wasm-unsafe-eval' 'sha256-Z5KW83D+6/pygIQS3h9XDpF52xW3l3BHc7JL9tj3uMs=' style-src 'self' #{local_domain} 'nonce-ZbA+JmE7+bK8F5qvADZHuQ==' worker-src 'self' blob: #{local_domain} CSP From 93f5ed0fcecee13a59c2ca9e59c40b60574c0d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heath=20Dutton=F0=9F=95=B4=EF=B8=8F?= Date: Thu, 15 Jan 2026 12:02:17 -0500 Subject: [PATCH 021/145] Fix directory showing load more button when no more profiles exist (#37465) --- app/javascript/mastodon/actions/directory.ts | 13 +++++++++---- .../mastodon/features/directory/index.tsx | 5 ++++- app/javascript/mastodon/reducers/user_lists.js | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/actions/directory.ts b/app/javascript/mastodon/actions/directory.ts index 34ac309c66c..a50a377ffcf 100644 --- a/app/javascript/mastodon/actions/directory.ts +++ b/app/javascript/mastodon/actions/directory.ts @@ -6,15 +6,17 @@ import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; +const DIRECTORY_FETCH_LIMIT = 20; + export const fetchDirectory = createDataLoadingThunk( 'directory/fetch', async (params: Parameters[0]) => - apiGetDirectory(params), + apiGetDirectory(params, DIRECTORY_FETCH_LIMIT), (data, { dispatch }) => { dispatch(importFetchedAccounts(data)); dispatch(fetchRelationships(data.map((x) => x.id))); - return { accounts: data }; + return { accounts: data, isLast: data.length < DIRECTORY_FETCH_LIMIT }; }, ); @@ -26,12 +28,15 @@ export const expandDirectory = createDataLoadingThunk( 'items', ]) as ImmutableList; - return apiGetDirectory({ ...params, offset: loadedItems.size }, 20); + return apiGetDirectory( + { ...params, offset: loadedItems.size }, + DIRECTORY_FETCH_LIMIT, + ); }, (data, { dispatch }) => { dispatch(importFetchedAccounts(data)); dispatch(fetchRelationships(data.map((x) => x.id))); - return { accounts: data }; + return { accounts: data, isLast: data.length < DIRECTORY_FETCH_LIMIT }; }, ); diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx index 0fe140b4eb4..54317a6c76d 100644 --- a/app/javascript/mastodon/features/directory/index.tsx +++ b/app/javascript/mastodon/features/directory/index.tsx @@ -83,6 +83,9 @@ export const Directory: React.FC<{ (state) => state.user_lists.getIn(['directory', 'isLoading'], true) as boolean, ); + const hasMore = useAppSelector( + (state) => !!state.user_lists.getIn(['directory', 'next']), + ); useEffect(() => { void dispatch(fetchDirectory({ order, local })); @@ -182,7 +185,7 @@ export const Directory: React.FC<{
diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index 466bfe54d65..0393c06763a 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -204,9 +204,9 @@ export default function userLists(state = initialState, action) { else if (fetchFeaturedTags.rejected.match(action)) return state.setIn(['featured_tags', action.meta.arg.accountId, 'isLoading'], false); else if (fetchDirectory.fulfilled.match(action)) - return normalizeList(state, ['directory'], action.payload.accounts, undefined); + return normalizeList(state, ['directory'], action.payload.accounts, action.payload.isLast ? null : true); else if (expandDirectory.fulfilled.match(action)) - return appendToList(state, ['directory'], action.payload.accounts, undefined); + return appendToList(state, ['directory'], action.payload.accounts, action.payload.isLast ? null : true); else if (fetchDirectory.pending.match(action) || expandDirectory.pending.match(action)) return state.setIn(['directory', 'isLoading'], true); From 0d9fcb59a497902373c6293c8a193e46d307f2ff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:17:56 +0100 Subject: [PATCH 022/145] New Crowdin Translations (automated) (#37517) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 1 + app/javascript/mastodon/locales/da.json | 1 + app/javascript/mastodon/locales/de.json | 1 + app/javascript/mastodon/locales/el.json | 1 + app/javascript/mastodon/locales/en-GB.json | 1 + app/javascript/mastodon/locales/es-AR.json | 1 + app/javascript/mastodon/locales/es-MX.json | 1 + app/javascript/mastodon/locales/es.json | 1 + app/javascript/mastodon/locales/fo.json | 1 + app/javascript/mastodon/locales/hu.json | 1 + app/javascript/mastodon/locales/is.json | 1 + app/javascript/mastodon/locales/lt.json | 6 +++--- app/javascript/mastodon/locales/pt-PT.json | 1 + app/javascript/mastodon/locales/sq.json | 1 + app/javascript/mastodon/locales/vi.json | 1 + app/javascript/mastodon/locales/zh-CN.json | 1 + app/javascript/mastodon/locales/zh-TW.json | 1 + config/locales/cy.yml | 1 + config/locales/lt.yml | 2 +- 19 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index da4aa544093..caf49397770 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}", "loading_indicator.label": "Ідзе загрузка…", "media_gallery.hide": "Схаваць", + "minicard.more_items": "+ яшчэ {count}", "moved_to_account_banner.text": "Ваш уліковы запіс {disabledAccount} зараз адключаны, таму што Вы перайшлі на {movedToAccount}.", "mute_modal.hide_from_notifications": "Схаваць з апавяшчэнняў", "mute_modal.hide_options": "Схаваць опцыі", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 035037fccbb..a95303c0f2c 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# nyt element} other {# nye elementer}}", "loading_indicator.label": "Indlæser…", "media_gallery.hide": "Skjul", + "minicard.more_items": "+ {count} mere", "moved_to_account_banner.text": "Din konto {disabledAccount} er i øjeblikket deaktiveret, fordi du er flyttet til {movedToAccount}.", "mute_modal.hide_from_notifications": "Skjul fra notifikationer", "mute_modal.hide_options": "Skjul valgmuligheder", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 9e63460159b..f475865203d 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}", "loading_indicator.label": "Lädt …", "media_gallery.hide": "Ausblenden", + "minicard.more_items": "+ {count} weitere", "moved_to_account_banner.text": "Dein Konto {disabledAccount} ist derzeit deaktiviert, weil du zu {movedToAccount} umgezogen bist.", "mute_modal.hide_from_notifications": "Auch aus den Benachrichtigungen entfernen", "mute_modal.hide_options": "Optionen ausblenden", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index e31e6f3bde0..9903fed3b43 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# νέο στοιχείο} other {# νέα στοιχεία}}", "loading_indicator.label": "Φόρτωση…", "media_gallery.hide": "Απόκρυψη", + "minicard.more_items": "+ {count} ακόμα", "moved_to_account_banner.text": "Ο λογαριασμός σου {disabledAccount} είναι προσωρινά απενεργοποιημένος επειδή μεταφέρθηκες στον {movedToAccount}.", "mute_modal.hide_from_notifications": "Απόκρυψη από ειδοποιήσεις", "mute_modal.hide_options": "Απόκρυψη επιλογών", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 0863df66995..58912dd035d 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# new item} other {# new items}}", "loading_indicator.label": "Loading…", "media_gallery.hide": "Hide", + "minicard.more_items": "+ {count} more", "moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.", "mute_modal.hide_from_notifications": "Hide from notifications", "mute_modal.hide_options": "Hide options", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 670cc70055e..197dc7a92ae 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# elemento nuevo} other {# elementos nuevos}}", "loading_indicator.label": "Cargando…", "media_gallery.hide": "Ocultar", + "minicard.more_items": "y {count} más", "moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te mudaste a {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar en las notificaciones", "mute_modal.hide_options": "Ocultar opciones", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index db87e024631..48eaf1dc6fb 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}", "loading_indicator.label": "Cargando…", "media_gallery.hide": "Ocultar", + "minicard.more_items": "y {count} más", "moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar de las notificaciones", "mute_modal.hide_options": "Ocultar opciones", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 2b7d1905350..aa2b7588ddf 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}", "loading_indicator.label": "Cargando…", "media_gallery.hide": "Ocultar", + "minicard.more_items": "y {count} más", "moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar de las notificaciones", "mute_modal.hide_options": "Ocultar opciones", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 52ef3946c51..1f421614597 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# nýtt evni} other {# nýggj evni}}", "loading_indicator.label": "Innlesur…", "media_gallery.hide": "Fjal", + "minicard.more_items": "+ {count} meira", "moved_to_account_banner.text": "Konta tín {disabledAccount} er í løtuni óvirkin, tí tú flutti til {movedToAccount}.", "mute_modal.hide_from_notifications": "Fjal boð", "mute_modal.hide_options": "Fjal valmøguleikar", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 0f53d1b18eb..edcdd2b9f35 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# új elem} other {# új elem}}", "loading_indicator.label": "Betöltés…", "media_gallery.hide": "Elrejtés", + "minicard.more_items": "+ {count} további", "moved_to_account_banner.text": "A(z) {disabledAccount} fiókod jelenleg le van tiltva, mert átköltöztél ide: {movedToAccount}.", "mute_modal.hide_from_notifications": "Elrejtés az értesítések közül", "mute_modal.hide_options": "Beállítások elrejtése", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 0f255dfa977..e2efff0a47d 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# nýtt atriði} other {# ný atriði}}", "loading_indicator.label": "Hleð inn…", "media_gallery.hide": "Fela", + "minicard.more_items": "+ {count} í viðbót", "moved_to_account_banner.text": "Aðgangurinn þinn {disabledAccount} er óvirkur í augnablikinu vegna þess að þú fluttir þig yfir á {movedToAccount}.", "mute_modal.hide_from_notifications": "Fela úr tilkynningum", "mute_modal.hide_options": "Fela valkosti", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index cfbb355bb30..3b6957b0c26 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -69,7 +69,7 @@ "account.mute_short": "Nutildyti", "account.muted": "Nutildytas", "account.muting": "Užtildymas", - "account.mutual": "Jūs sekate vienas kitą", + "account.mutual": "Sekate vienas kitą", "account.no_bio": "Nėra pateikto aprašymo.", "account.open_original_page": "Atidaryti originalų puslapį", "account.posts": "Įrašai", @@ -685,7 +685,7 @@ "notifications.filter.follows": "Sekimai", "notifications.filter.mentions": "Paminėjimai", "notifications.filter.polls": "Balsavimo rezultatai", - "notifications.filter.statuses": "Naujinimai iš žmonių, kuriuos seki", + "notifications.filter.statuses": "Naujienos iš žmonių, kuriuos sekate", "notifications.grant_permission": "Suteikti leidimą.", "notifications.group": "{count} pranešimai", "notifications.mark_as_read": "Pažymėti kiekvieną pranešimą kaip perskaitytą", @@ -821,7 +821,7 @@ "report.thanks.title": "Nenori to matyti?", "report.thanks.title_actionable": "Ačiū, kad pranešei, mes tai išnagrinėsime.", "report.unfollow": "Nebesekti @{name}", - "report.unfollow_explanation": "Tu seki šią paskyrą. Jei nori nebematyti jų įrašų savo pagrindiniame sraute, nebesek jų.", + "report.unfollow_explanation": "Jūs sekate šią paskyrą. Kad nebematytumėte jų įrašų savo pagrindiniame sraute, nebesekite.", "report_notification.attached_statuses": "Pridėti {count, plural, one {{count} įrašas} few {{count} įrašai} many {{count} įrašo} other {{count} įrašų}}", "report_notification.categories.legal": "Teisinės", "report_notification.categories.legal_sentence": "nelegalus turinys", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 66710d1172b..72b97b22cb9 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# novo item} other {# novos itens}}", "loading_indicator.label": "A carregar…", "media_gallery.hide": "Esconder", + "minicard.more_items": "+ {count} mais", "moved_to_account_banner.text": "A tua conta {disabledAccount} está neste momento desativada porque migraste para {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar das notificações", "mute_modal.hide_options": "Ocultar opções", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 300966b2093..897cbd8722b 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -587,6 +587,7 @@ "load_pending": "{count, plural,one {# objekt i ri }other {# objekte të rinj }}", "loading_indicator.label": "Po ngarkohet…", "media_gallery.hide": "Fshihe", + "minicard.more_items": "+ {count} më tepër", "moved_to_account_banner.text": "Llogaria juaj {disabledAccount} aktualisht është e çaktivizuar, ngaqë kaluat te {movedToAccount}.", "mute_modal.hide_from_notifications": "Fshihe prej njoftimeve", "mute_modal.hide_options": "Fshihi mundësitë", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 4a9158e5b94..6b1138ec087 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, one {# tút mới} other {# tút mới}}", "loading_indicator.label": "Đang tải…", "media_gallery.hide": "Ẩn", + "minicard.more_items": "+ {count} khác", "moved_to_account_banner.text": "Tài khoản {disabledAccount} của bạn hiện không khả dụng vì bạn đã chuyển sang {movedToAccount}.", "mute_modal.hide_from_notifications": "Ẩn thông báo", "mute_modal.hide_options": "Ẩn tùy chọn", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index d69f490b5b4..92c0eaa3f40 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -589,6 +589,7 @@ "load_pending": "{count} 项", "loading_indicator.label": "加载中…", "media_gallery.hide": "隐藏", + "minicard.more_items": "+ 还有 {count} 项", "moved_to_account_banner.text": "你的账号 {disabledAccount} 已禁用,因为你已迁移到 {movedToAccount}。", "mute_modal.hide_from_notifications": "从通知中隐藏", "mute_modal.hide_options": "隐藏选项", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 78d191efa38..f6e4e3967b1 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -589,6 +589,7 @@ "load_pending": "{count, plural, other {# 個新項目}}", "loading_indicator.label": "正在載入...", "media_gallery.hide": "隱藏", + "minicard.more_items": "+ 其餘 {count} 個", "moved_to_account_banner.text": "您的帳號 {disabledAccount} 目前已停用,因為您已搬家至 {movedToAccount}。", "mute_modal.hide_from_notifications": "於推播通知中隱藏", "mute_modal.hide_options": "隱藏選項", diff --git a/config/locales/cy.yml b/config/locales/cy.yml index a74a51d31f0..1f38ea6eeea 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -904,6 +904,7 @@ cy: publish_statistics: Cyhoeddi ystadegau title: Darganfod trends: Tueddiadau + wrapstodon: Wrapstodon domain_blocks: all: I bawb disabled: I neb diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 33028404502..6666a4d7bd8 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -1033,7 +1033,7 @@ lt: merge_long: Išsaugoti esančius įrašus ir pridėti naujus overwrite: Perrašyti overwrite_long: Pakeisti senus įrašus naujais - preface: Gali importuoti duomenis, kuriuos eksportavai iš kito serverio, pavyzdžiui, sekamų arba blokuojamų žmonių sąrašą. + preface: Galite importuoti duomenis, kuriuos eksportavote iš kito serverio kaip sekamų arba blokuojamų žmonių sąrašą. success: Jūsų informacija sėkmingai įkelta ir bus apdorota kaip įmanoma greičiau types: blocking: Blokuojamų sąrašas From be00db4fa3164f89090f60676231ecb0792e2f77 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Fri, 16 Jan 2026 10:49:53 +0100 Subject: [PATCH 023/145] Use snowflake ids for collections and their items (#37514) --- ...53219_use_snowflake_ids_for_collections.rb | 25 +++++++++++++++++++ db/schema.rb | 6 ++--- 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20260115153219_use_snowflake_ids_for_collections.rb diff --git a/db/migrate/20260115153219_use_snowflake_ids_for_collections.rb b/db/migrate/20260115153219_use_snowflake_ids_for_collections.rb new file mode 100644 index 00000000000..25aad0732ed --- /dev/null +++ b/db/migrate/20260115153219_use_snowflake_ids_for_collections.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class UseSnowflakeIdsForCollections < ActiveRecord::Migration[8.0] + def up + safety_assured do + execute(<<~SQL.squish) + ALTER TABLE collections ALTER COLUMN id SET DEFAULT timestamp_id('collections'); + ALTER TABLE collection_items ALTER COLUMN id SET DEFAULT timestamp_id('collection_items'); + SQL + end + + Mastodon::Snowflake.ensure_id_sequences_exist + end + + def down + execute(<<~SQL.squish) + LOCK collections; + SELECT setval('collections_id_seq', (SELECT MAX(id) FROM collections)); + ALTER TABLE collections ALTER COLUMN id SET DEFAULT nextval('collections_id_seq'); + LOCK collection_items; + SELECT setval('collection_items_id_seq', (SELECT MAX(id) FROM collection_items)); + ALTER TABLE collection_items ALTER COLUMN id SET DEFAULT nextval('collection_items_id_seq'); + SQL + end +end diff --git a/db/schema.rb b/db/schema.rb index 78d7ef68261..8dc1e88fd17 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.0].define(version: 2025_12_17_091936) do +ActiveRecord::Schema[8.0].define(version: 2026_01_15_153219) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -352,7 +352,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_17_091936) do t.index ["reference_account_id"], name: "index_canonical_email_blocks_on_reference_account_id" end - create_table "collection_items", force: :cascade do |t| + create_table "collection_items", id: :bigint, default: -> { "timestamp_id('collection_items'::text)" }, force: :cascade do |t| t.bigint "collection_id", null: false t.bigint "account_id" t.integer "position", default: 1, null: false @@ -369,7 +369,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_17_091936) do t.index ["object_uri"], name: "index_collection_items_on_object_uri", unique: true, where: "(activity_uri IS NOT NULL)" end - create_table "collections", force: :cascade do |t| + create_table "collections", id: :bigint, default: -> { "timestamp_id('collections'::text)" }, force: :cascade do |t| t.bigint "account_id", null: false t.string "name", null: false t.text "description", null: false From e58084a85f6ccf64325c9af6f71ed7ef030fed65 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 16 Jan 2026 11:00:04 +0100 Subject: [PATCH 024/145] Update `theme_color_tags` helper for new theme infrastructure (#37519) --- app/helpers/theme_helper.rb | 15 +++++++-------- app/views/layouts/application.html.haml | 2 +- spec/helpers/theme_helper_spec.rb | 18 +++++++++--------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index 1d642056809..e7655414145 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -28,14 +28,17 @@ module ThemeHelper end end - def theme_color_tags(theme) - if theme == 'system' + def theme_color_tags(color_scheme) + case color_scheme + when 'auto' ''.html_safe.tap do |tags| tags << tag.meta(name: 'theme-color', content: Themes::THEME_COLORS[:dark], media: '(prefers-color-scheme: dark)') tags << tag.meta(name: 'theme-color', content: Themes::THEME_COLORS[:light], media: '(prefers-color-scheme: light)') end - else - tag.meta name: 'theme-color', content: theme_color_for(theme) + when 'light' + tag.meta name: 'theme-color', content: Themes::THEME_COLORS[:light] + when 'dark' + tag.meta name: 'theme-color', content: Themes::THEME_COLORS[:dark] end end @@ -65,8 +68,4 @@ module ThemeHelper Setting.custom_css&.then { |content| Digest::SHA256.hexdigest(content) } end end - - def theme_color_for(theme) - theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark] - end end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 0cbd51c85b0..15f1e961ed6 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -21,7 +21,7 @@ %link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/ %link{ rel: 'manifest', href: manifest_path(format: :json) }/ = javascript_inline_tag 'theme-selection.js' - = theme_color_tags current_theme + = theme_color_tags color_scheme %meta{ name: 'mobile-web-app-capable', content: 'yes' }/ %title= html_title diff --git a/spec/helpers/theme_helper_spec.rb b/spec/helpers/theme_helper_spec.rb index 9eefa01f902..9e8119a7429 100644 --- a/spec/helpers/theme_helper_spec.rb +++ b/spec/helpers/theme_helper_spec.rb @@ -49,12 +49,12 @@ RSpec.describe ThemeHelper do end describe 'theme_color_tags' do - let(:result) { helper.theme_color_tags(theme) } + let(:result) { helper.theme_color_tags(color_scheme) } context 'when using system theme' do - let(:theme) { 'system' } + let(:color_scheme) { 'auto' } - it 'returns the mastodon-light and default stylesheets with correct color schemes' do + it 'returns both color schemes with appropriate media queries' do expect(html_theme_colors.first.attributes.symbolize_keys) .to include( content: have_attributes(value: Themes::THEME_COLORS[:dark]), @@ -68,10 +68,10 @@ RSpec.describe ThemeHelper do end end - context 'when using mastodon-light theme' do - let(:theme) { 'mastodon-light' } + context 'when light color scheme' do + let(:color_scheme) { 'light' } - it 'returns the theme stylesheet without color scheme information' do + it 'returns the light color' do expect(html_theme_colors.first.attributes.symbolize_keys) .to include( content: have_attributes(value: Themes::THEME_COLORS[:light]) @@ -79,10 +79,10 @@ RSpec.describe ThemeHelper do end end - context 'when using other theme' do - let(:theme) { 'contrast' } + context 'when using dark color scheme' do + let(:color_scheme) { 'dark' } - it 'returns the theme stylesheet without color scheme information' do + it 'returns the dark color' do expect(html_theme_colors.first.attributes.symbolize_keys) .to include( content: have_attributes(value: Themes::THEME_COLORS[:dark]) From 918563704f5ff5eeb1efa31a412ac38f97781e9e Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 16 Jan 2026 11:28:50 +0100 Subject: [PATCH 025/145] =?UTF-8?q?Update=20the=20=E2=80=9Cembedded?= =?UTF-8?q?=E2=80=9D=20styling=20to=20the=20new=20theme=20infrastructure?= =?UTF-8?q?=20(#37520)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/embedded.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml index b802854ae72..d815b59140e 100644 --- a/app/views/layouts/embedded.html.haml +++ b/app/views/layouts/embedded.html.haml @@ -1,5 +1,5 @@ !!! 5 -%html{ lang: I18n.locale } +%html{ lang: I18n.locale, 'data-contrast': 'auto', 'data-color-scheme': 'light' } %head %meta{ charset: 'utf-8' }/ %meta{ name: 'robots', content: 'noindex' }/ @@ -11,10 +11,11 @@ - if storage_host? %link{ rel: 'dns-prefetch', href: storage_host }/ + = javascript_inline_tag 'theme-selection.js' = vite_client_tag = vite_react_refresh_tag = vite_polyfills_tag - = theme_style_tags 'mastodon-light' + = theme_style_tags 'system' = vite_preload_file_tag "mastodon/locales/#{I18n.locale}.json" = render_initial_state = vite_typescript_tag 'embed.tsx', integrity: true, crossorigin: 'anonymous' From 047338e684a3b8fb89080b3ecbbd2f7c831e9c16 Mon Sep 17 00:00:00 2001 From: Echo Date: Fri, 16 Jan 2026 13:44:49 +0100 Subject: [PATCH 026/145] Profile fields redesign (#37513) --- .../mastodon/components/mini_card/list.tsx | 36 ++++--- .../mini_card/mini_card.stories.tsx | 35 ++++--- .../components/mini_card/styles.module.css | 14 +-- .../features/account_timeline/common.ts | 5 + .../components/account_header.tsx | 33 +------ .../account_timeline/components/fields.tsx | 97 +++++++++++++++++++ .../components/fields_modal.tsx | 80 +++++++++++++++ .../account_timeline/components/links.tsx | 58 ----------- .../components/number_fields.tsx | 94 ++++++++++++++++++ .../components/redesign.module.scss | 47 +++++++++ .../features/ui/components/modal_root.jsx | 1 + app/javascript/mastodon/locales/en.json | 5 +- 12 files changed, 386 insertions(+), 119 deletions(-) create mode 100644 app/javascript/mastodon/features/account_timeline/common.ts create mode 100644 app/javascript/mastodon/features/account_timeline/components/fields.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx delete mode 100644 app/javascript/mastodon/features/account_timeline/components/links.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/number_fields.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/redesign.module.scss diff --git a/app/javascript/mastodon/components/mini_card/list.tsx b/app/javascript/mastodon/components/mini_card/list.tsx index b5b8fbc2c80..f775e70aac8 100644 --- a/app/javascript/mastodon/components/mini_card/list.tsx +++ b/app/javascript/mastodon/components/mini_card/list.tsx @@ -11,11 +11,13 @@ import classes from './styles.module.css'; interface MiniCardListProps { cards?: (Pick & { key?: Key })[]; + className?: string; onOverflowClick?: MouseEventHandler; } export const MiniCardList: FC = ({ cards = [], + className, onOverflowClick, }) => { const { @@ -27,29 +29,37 @@ export const MiniCardList: FC = ({ maxWidth, } = useOverflow(); + if (!cards.length) { + return null; + } + return ( -
+
{cards.map((card, index) => (
- + {cards.length > 1 && ( +
+ +
+ )}
); }; diff --git a/app/javascript/mastodon/components/mini_card/mini_card.stories.tsx b/app/javascript/mastodon/components/mini_card/mini_card.stories.tsx index ada76011b27..60534f05f6b 100644 --- a/app/javascript/mastodon/components/mini_card/mini_card.stories.tsx +++ b/app/javascript/mastodon/components/mini_card/mini_card.stories.tsx @@ -7,18 +7,6 @@ const meta = { title: 'Components/MiniCard', component: MiniCardList, args: { - cards: [ - { label: 'Pronouns', value: 'they/them' }, - { - label: 'Website', - value: bowie-the-db.meow, - }, - { - label: 'Free playlists', - value: soundcloud.com, - }, - { label: 'Location', value: 'Purris, France' }, - ], onOverflowClick: action('Overflow clicked'), }, render(args) { @@ -43,7 +31,22 @@ export default meta; type Story = StoryObj; -export const Default: Story = {}; +export const Default: Story = { + args: { + cards: [ + { label: 'Pronouns', value: 'they/them' }, + { + label: 'Website', + value: bowie-the-db.meow, + }, + { + label: 'Free playlists', + value: soundcloud.com, + }, + { label: 'Location', value: 'Purris, France' }, + ], + }, +}; export const LongValue: Story = { args: { @@ -60,3 +63,9 @@ export const LongValue: Story = { ], }, }; + +export const OneCard: Story = { + args: { + cards: [{ label: 'Pronouns', value: 'they/them' }], + }, +}; diff --git a/app/javascript/mastodon/components/mini_card/styles.module.css b/app/javascript/mastodon/components/mini_card/styles.module.css index d912f1e5cf5..642c08c5fac 100644 --- a/app/javascript/mastodon/components/mini_card/styles.module.css +++ b/app/javascript/mastodon/components/mini_card/styles.module.css @@ -6,7 +6,6 @@ } .list { - min-width: 0; display: flex; gap: 4px; overflow: hidden; @@ -21,16 +20,19 @@ flex-shrink: 0; } -.card { - max-width: 20vw; - overflow: hidden; -} - .more { color: var(--color-text-secondary); font-weight: 600; appearance: none; background: none; + aspect-ratio: 1; + height: 100%; + transition: all 300ms linear; +} + +.more:hover { + background-color: var(--color-bg-brand-softer); + color: var(--color-text-primary); } .hidden { diff --git a/app/javascript/mastodon/features/account_timeline/common.ts b/app/javascript/mastodon/features/account_timeline/common.ts new file mode 100644 index 00000000000..ee76c25bb74 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/common.ts @@ -0,0 +1,5 @@ +import { isClientFeatureEnabled } from '@/mastodon/utils/environment'; + +export function isRedesignEnabled() { + return isClientFeatureEnabled('profile_redesign'); +} diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index 4244d7f7026..8fc89d206b0 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -1,18 +1,16 @@ import { useCallback } from 'react'; -import { useIntl, FormattedMessage } from 'react-intl'; +import { useIntl } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; import { AccountBio } from '@/mastodon/components/account_bio'; -import { AccountFields } from '@/mastodon/components/account_fields'; import { DisplayName } from '@/mastodon/components/display_name'; import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import { openModal } from 'mastodon/actions/modal'; import { Avatar } from 'mastodon/components/avatar'; -import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; import { Icon } from 'mastodon/components/icon'; import { AccountNote } from 'mastodon/features/account/components/account_note'; import { DomainPill } from 'mastodon/features/account/components/domain_pill'; @@ -25,10 +23,11 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store'; import { AccountBadges } from './badges'; import { AccountButtons } from './buttons'; import { FamiliarFollowers } from './familiar_followers'; +import { AccountHeaderFields } from './fields'; import { AccountInfo } from './info'; -import { AccountLinks } from './links'; import { MemorialNote } from './memorial_note'; import { MovedNote } from './moved_note'; +import { AccountNumberFields } from './number_fields'; import { AccountTabs } from './tabs'; const titleFromAccount = (account: Account) => { @@ -192,32 +191,10 @@ export const AccountHeader: React.FC<{ className='account__header__content' /> -
-
-
- -
-
- -
-
- - -
+
- +
)}
diff --git a/app/javascript/mastodon/features/account_timeline/components/fields.tsx b/app/javascript/mastodon/features/account_timeline/components/fields.tsx new file mode 100644 index 00000000000..a73d92c1b69 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/fields.tsx @@ -0,0 +1,97 @@ +import { useCallback, useMemo } from 'react'; +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { openModal } from '@/mastodon/actions/modal'; +import { AccountFields } from '@/mastodon/components/account_fields'; +import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { FormattedDateWrapper } from '@/mastodon/components/formatted_date'; +import { MiniCardList } from '@/mastodon/components/mini_card/list'; +import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; +import { useAccount } from '@/mastodon/hooks/useAccount'; +import type { Account } from '@/mastodon/models/account'; +import { useAppDispatch } from '@/mastodon/store'; + +import { isRedesignEnabled } from '../common'; + +import classes from './redesign.module.scss'; + +export const AccountHeaderFields: FC<{ accountId: string }> = ({ + accountId, +}) => { + const account = useAccount(accountId); + + if (!account) { + return null; + } + + if (isRedesignEnabled()) { + return ; + } + + return ( +
+
+
+ +
+
+ +
+
+ + +
+ ); +}; + +const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => { + const htmlHandlers = useElementHandledLink(); + const cards = useMemo( + () => + account.fields.toArray().map(({ value_emojified, name_emojified }) => ({ + label: ( + + ), + value: ( + + ), + })), + [account.emojis, account.fields, htmlHandlers], + ); + + const dispatch = useAppDispatch(); + const handleOverflowClick = useCallback(() => { + dispatch( + openModal({ + modalType: 'ACCOUNT_FIELDS', + modalProps: { accountId: account.id }, + }), + ); + }, [account.id, dispatch]); + + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx b/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx new file mode 100644 index 00000000000..715f6097f45 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx @@ -0,0 +1,80 @@ +import type { FC } from 'react'; + +import { FormattedMessage, useIntl } from 'react-intl'; + +import { DisplayName } from '@/mastodon/components/display_name'; +import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; +import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { IconButton } from '@/mastodon/components/icon_button'; +import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; +import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; +import { useAccount } from '@/mastodon/hooks/useAccount'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; + +import classes from './redesign.module.scss'; + +export const AccountFieldsModal: FC<{ + accountId: string; + onClose: () => void; +}> = ({ accountId, onClose }) => { + const intl = useIntl(); + const account = useAccount(accountId); + const htmlHandlers = useElementHandledLink(); + + if (!account) { + return ( +
+ +
+ ); + } + + return ( +
+
+ + + , + }} + /> + +
+
+ +
+ {account.fields.map((field, index) => ( +
+ + +
+ ))} +
+
+
+
+ ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/links.tsx b/app/javascript/mastodon/features/account_timeline/components/links.tsx deleted file mode 100644 index 2e056e4e577..00000000000 --- a/app/javascript/mastodon/features/account_timeline/components/links.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { FC } from 'react'; - -import { useIntl } from 'react-intl'; - -import { NavLink } from 'react-router-dom'; - -import { - FollowersCounter, - FollowingCounter, - StatusesCounter, -} from '@/mastodon/components/counters'; -import { ShortNumber } from '@/mastodon/components/short_number'; -import { useAccount } from '@/mastodon/hooks/useAccount'; - -export const AccountLinks: FC<{ accountId: string }> = ({ accountId }) => { - const intl = useIntl(); - const account = useAccount(accountId); - - if (!account) { - return null; - } - - return ( -
- - - - - - - - - - - -
- ); -}; diff --git a/app/javascript/mastodon/features/account_timeline/components/number_fields.tsx b/app/javascript/mastodon/features/account_timeline/components/number_fields.tsx new file mode 100644 index 00000000000..20df6a0125f --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/number_fields.tsx @@ -0,0 +1,94 @@ +import type { FC } from 'react'; + +import { FormattedMessage, useIntl } from 'react-intl'; + +import classNames from 'classnames'; +import { NavLink } from 'react-router-dom'; + +import { + FollowersCounter, + FollowingCounter, + StatusesCounter, +} from '@/mastodon/components/counters'; +import { FormattedDateWrapper } from '@/mastodon/components/formatted_date'; +import { ShortNumber } from '@/mastodon/components/short_number'; +import { useAccount } from '@/mastodon/hooks/useAccount'; + +import { isRedesignEnabled } from '../common'; + +import classes from './redesign.module.scss'; + +export const AccountNumberFields: FC<{ accountId: string }> = ({ + accountId, +}) => { + const intl = useIntl(); + const account = useAccount(accountId); + + if (!account) { + return null; + } + + return ( +
+ {!isRedesignEnabled() && ( + + + + )} + + + + + + + + + + {isRedesignEnabled() && ( + + + + + ), + }} + /> + + )} +
+ ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss new file mode 100644 index 00000000000..dd09f199e56 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -0,0 +1,47 @@ +.fieldList { + margin-top: 16px; +} + +.fieldNumbersWrapper { + a { + font-weight: unset; + } +} + +.modalCloseButton { + padding: 8px; + border-radius: 50%; + border: 1px solid var(--color-border-primary); +} + +.modalTitle { + flex-grow: 1; + text-align: center; +} + +.modalFieldsList { + padding: 24px; +} + +.modalFieldItem { + &:not(:first-child) { + padding-top: 12px; + } + + &:not(:last-child)::after { + content: ''; + display: block; + border-bottom: 1px solid var(--color-border-primary); + margin-top: 12px; + } + + dt { + color: var(--color-text-secondary); + font-size: 13px; + } + + dd { + font-weight: 600; + font-size: 15px; + } +} diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 6f9b23042d7..0e718747d9c 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -85,6 +85,7 @@ export const MODAL_COMPONENTS = { 'IGNORE_NOTIFICATIONS': IgnoreNotificationsModal, 'ANNUAL_REPORT': AnnualReportModal, 'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }), + 'ACCOUNT_FIELDS': () => import('mastodon/features/account_timeline/components/fields_modal.tsx').then(module => ({ default: module.AccountFieldsModal })), }; export default class ModalRoot extends PureComponent { diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 77dd9538871..afccb038570 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Go to profile", "account.hide_reblogs": "Hide boosts from @{name}", "account.in_memoriam": "In Memoriam.", + "account.joined_long": "Joined on {date}", "account.joined_short": "Joined", "account.languages": "Change subscribed languages", "account.link_verified_on": "Ownership of this link was checked on {date}", @@ -90,6 +91,8 @@ "account.unmute": "Unmute @{name}", "account.unmute_notifications_short": "Unmute notifications", "account.unmute_short": "Unmute", + "account_fields_modal.close": "Close", + "account_fields_modal.title": "{name}'s info", "account_note.placeholder": "Click to add note", "admin.dashboard.daily_retention": "User retention rate by day after sign-up", "admin.dashboard.monthly_retention": "User retention rate by month after sign-up", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# new item} other {# new items}}", "loading_indicator.label": "Loading…", "media_gallery.hide": "Hide", - "minicard.more_items": "+ {count} more", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.", "mute_modal.hide_from_notifications": "Hide from notifications", "mute_modal.hide_options": "Hide options", From e715bb50758687ade8cb574f4eebd6ad3918fc7e Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 16 Jan 2026 14:57:48 +0100 Subject: [PATCH 027/145] Add high-contrast styles to common stylesheet (#37523) --- app/javascript/styles/contrast.scss | 1 - .../styles/mastodon/components.scss | 41 +++++++++++++++---- .../styles/mastodon/high-contrast.scss | 40 ------------------ spec/helpers/theme_helper_spec.rb | 2 +- 4 files changed, 34 insertions(+), 50 deletions(-) delete mode 100644 app/javascript/styles/mastodon/high-contrast.scss diff --git a/app/javascript/styles/contrast.scss b/app/javascript/styles/contrast.scss index 93bbfed0805..cc23627a15a 100644 --- a/app/javascript/styles/contrast.scss +++ b/app/javascript/styles/contrast.scss @@ -1,2 +1 @@ @use 'common'; -@use 'mastodon/high-contrast'; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 95460764030..6526b380df3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -39,8 +39,8 @@ color: var(--color-text-error); } - &:hover, - &:active { + &:not(:disabled):hover, + &:not(:disabled):active { text-decoration: underline; } @@ -1081,20 +1081,36 @@ body > [data-popper-placement] { } a { + --text-decoration-default: none; + --text-decoration-hover: underline; + + [data-contrast='high'] & { + --text-decoration-default: underline; + --text-decoration-hover: none; + } + color: var(--color-text-status-links); - text-decoration: none; + text-decoration: var(--text-decoration-default); unicode-bidi: isolate; - &:hover { - text-decoration: underline; + &:hover, + &:focus, + &:active { + text-decoration: var(--text-decoration-hover); } &.mention { - &:hover { - text-decoration: none; + text-decoration: none; + span { + text-decoration: var(--text-decoration-default); + } + + &:hover, + &:focus, + &:active { span { - text-decoration: underline; + text-decoration: var(--text-decoration-hover); } } } @@ -1356,6 +1372,15 @@ body > [data-popper-placement] { text-decoration: underline; } + [data-contrast='high'] & { + text-decoration: underline; + + &:hover, + &:active { + text-decoration: none; + } + } + .icon { width: 15px; height: 15px; diff --git a/app/javascript/styles/mastodon/high-contrast.scss b/app/javascript/styles/mastodon/high-contrast.scss deleted file mode 100644 index f55e7fae3b8..00000000000 --- a/app/javascript/styles/mastodon/high-contrast.scss +++ /dev/null @@ -1,40 +0,0 @@ -.status__content a, -.reply-indicator__content a, -.edit-indicator__content a, -.link-footer a, -.status__content__read-more-button, -.status__content__translate-button { - text-decoration: underline; - - &:hover, - &:focus, - &:active { - text-decoration: none; - } - - &.mention { - text-decoration: none; - - span { - text-decoration: underline; - } - - &:hover, - &:focus, - &:active { - span { - text-decoration: none; - } - } - } -} - -.link-button:disabled { - cursor: not-allowed; - - &:hover, - &:focus, - &:active { - text-decoration: none !important; - } -} diff --git a/spec/helpers/theme_helper_spec.rb b/spec/helpers/theme_helper_spec.rb index 9e8119a7429..35798cced6e 100644 --- a/spec/helpers/theme_helper_spec.rb +++ b/spec/helpers/theme_helper_spec.rb @@ -41,7 +41,7 @@ RSpec.describe ThemeHelper do it 'returns the theme stylesheet without color scheme information' do expect(html_links.first.attributes.symbolize_keys) .to include( - href: have_attributes(value: match(/contrast/)), + href: have_attributes(value: match(/default/)), media: have_attributes(value: 'all') ) end From 5b5b00055ec54f3949991662823b10c8a87c534a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:35:42 +0100 Subject: [PATCH 028/145] Update dependency pino to v10.2.0 (#37507) 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 c93adccaa13..8309b1e4f55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10669,8 +10669,8 @@ __metadata: linkType: hard "pino@npm:^10.0.0": - version: 10.1.1 - resolution: "pino@npm:10.1.1" + version: 10.2.0 + resolution: "pino@npm:10.2.0" dependencies: "@pinojs/redact": "npm:^0.4.0" atomic-sleep: "npm:^1.0.0" @@ -10685,7 +10685,7 @@ __metadata: thread-stream: "npm:^4.0.0" bin: pino: bin.js - checksum: 10c0/c0a78ae4209b81879e3b2c72abd523c6045412066d214b7577f4073b23369751bbb7a1c6abb691bdf347ac81fab5e9cad9d8db0612fe2e5a9607ef895651a1bb + checksum: 10c0/8f88a2e205508d47ef04d2a6ec26ec450abb4b344d2d998d2e24b9e624e1a1ef7184f260ca5be06bc3733aa1ad76704657e373b359c7b71489a11709227e26da languageName: node linkType: hard From a9bdf5eef9b8b6b1742f172e02128776d9bf9dfd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:35:51 +0100 Subject: [PATCH 029/145] Update dependency pg-connection-string to v2.10.0 (#37495) 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 8309b1e4f55..ccec52e40a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10507,9 +10507,9 @@ __metadata: linkType: hard "pg-connection-string@npm:^2.6.0, pg-connection-string@npm:^2.9.1": - version: 2.9.1 - resolution: "pg-connection-string@npm:2.9.1" - checksum: 10c0/9a646529bbc0843806fc5de98ce93735a4612b571f11867178a85665d11989a827e6fd157388ca0e34ec948098564fce836c178cfd499b9f0e8cd9972b8e2e5c + version: 2.10.0 + resolution: "pg-connection-string@npm:2.10.0" + checksum: 10c0/6639d3e12f66bc3cc5fa1e5794b4b22a4212e30424cbf001422e041c77c598ee0cd19395a9d79cb99a962da612b03c021e8148c3437cf01a29a18437bad193eb languageName: node linkType: hard From 5727b866b14d29d696f2ef20e92ad4c22ff43c1d Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 16 Jan 2026 15:44:58 +0100 Subject: [PATCH 030/145] Fix error pages not using the new theming infrastructure (#37524) --- app/javascript/entrypoints/theme-selection.ts | 1 + app/views/layouts/error.html.haml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 app/javascript/entrypoints/theme-selection.ts diff --git a/app/javascript/entrypoints/theme-selection.ts b/app/javascript/entrypoints/theme-selection.ts new file mode 100644 index 00000000000..76e46e15f19 --- /dev/null +++ b/app/javascript/entrypoints/theme-selection.ts @@ -0,0 +1 @@ +import '../inline/theme-selection'; diff --git a/app/views/layouts/error.html.haml b/app/views/layouts/error.html.haml index f85a671a531..03cb93dba7b 100644 --- a/app/views/layouts/error.html.haml +++ b/app/views/layouts/error.html.haml @@ -1,5 +1,5 @@ !!! -%html{ lang: I18n.locale } +%html{ lang: I18n.locale, 'data-contrast': 'auto', 'data-color-scheme': 'auto' } %head %meta{ 'content' => 'text/html; charset=UTF-8', 'http-equiv' => 'Content-Type' }/ %meta{ charset: 'utf-8' }/ @@ -8,6 +8,7 @@ = vite_client_tag = vite_react_refresh_tag = vite_polyfills_tag + = vite_typescript_tag 'theme-selection.ts', crossorigin: 'anonymous', blocking: 'render' = theme_style_tags Setting.default_settings['theme'] = vite_typescript_tag 'error.ts', crossorigin: 'anonymous' %body.error From e21cb9f0d08778000af4ae291e41fad633dff292 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:43:36 +0100 Subject: [PATCH 031/145] Update dependency pg to v8.17.1 (#37494) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/yarn.lock b/yarn.lock index ccec52e40a9..7cfdb5e4d30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10499,14 +10499,14 @@ __metadata: languageName: node linkType: hard -"pg-cloudflare@npm:^1.2.7": - version: 1.2.7 - resolution: "pg-cloudflare@npm:1.2.7" - checksum: 10c0/8a52713dbdecc9d389dc4e65e3b7ede2e199ec3715f7491ee80a15db171f2d75677a102e9c2cef0cb91a2f310e91f976eaec0dd6ef5d8bf357de0b948f9d9431 +"pg-cloudflare@npm:^1.3.0": + version: 1.3.0 + resolution: "pg-cloudflare@npm:1.3.0" + checksum: 10c0/b0866c88af8e54c7b3ed510719d92df37714b3af5e3a3a10d9f761fcec99483e222f5b78a1f2de590368127648087c45c01aaf66fadbe46edb25673eedc4f8fc languageName: node linkType: hard -"pg-connection-string@npm:^2.6.0, pg-connection-string@npm:^2.9.1": +"pg-connection-string@npm:^2.10.0, pg-connection-string@npm:^2.6.0": version: 2.10.0 resolution: "pg-connection-string@npm:2.10.0" checksum: 10c0/6639d3e12f66bc3cc5fa1e5794b4b22a4212e30424cbf001422e041c77c598ee0cd19395a9d79cb99a962da612b03c021e8148c3437cf01a29a18437bad193eb @@ -10520,19 +10520,19 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:^3.10.1": - version: 3.10.1 - resolution: "pg-pool@npm:3.10.1" +"pg-pool@npm:^3.11.0": + version: 3.11.0 + resolution: "pg-pool@npm:3.11.0" peerDependencies: pg: ">=8.0" - checksum: 10c0/a00916b7df64226cc597fe769e3a757ff9b11562dc87ce5b0a54101a18c1fe282daaa2accaf27221e81e1e4cdf4da6a33dab09614734d32904d6c4e11c44a079 + checksum: 10c0/4b104b48a47257a0edad0c62e5ea1908b72cb79386270264b452e69895e9e4c589d00cdbf6e46d4e9c05bc7e7d191656b66814b5282d65f33b12648a21df3c7f languageName: node linkType: hard -"pg-protocol@npm:*, pg-protocol@npm:^1.10.3": - version: 1.10.3 - resolution: "pg-protocol@npm:1.10.3" - checksum: 10c0/f7ef54708c93ee6d271e37678296fc5097e4337fca91a88a3d99359b78633dbdbf6e983f0adb34b7cdd261b7ec7266deb20c3233bf3dfdb498b3e1098e8750b9 +"pg-protocol@npm:*, pg-protocol@npm:^1.11.0": + version: 1.11.0 + resolution: "pg-protocol@npm:1.11.0" + checksum: 10c0/93e83581781418c9173eba4e4545f73392cfe66b78dd1d3624d7339fbd37e7f4abebaf2615e68e0701a9bf0edf5b81a4ad533836f388f775fe25fa24a691c464 languageName: node linkType: hard @@ -10550,13 +10550,13 @@ __metadata: linkType: hard "pg@npm:^8.5.0": - version: 8.16.3 - resolution: "pg@npm:8.16.3" + version: 8.17.1 + resolution: "pg@npm:8.17.1" dependencies: - pg-cloudflare: "npm:^1.2.7" - pg-connection-string: "npm:^2.9.1" - pg-pool: "npm:^3.10.1" - pg-protocol: "npm:^1.10.3" + pg-cloudflare: "npm:^1.3.0" + pg-connection-string: "npm:^2.10.0" + pg-pool: "npm:^3.11.0" + pg-protocol: "npm:^1.11.0" pg-types: "npm:2.2.0" pgpass: "npm:1.0.5" peerDependencies: @@ -10567,7 +10567,7 @@ __metadata: peerDependenciesMeta: pg-native: optional: true - checksum: 10c0/a6a407ff0efb7599760d72ffdcda47a74c34c0fd71d896623caac45cf2cfb0f49a10973cce23110f182b9810639a1e9f6904454d7358c7001574ee0ffdcbce2a + checksum: 10c0/39a92391adfc73f793d195b4062bc2d21aa3537073e3973f3979d72901d92a59e129f31f42577ff916038a6c3f9fe423b6024717529609ae8548fda21248cfe7 languageName: node linkType: hard From cc3c7ba532f75617505bb41297651a5c14f71bdd Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 16 Jan 2026 16:55:16 +0100 Subject: [PATCH 032/145] Fix `system` theme being included twice (#37526) --- app/helpers/theme_helper.rb | 12 ++++-------- spec/helpers/theme_helper_spec.rb | 14 +++----------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index e7655414145..f651a495ffe 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -18,14 +18,10 @@ module ThemeHelper end def theme_style_tags(theme) - if theme == 'system' - ''.html_safe.tap do |tags| - tags << vite_stylesheet_tag('themes/mastodon-light', type: :virtual, media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous') - tags << vite_stylesheet_tag('themes/default', type: :virtual, media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous') - end - else - vite_stylesheet_tag "themes/#{theme}", type: :virtual, media: 'all', crossorigin: 'anonymous' - end + # TODO: get rid of that when we retire the themes and perform the settings migration + theme = 'default' if %w(mastodon-light contrast system).include?(theme) + + vite_stylesheet_tag "themes/#{theme}", type: :virtual, media: 'all', crossorigin: 'anonymous' end def theme_color_tags(color_scheme) diff --git a/spec/helpers/theme_helper_spec.rb b/spec/helpers/theme_helper_spec.rb index 35798cced6e..a4028857114 100644 --- a/spec/helpers/theme_helper_spec.rb +++ b/spec/helpers/theme_helper_spec.rb @@ -9,17 +9,10 @@ RSpec.describe ThemeHelper do context 'when using "system" theme' do let(:theme) { 'system' } - it 'returns the mastodon-light and application stylesheets with correct color schemes' do + it 'returns the default theme' do expect(html_links.first.attributes.symbolize_keys) .to include( - # This is now identical to the default theme & will be unified very soon - href: have_attributes(value: match(/default/)), - media: have_attributes(value: 'not all and (prefers-color-scheme: dark)') - ) - expect(html_links.last.attributes.symbolize_keys) - .to include( - href: have_attributes(value: match(/default/)), - media: have_attributes(value: '(prefers-color-scheme: dark)') + href: have_attributes(value: match(/default/)) ) end end @@ -41,8 +34,7 @@ RSpec.describe ThemeHelper do it 'returns the theme stylesheet without color scheme information' do expect(html_links.first.attributes.symbolize_keys) .to include( - href: have_attributes(value: match(/default/)), - media: have_attributes(value: 'all') + href: have_attributes(value: match(/default/)) ) end end From f05e76e591781149ee1aeb68c93a5574cbc954e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:22:35 +0100 Subject: [PATCH 033/145] Update dependency postcss-preset-env to v11.1.1 (#37508) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7cfdb5e4d30..82ce70acfd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1386,9 +1386,9 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-alpha-function@npm:^2.0.0": - version: 2.0.0 - resolution: "@csstools/postcss-alpha-function@npm:2.0.0" +"@csstools/postcss-alpha-function@npm:^2.0.1": + version: 2.0.1 + resolution: "@csstools/postcss-alpha-function@npm:2.0.1" dependencies: "@csstools/css-color-parser": "npm:^4.0.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" @@ -1397,7 +1397,7 @@ __metadata: "@csstools/utilities": "npm:^3.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/b267a4d48c132a2e0a9160a17811bf6bda09e4ff4b393d6d7643cdd8c9335a2db37d956abe8960545139cf503ed5c1c57e433456a19d7361a0f206b96291b7f7 + checksum: 10c0/7e38e245eb4a4d76dcaafd9ee1f6d1cdfc8fceabf835dd1615fbb9fb0944224d29c8d601e55db4b3ba1dd37a541c8568fd130106171ca11b012eb512ff1ec7a9 languageName: node linkType: hard @@ -1695,6 +1695,18 @@ __metadata: languageName: node linkType: hard +"@csstools/postcss-mixins@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-mixins@npm:1.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10c0/3160f87f8c307ccdb97fe9ef3507c36e59bec11c0cfa7e486fbe7b0df96cb41b071d7e475d8ef52974bbcc4bf8a2243d5c2c849c0ba8a171aa384ba4b6c0b768 + languageName: node + linkType: hard + "@csstools/postcss-nested-calc@npm:^5.0.0": version: 5.0.0 resolution: "@csstools/postcss-nested-calc@npm:5.0.0" @@ -6341,10 +6353,10 @@ __metadata: languageName: node linkType: hard -"cssdb@npm:^8.6.0": - version: 8.6.0 - resolution: "cssdb@npm:8.6.0" - checksum: 10c0/4bb7b77ba24902e8d481e9514ec0be56e205186a2b7d9f5027fedfe718952c559c62acfd2859f92869f8090da7c2170f83d68170db5058a6ba8d9d5e8ded3b3e +"cssdb@npm:^8.7.0": + version: 8.7.0 + resolution: "cssdb@npm:8.7.0" + checksum: 10c0/8db722877a68732d378e6f734dcf343f5d62670a1df630a74f5ae4a4c1ec482223730128825c5bcd99307fcd2128160b3705a6b1508d446e80e6ab2eebc7e8bc languageName: node linkType: hard @@ -11056,10 +11068,10 @@ __metadata: linkType: hard "postcss-preset-env@npm:^11.0.0": - version: 11.0.1 - resolution: "postcss-preset-env@npm:11.0.1" + version: 11.1.1 + resolution: "postcss-preset-env@npm:11.1.1" dependencies: - "@csstools/postcss-alpha-function": "npm:^2.0.0" + "@csstools/postcss-alpha-function": "npm:^2.0.1" "@csstools/postcss-cascade-layers": "npm:^6.0.0" "@csstools/postcss-color-function": "npm:^5.0.0" "@csstools/postcss-color-function-display-p3-linear": "npm:^2.0.0" @@ -11083,6 +11095,7 @@ __metadata: "@csstools/postcss-logical-viewport-units": "npm:^4.0.0" "@csstools/postcss-media-minmax": "npm:^3.0.0" "@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.0" "@csstools/postcss-oklab-function": "npm:^5.0.0" @@ -11104,7 +11117,7 @@ __metadata: css-blank-pseudo: "npm:^8.0.1" css-has-pseudo: "npm:^8.0.0" css-prefers-color-scheme: "npm:^11.0.0" - cssdb: "npm:^8.6.0" + cssdb: "npm:^8.7.0" postcss-attribute-case-insensitive: "npm:^8.0.0" postcss-clamp: "npm:^4.1.0" postcss-color-functional-notation: "npm:^8.0.0" @@ -11132,7 +11145,7 @@ __metadata: postcss-selector-not: "npm:^9.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/f4f3a01b1e20fd784c9e4233ed84718293adbf040db78f6af6f703fce0c320c921bd4131657df01099e37bd7082a815b9387ca92c4406dccd4808ceb0c7ab9ca + checksum: 10c0/ed9e0b07c82368defe1ab7f776e8b163610c9db3a79e36cf95291f65959d912261aa8c4b94734bba047fe875fcf3eb6e0dd71dec93640dfd94578cba33833c5a languageName: node linkType: hard From 4339f0ea719dd54d550c79f00a5112c34b753356 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:22:42 +0100 Subject: [PATCH 034/145] Update dependency ioredis to v5.9.2 (#37510) 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 82ce70acfd9..f37750a4cc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8563,8 +8563,8 @@ __metadata: linkType: hard "ioredis@npm:^5.3.2": - version: 5.9.1 - resolution: "ioredis@npm:5.9.1" + version: 5.9.2 + resolution: "ioredis@npm:5.9.2" dependencies: "@ioredis/commands": "npm:1.5.0" cluster-key-slot: "npm:^1.1.0" @@ -8575,7 +8575,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10c0/0993372ac380332d487e329882f247254a77dbf2d6f746e67c2211b50dda8067c0bce16c120b77ffe1eb693766dc4f36fac8cf5679231ab0c27671e34ef13449 + checksum: 10c0/9732a3adab56c63a0e76bd1a1c891e4201448c44ab31e7e9aee8e83005be07597a4969199593c0d59642fb3fcd64ce00e9a8fad6c92d80e648df20b4b2b184ce languageName: node linkType: hard From fd78aa9eab2d5dde2481a398f9c3127bc7adcde6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:23:01 +0100 Subject: [PATCH 035/145] Update dependency haml_lint to v0.69.0 (#37531) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f920acecbf8..3ee1f77aa0b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -282,7 +282,7 @@ GEM rake (>= 13) googleapis-common-protos-types (1.22.0) google-protobuf (~> 4.26) - haml (7.1.0) + haml (7.2.0) temple (>= 0.8.2) thor tilt @@ -291,7 +291,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.68.0) + haml_lint (0.69.0) haml (>= 5.0) parallel (~> 1.10) rainbow @@ -589,7 +589,7 @@ GEM ox (2.14.23) bigdecimal (>= 3.0) parallel (1.27.0) - parser (3.3.10.0) + parser (3.3.10.1) ast (~> 2.4.1) racc parslet (2.0.0) @@ -613,7 +613,7 @@ GEM net-smtp premailer (~> 1.7, >= 1.7.9) prettyprint (0.2.0) - prism (1.7.0) + prism (1.8.0) prometheus_exporter (2.3.1) webrick propshaft (1.3.1) @@ -863,7 +863,7 @@ GEM climate_control test-prof (1.5.0) thor (1.5.0) - tilt (2.6.1) + tilt (2.7.0) timeout (0.6.0) tpm-key_attestation (0.14.1) bindata (~> 2.4) From e4c6130f778c2d9c070feccbbfbcb7db275a13dc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:40:47 +0100 Subject: [PATCH 036/145] New Crowdin Translations (automated) (#37532) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 5 ++- app/javascript/mastodon/locales/cs.json | 4 +++ app/javascript/mastodon/locales/da.json | 5 ++- app/javascript/mastodon/locales/de.json | 5 ++- app/javascript/mastodon/locales/el.json | 5 ++- app/javascript/mastodon/locales/en-GB.json | 5 ++- app/javascript/mastodon/locales/es-AR.json | 5 ++- app/javascript/mastodon/locales/es-MX.json | 5 ++- app/javascript/mastodon/locales/es.json | 1 - app/javascript/mastodon/locales/fi.json | 4 +++ app/javascript/mastodon/locales/fo.json | 5 ++- app/javascript/mastodon/locales/fr-CA.json | 10 ++++-- app/javascript/mastodon/locales/fr.json | 18 ++++++---- app/javascript/mastodon/locales/ga.json | 4 +++ app/javascript/mastodon/locales/gl.json | 6 +++- app/javascript/mastodon/locales/he.json | 4 +++ app/javascript/mastodon/locales/hu.json | 5 ++- app/javascript/mastodon/locales/is.json | 5 ++- app/javascript/mastodon/locales/it.json | 4 +++ app/javascript/mastodon/locales/nn.json | 4 +++ app/javascript/mastodon/locales/pa.json | 17 +++++++++ app/javascript/mastodon/locales/pt-PT.json | 5 ++- app/javascript/mastodon/locales/sl.json | 31 ++++++++++++++++ app/javascript/mastodon/locales/sq.json | 5 ++- app/javascript/mastodon/locales/vi.json | 5 ++- app/javascript/mastodon/locales/zh-CN.json | 5 ++- app/javascript/mastodon/locales/zh-TW.json | 5 ++- config/locales/activerecord.sl.yml | 6 ++++ config/locales/ko.yml | 2 +- config/locales/nn.yml | 1 + config/locales/simple_form.sl.yml | 5 +++ config/locales/sl.yml | 41 ++++++++++++++++++++++ 32 files changed, 209 insertions(+), 28 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index caf49397770..02f88f1513a 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Перайсці да профілю", "account.hide_reblogs": "Схаваць пашырэнні ад @{name}", "account.in_memoriam": "У памяць.", + "account.joined_long": "Далучыў(-ла)ся {date}", "account.joined_short": "Далучыўся", "account.languages": "Змяніць выбраныя мовы", "account.link_verified_on": "Права ўласнасці на гэтую спасылку праверана {date}", @@ -90,6 +91,8 @@ "account.unmute": "Не ігнараваць @{name}", "account.unmute_notifications_short": "Апавяшчаць", "account.unmute_short": "Не ігнараваць", + "account_fields_modal.close": "Закрыць", + "account_fields_modal.title": "Інфармацыя пра {name}", "account_note.placeholder": "Націсніце, каб дадаць нататку", "admin.dashboard.daily_retention": "Штодзённы паказчык утрымання карыстальнікаў пасля рэгістрацыі", "admin.dashboard.monthly_retention": "Штомесячны паказчык утрымання карыстальнікаў пасля рэгістрацыі", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}", "loading_indicator.label": "Ідзе загрузка…", "media_gallery.hide": "Схаваць", - "minicard.more_items": "+ яшчэ {count}", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Ваш уліковы запіс {disabledAccount} зараз адключаны, таму што Вы перайшлі на {movedToAccount}.", "mute_modal.hide_from_notifications": "Схаваць з апавяшчэнняў", "mute_modal.hide_options": "Схаваць опцыі", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 447bbf56ea0..b29ca9c3521 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Přejít na profil", "account.hide_reblogs": "Skrýt boosty od @{name}", "account.in_memoriam": "In Memoriam.", + "account.joined_long": "Přidali se {date}", "account.joined_short": "Připojen/a", "account.languages": "Změnit odebírané jazyky", "account.link_verified_on": "Vlastnictví tohoto odkazu bylo zkontrolováno {date}", @@ -90,6 +91,8 @@ "account.unmute": "Zrušit skrytí @{name}", "account.unmute_notifications_short": "Zrušit ztlumení oznámení", "account.unmute_short": "Zrušit skrytí", + "account_fields_modal.close": "Zavřít", + "account_fields_modal.title": "info o {name}", "account_note.placeholder": "Klikněte pro přidání poznámky", "admin.dashboard.daily_retention": "Míra udržení uživatelů podle dne po registraci", "admin.dashboard.monthly_retention": "Míra udržení uživatelů podle měsíce po registraci", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# nová položka} few {# nové položky} many {# nových položek} other {# nových položek}}", "loading_indicator.label": "Načítání…", "media_gallery.hide": "Skrýt", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Váš účet {disabledAccount} je momentálně deaktivován, protože jste se přesunul/a na {movedToAccount}.", "mute_modal.hide_from_notifications": "Skrýt z oznámení", "mute_modal.hide_options": "Skrýt možnosti", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index a95303c0f2c..bc738a0ee5f 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Gå til profil", "account.hide_reblogs": "Skjul fremhævelser fra @{name}", "account.in_memoriam": "Til minde om.", + "account.joined_long": "Tilmeldt {date}", "account.joined_short": "Oprettet", "account.languages": "Skift abonnementssprog", "account.link_verified_on": "Ejerskab af dette link blev tjekket {date}", @@ -90,6 +91,8 @@ "account.unmute": "Vis @{name} igen", "account.unmute_notifications_short": "Vis notifikationer igen", "account.unmute_short": "Vis igen", + "account_fields_modal.close": "Luk", + "account_fields_modal.title": "Information om {name}", "account_note.placeholder": "Klik for at tilføje notat", "admin.dashboard.daily_retention": "Brugerfastholdelsesrate pr. dag efter tilmelding", "admin.dashboard.monthly_retention": "Brugerfastholdelsesrate pr. måned efter tilmelding", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# nyt element} other {# nye elementer}}", "loading_indicator.label": "Indlæser…", "media_gallery.hide": "Skjul", - "minicard.more_items": "+ {count} mere", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Din konto {disabledAccount} er i øjeblikket deaktiveret, fordi du er flyttet til {movedToAccount}.", "mute_modal.hide_from_notifications": "Skjul fra notifikationer", "mute_modal.hide_options": "Skjul valgmuligheder", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index f475865203d..52fa21bead1 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Profil aufrufen", "account.hide_reblogs": "Geteilte Beiträge von @{name} ausblenden", "account.in_memoriam": "Zum Andenken.", + "account.joined_long": "Registriert am {date}", "account.joined_short": "Registriert am", "account.languages": "Sprachen verwalten", "account.link_verified_on": "Das Profil mit dieser E-Mail-Adresse wurde bereits am {date} bestätigt", @@ -90,6 +91,8 @@ "account.unmute": "Stummschaltung von @{name} aufheben", "account.unmute_notifications_short": "Stummschaltung der Benachrichtigungen aufheben", "account.unmute_short": "Stummschaltung aufheben", + "account_fields_modal.close": "Schließen", + "account_fields_modal.title": "Informationen über {name}", "account_note.placeholder": "Klicken, um private Anmerkung hinzuzufügen", "admin.dashboard.daily_retention": "Verweildauer der Nutzer*innen pro Tag seit der Registrierung", "admin.dashboard.monthly_retention": "Verweildauer der Nutzer*innen pro Monat seit der Registrierung", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}", "loading_indicator.label": "Lädt …", "media_gallery.hide": "Ausblenden", - "minicard.more_items": "+ {count} weitere", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Dein Konto {disabledAccount} ist derzeit deaktiviert, weil du zu {movedToAccount} umgezogen bist.", "mute_modal.hide_from_notifications": "Auch aus den Benachrichtigungen entfernen", "mute_modal.hide_options": "Optionen ausblenden", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 9903fed3b43..c67074ea2da 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Μετάβαση στο προφίλ", "account.hide_reblogs": "Απόκρυψη ενισχύσεων από @{name}", "account.in_memoriam": "Εις μνήμην.", + "account.joined_long": "Έγινε μέλος {date}", "account.joined_short": "Έγινε μέλος", "account.languages": "Αλλαγή εγγεγραμμένων γλωσσών", "account.link_verified_on": "Η ιδιοκτησία αυτού του συνδέσμου ελέχθηκε στις {date}", @@ -90,6 +91,8 @@ "account.unmute": "Άρση σίγασης @{name}", "account.unmute_notifications_short": "Σίγαση ειδοποιήσεων", "account.unmute_short": "Κατάργηση σίγασης", + "account_fields_modal.close": "Κλείσιμο", + "account_fields_modal.title": "Πληροφορίες {name}", "account_note.placeholder": "Κάνε κλικ για να προσθέσεις σημείωση", "admin.dashboard.daily_retention": "Ποσοστό χρηστών που παραμένουν μετά την εγγραφή, ανά ημέρα", "admin.dashboard.monthly_retention": "Ποσοστό χρηστών που παραμένουν μετά την εγγραφή, ανά μήνα", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# νέο στοιχείο} other {# νέα στοιχεία}}", "loading_indicator.label": "Φόρτωση…", "media_gallery.hide": "Απόκρυψη", - "minicard.more_items": "+ {count} ακόμα", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Ο λογαριασμός σου {disabledAccount} είναι προσωρινά απενεργοποιημένος επειδή μεταφέρθηκες στον {movedToAccount}.", "mute_modal.hide_from_notifications": "Απόκρυψη από ειδοποιήσεις", "mute_modal.hide_options": "Απόκρυψη επιλογών", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 58912dd035d..91937f69233 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Go to profile", "account.hide_reblogs": "Hide boosts from @{name}", "account.in_memoriam": "In Memoriam.", + "account.joined_long": "Joined on {date}", "account.joined_short": "Joined", "account.languages": "Change subscribed languages", "account.link_verified_on": "Ownership of this link was checked on {date}", @@ -90,6 +91,8 @@ "account.unmute": "Unmute @{name}", "account.unmute_notifications_short": "Unmute notifications", "account.unmute_short": "Unmute", + "account_fields_modal.close": "Close", + "account_fields_modal.title": "{name}'s info", "account_note.placeholder": "Click to add note", "admin.dashboard.daily_retention": "User retention rate by day after sign-up", "admin.dashboard.monthly_retention": "User retention rate by month after sign-up", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# new item} other {# new items}}", "loading_indicator.label": "Loading…", "media_gallery.hide": "Hide", - "minicard.more_items": "+ {count} more", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.", "mute_modal.hide_from_notifications": "Hide from notifications", "mute_modal.hide_options": "Hide options", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 197dc7a92ae..fd4e291f171 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar adhesiones de @{name}", "account.in_memoriam": "Cuenta conmemorativa.", + "account.joined_long": "En este servidor desde el {date}", "account.joined_short": "En este servidor desde el", "account.languages": "Cambiar idiomas suscritos", "account.link_verified_on": "La propiedad de este enlace fue verificada el {date}", @@ -90,6 +91,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_fields_modal.close": "Cerrar", + "account_fields_modal.title": "Información de {name}", "account_note.placeholder": "Hacé clic par agregar una nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día, después del registro", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes, después del registro", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# elemento nuevo} other {# elementos nuevos}}", "loading_indicator.label": "Cargando…", "media_gallery.hide": "Ocultar", - "minicard.more_items": "y {count} más", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te mudaste a {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar en las notificaciones", "mute_modal.hide_options": "Ocultar opciones", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 48eaf1dc6fb..9aafae6f236 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar impulsos de @{name}", "account.in_memoriam": "En memoria.", + "account.joined_long": "Se unión el {date}", "account.joined_short": "Se unió", "account.languages": "Cambiar idiomas suscritos", "account.link_verified_on": "El proprietario de este enlace fue comprobado el {date}", @@ -90,6 +91,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_fields_modal.close": "Cerrar", + "account_fields_modal.title": "Información de {name}", "account_note.placeholder": "Haz clic para agregar una nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después de unirse", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después de unirse", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}", "loading_indicator.label": "Cargando…", "media_gallery.hide": "Ocultar", - "minicard.more_items": "y {count} más", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar de las notificaciones", "mute_modal.hide_options": "Ocultar opciones", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index aa2b7588ddf..2b7d1905350 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -589,7 +589,6 @@ "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}", "loading_indicator.label": "Cargando…", "media_gallery.hide": "Ocultar", - "minicard.more_items": "y {count} más", "moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar de las notificaciones", "mute_modal.hide_options": "Ocultar opciones", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index c0cd9a77cd5..db3da7bf2e7 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Siirry profiiliin", "account.hide_reblogs": "Piilota käyttäjän @{name} tehostukset", "account.in_memoriam": "Muistoissamme.", + "account.joined_long": "Liittynyt {date}", "account.joined_short": "Liittynyt", "account.languages": "Vaihda tilattuja kieliä", "account.link_verified_on": "Linkin omistus tarkistettiin {date}", @@ -90,6 +91,8 @@ "account.unmute": "Poista käyttäjän @{name} mykistys", "account.unmute_notifications_short": "Poista ilmoitusten mykistys", "account.unmute_short": "Poista mykistys", + "account_fields_modal.close": "Sulje", + "account_fields_modal.title": "Käyttäjän {name} tiedot", "account_note.placeholder": "Lisää muistiinpano napsauttamalla", "admin.dashboard.daily_retention": "Käyttäjien pysyvyys päivittäin rekisteröitymisen jälkeen", "admin.dashboard.monthly_retention": "Käyttäjien pysyvyys kuukausittain rekisteröitymisen jälkeen", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# uusi kohde} other {# uutta kohdetta}}", "loading_indicator.label": "Ladataan…", "media_gallery.hide": "Piilota", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Tilisi {disabledAccount} on tällä hetkellä poissa käytöstä, koska teit siirron tiliin {movedToAccount}.", "mute_modal.hide_from_notifications": "Piilota ilmoituksista", "mute_modal.hide_options": "Piilota vaihtoehdot", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 1f421614597..aac6797b046 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Far til vanga", "account.hide_reblogs": "Fjal stimbran frá @{name}", "account.in_memoriam": "In memoriam.", + "account.joined_long": "Meldaði til {date}", "account.joined_short": "Gjørdist limur", "account.languages": "Broyt fylgd mál", "account.link_verified_on": "Ognarskapur av hesum leinki var eftirkannaður {date}", @@ -90,6 +91,8 @@ "account.unmute": "Doyv ikki @{name}", "account.unmute_notifications_short": "Tendra fráboðanir", "account.unmute_short": "Doyv ikki", + "account_fields_modal.close": "Lat aftur", + "account_fields_modal.title": "Upplýsingarnar hjá {name}", "account_note.placeholder": "Klikka fyri at leggja viðmerking afturat", "admin.dashboard.daily_retention": "Hvussu nógvir brúkarar eru eftir, síðani tey skrásettu seg, roknað í døgum", "admin.dashboard.monthly_retention": "Hvussu nógvir brúkarar eru eftir síðani tey skrásettu seg, roknað í mánaðum", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# nýtt evni} other {# nýggj evni}}", "loading_indicator.label": "Innlesur…", "media_gallery.hide": "Fjal", - "minicard.more_items": "+ {count} meira", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Konta tín {disabledAccount} er í løtuni óvirkin, tí tú flutti til {movedToAccount}.", "mute_modal.hide_from_notifications": "Fjal boð", "mute_modal.hide_options": "Fjal valmøguleikar", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 5cf1c8b4b9f..7c8fc6358b7 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -21,7 +21,7 @@ "account.block_domain": "Bloquer le domaine {domain}", "account.block_short": "Bloquer", "account.blocked": "Bloqué·e", - "account.blocking": "Bloqué", + "account.blocking": "Bloqué·e", "account.cancel_follow_request": "Retirer cette demande d'abonnement", "account.copy": "Copier le lien du profil", "account.direct": "Mention privée @{name}", @@ -32,8 +32,8 @@ "account.enable_notifications": "Me notifier quand @{name} publie", "account.endorse": "Inclure sur profil", "account.familiar_followers_many": "Suivi par {name1}, {name2}, et {othersCount, plural, one {une autre personne que vous suivez} other {# autres personnes que vous suivez}}", - "account.familiar_followers_one": "Suivi par {name1}", - "account.familiar_followers_two": "Suivi par {name1} et {name2}", + "account.familiar_followers_one": "Suivi·e par {name1}", + "account.familiar_followers_two": "Suivi·e par {name1} et {name2}", "account.featured": "En vedette", "account.featured.accounts": "Profils", "account.featured.hashtags": "Hashtags", @@ -57,6 +57,7 @@ "account.go_to_profile": "Voir ce profil", "account.hide_reblogs": "Masquer les boosts de @{name}", "account.in_memoriam": "En souvenir de", + "account.joined_long": "Ici depuis le {date}", "account.joined_short": "Inscrit·e", "account.languages": "Changer les langues abonnées", "account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}", @@ -90,6 +91,8 @@ "account.unmute": "Ne plus masquer @{name}", "account.unmute_notifications_short": "Ne plus masquer les notifications", "account.unmute_short": "Ne plus masquer", + "account_fields_modal.close": "Fermer", + "account_fields_modal.title": "Infos de {name}", "account_note.placeholder": "Cliquez pour ajouter une note", "admin.dashboard.daily_retention": "Taux de rétention des comptes par jour après inscription", "admin.dashboard.monthly_retention": "Taux de rétention des comptes par mois après inscription", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}", "loading_indicator.label": "Chargement…", "media_gallery.hide": "Masquer", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous avez déménagé sur {movedToAccount}.", "mute_modal.hide_from_notifications": "Cacher des notifications", "mute_modal.hide_options": "Masquer les options", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index df03661c9df..456258ca2ea 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -4,7 +4,7 @@ "about.default_locale": "Défaut", "about.disclaimer": "Mastodon est un logiciel libre, open-source et une marque déposée de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Raison non disponible", - "about.domain_blocks.preamble": "Mastodon vous permet de visualiser le contenu et d'interagir avec les utilisateurs et utilisatrices de généralement n'importe quel autre serveur dans le fédivers. Voici les exceptions qui ont été faites sur ce serveur.", + "about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateurs et utilisatrices de n'importe quel autre serveur dans le fédivers. Voici les exceptions qui ont été faites sur ce serveur.", "about.domain_blocks.silenced.explanation": "Vous ne verrez généralement pas les profils et le contenu de ce serveur, à moins que vous ne les recherchiez explicitement ou que vous ne choisissiez de les suivre.", "about.domain_blocks.silenced.title": "Limité", "about.domain_blocks.suspended.explanation": "Aucune donnée de ce serveur ne sera traitée, enregistrée ou échangée, rendant impossible toute interaction ou communication avec les comptes de ce serveur.", @@ -21,19 +21,19 @@ "account.block_domain": "Bloquer le domaine {domain}", "account.block_short": "Bloquer", "account.blocked": "Bloqué·e", - "account.blocking": "Bloqué", + "account.blocking": "Bloqué·e", "account.cancel_follow_request": "Annuler l'abonnement", "account.copy": "Copier le lien du profil", - "account.direct": "Mention privée @{name}", - "account.disable_notifications": "Ne plus me notifier quand @{name} publie quelque chose", + "account.direct": "Mentionner @{name} en privé", + "account.disable_notifications": "Ne plus me notifier les publications de @{name}", "account.domain_blocking": "Domaine bloqué", "account.edit_profile": "Modifier le profil", "account.edit_profile_short": "Modifier", - "account.enable_notifications": "Me notifier quand @{name} publie quelque chose", + "account.enable_notifications": "Me notifier les publications de @{name}", "account.endorse": "Recommander sur votre profil", "account.familiar_followers_many": "Suivi par {name1}, {name2}, et {othersCount, plural, one {une autre personne que vous suivez} other {# autres personnes que vous suivez}}", - "account.familiar_followers_one": "Suivi par {name1}", - "account.familiar_followers_two": "Suivi par {name1} et {name2}", + "account.familiar_followers_one": "Suivi·e par {name1}", + "account.familiar_followers_two": "Suivi·e par {name1} et {name2}", "account.featured": "En vedette", "account.featured.accounts": "Profils", "account.featured.hashtags": "Hashtags", @@ -57,6 +57,7 @@ "account.go_to_profile": "Voir le profil", "account.hide_reblogs": "Masquer les partages de @{name}", "account.in_memoriam": "En mémoire de.", + "account.joined_long": "Ici depuis le {date}", "account.joined_short": "Ici depuis", "account.languages": "Modifier les langues d'abonnements", "account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}", @@ -90,6 +91,8 @@ "account.unmute": "Ne plus masquer @{name}", "account.unmute_notifications_short": "Réactiver les notifications", "account.unmute_short": "Ne plus masquer", + "account_fields_modal.close": "Fermer", + "account_fields_modal.title": "Infos de {name}", "account_note.placeholder": "Cliquez pour ajouter une note", "admin.dashboard.daily_retention": "Taux de rétention des utilisateur·rice·s par jour après inscription", "admin.dashboard.monthly_retention": "Taux de rétention des utilisateur·rice·s par mois après inscription", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}", "loading_indicator.label": "Chargement…", "media_gallery.hide": "Masquer", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous l'avez déplacé à {movedToAccount}.", "mute_modal.hide_from_notifications": "Cacher des notifications", "mute_modal.hide_options": "Masquer les options", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index c3a57d6e09d..111a0450fff 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Téigh go dtí próifíl", "account.hide_reblogs": "Folaigh moltaí ó @{name}", "account.in_memoriam": "Ón tseanaimsir.", + "account.joined_long": "Chuaigh isteach ar {date}", "account.joined_short": "Cláraithe", "account.languages": "Athraigh teangacha foscríofa", "account.link_verified_on": "Seiceáladh úinéireacht an naisc seo ar {date}", @@ -90,6 +91,8 @@ "account.unmute": "Díbhalbhaigh @{name}", "account.unmute_notifications_short": "Díbhalbhaigh fógraí", "account.unmute_short": "Díbhalbhaigh", + "account_fields_modal.close": "Dún", + "account_fields_modal.title": "Eolas {name}", "account_note.placeholder": "Cliceáil chun nóta a chuir leis", "admin.dashboard.daily_retention": "Ráta coinneála an úsáideora de réir an lae tar éis clárú", "admin.dashboard.monthly_retention": "Ráta coinneála na n-úsáideoirí de réir na míosa tar éis dóibh clárú", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# mír nua} two {# mír nua} few {# mír nua} many {# mír nua} other {# mír nua}}", "loading_indicator.label": "Á lódáil…", "media_gallery.hide": "Folaigh", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Tá do chuntas {disabledAccount} díchumasaithe faoi láthair toisc gur bhog tú go {movedToAccount}.", "mute_modal.hide_from_notifications": "Folaigh ó fhógraí", "mute_modal.hide_options": "Folaigh roghanna", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 03a4e4acf94..430687916df 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -18,7 +18,7 @@ "account.badges.bot": "Automatizada", "account.badges.group": "Grupo", "account.block": "Bloquear @{name}", - "account.block_domain": "Agochar todo de {domain}", + "account.block_domain": "Bloquear o dominio {domain}", "account.block_short": "Bloquear", "account.blocked": "Bloqueada", "account.blocking": "Bloqueos", @@ -57,6 +57,7 @@ "account.go_to_profile": "Ir ao perfil", "account.hide_reblogs": "Agochar promocións de @{name}", "account.in_memoriam": "Lembranzas.", + "account.joined_long": "Uníuse o {date}", "account.joined_short": "Uniuse", "account.languages": "Modificar os idiomas subscritos", "account.link_verified_on": "A propiedade desta ligazón foi verificada o {date}", @@ -90,6 +91,8 @@ "account.unmute": "Deixar de silenciar a @{name}", "account.unmute_notifications_short": "Reactivar notificacións", "account.unmute_short": "Non silenciar", + "account_fields_modal.close": "Fechar", + "account_fields_modal.title": "Info sobre {name}", "account_note.placeholder": "Preme para engadir nota", "admin.dashboard.daily_retention": "Ratio de retención de usuarias diaria após rexistrarse", "admin.dashboard.monthly_retention": "Ratio de retención de usuarias mensual após o rexistro", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# novo elemento} other {# novos elementos}}", "loading_indicator.label": "Estase a cargar…", "media_gallery.hide": "Agochar", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "A túa conta {disabledAccount} está actualmente desactivada porque movéchela a {movedToAccount}.", "mute_modal.hide_from_notifications": "Agochar nas notificacións", "mute_modal.hide_options": "Opcións ao ocultar", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 3cee7adc5e1..f01e43968ce 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -57,6 +57,7 @@ "account.go_to_profile": "מעבר לפרופיל", "account.hide_reblogs": "להסתיר הידהודים מאת @{name}", "account.in_memoriam": "פרופיל זכרון.", + "account.joined_long": "הצטרפו ב־{date}", "account.joined_short": "תאריך הצטרפות", "account.languages": "שנה רישום לשפות", "account.link_verified_on": "בעלות על הקישור הזה נבדקה לאחרונה ב{date}", @@ -90,6 +91,8 @@ "account.unmute": "הפסקת השתקת @{name}", "account.unmute_notifications_short": "הפעלת הודעות", "account.unmute_short": "ביטול השתקה", + "account_fields_modal.close": "סגירה", + "account_fields_modal.title": "הפרטים של {name}", "account_note.placeholder": "יש ללחוץ כדי להוסיף הערות", "admin.dashboard.daily_retention": "קצב שימור משתמשים יומי אחרי ההרשמה", "admin.dashboard.monthly_retention": "קצב שימור משתמשים (פר חודש) אחרי ההרשמה", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# פריט חדש} other {# פריטים חדשים}}", "loading_indicator.label": "בטעינה…", "media_gallery.hide": "להסתיר", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "חשבונך {disabledAccount} אינו פעיל כרגע עקב מעבר ל{movedToAccount}.", "mute_modal.hide_from_notifications": "להסתיר מהתראות", "mute_modal.hide_options": "הסתרת אפשרויות", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index edcdd2b9f35..26d9658dbdf 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Ugrás a profilhoz", "account.hide_reblogs": "@{name} megtolásainak elrejtése", "account.in_memoriam": "Emlékünkben.", + "account.joined_long": "Csatlakozás ideje: {date}", "account.joined_short": "Csatlakozott", "account.languages": "Feliratkozott nyelvek módosítása", "account.link_verified_on": "A linket eredetiségét ebben az időpontban ellenőriztük: {date}", @@ -90,6 +91,8 @@ "account.unmute": "@{name} némításának feloldása", "account.unmute_notifications_short": "Értesítések némításának feloldása", "account.unmute_short": "Némitás feloldása", + "account_fields_modal.close": "Bezárás", + "account_fields_modal.title": "{name} információi", "account_note.placeholder": "Kattintás jegyzet hozzáadásához", "admin.dashboard.daily_retention": "Napi regisztráció utáni felhasználómegtartási arány", "admin.dashboard.monthly_retention": "Havi regisztráció utáni felhasználómegtartási arány", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# új elem} other {# új elem}}", "loading_indicator.label": "Betöltés…", "media_gallery.hide": "Elrejtés", - "minicard.more_items": "+ {count} további", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "A(z) {disabledAccount} fiókod jelenleg le van tiltva, mert átköltöztél ide: {movedToAccount}.", "mute_modal.hide_from_notifications": "Elrejtés az értesítések közül", "mute_modal.hide_options": "Beállítások elrejtése", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index e2efff0a47d..efdce412ca2 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Fara í notandasnið", "account.hide_reblogs": "Fela endurbirtingar fyrir @{name}", "account.in_memoriam": "Minning.", + "account.joined_long": "Skáði sig {date}", "account.joined_short": "Gerðist þátttakandi", "account.languages": "Breyta tungumálum í áskrift", "account.link_verified_on": "Eignarhald á þessum tengli var athugað þann {date}", @@ -90,6 +91,8 @@ "account.unmute": "Hætta að þagga niður í @{name}", "account.unmute_notifications_short": "Hætta að þagga í tilkynningum", "account.unmute_short": "Hætta að þagga niður", + "account_fields_modal.close": "Loka", + "account_fields_modal.title": "Upplýsingar um {name}", "account_note.placeholder": "Smelltu til að bæta við minnispunkti", "admin.dashboard.daily_retention": "Hlutfall virkra notenda eftir nýskráningu eftir dögum", "admin.dashboard.monthly_retention": "Hlutfall virkra notenda eftir nýskráningu eftir mánuðum", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# nýtt atriði} other {# ný atriði}}", "loading_indicator.label": "Hleð inn…", "media_gallery.hide": "Fela", - "minicard.more_items": "+ {count} í viðbót", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Aðgangurinn þinn {disabledAccount} er óvirkur í augnablikinu vegna þess að þú fluttir þig yfir á {movedToAccount}.", "mute_modal.hide_from_notifications": "Fela úr tilkynningum", "mute_modal.hide_options": "Fela valkosti", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 8b6c70bd140..364b6227c0d 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Vai al profilo", "account.hide_reblogs": "Nascondi condivisioni da @{name}", "account.in_memoriam": "In memoria.", + "account.joined_long": "Su questa istanza dal {date}", "account.joined_short": "Iscritto", "account.languages": "Modifica le lingue d'iscrizione", "account.link_verified_on": "La proprietà di questo link è stata controllata il {date}", @@ -90,6 +91,8 @@ "account.unmute": "Riattiva @{name}", "account.unmute_notifications_short": "Riattiva notifiche", "account.unmute_short": "Attiva audio", + "account_fields_modal.close": "Chiudi", + "account_fields_modal.title": "Informazioni su {name}", "account_note.placeholder": "Clicca per aggiungere una nota", "admin.dashboard.daily_retention": "Tasso di ritenzione dell'utente per giorno, dopo la registrazione", "admin.dashboard.monthly_retention": "Tasso di ritenzione dell'utente per mese, dopo la registrazione", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# nuovo oggetto} other {# nuovi oggetti}}", "loading_indicator.label": "Caricamento…", "media_gallery.hide": "Nascondi", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Il tuo profilo {disabledAccount} è correntemente disabilitato perché ti sei spostato a {movedToAccount}.", "mute_modal.hide_from_notifications": "Nascondi dalle notifiche", "mute_modal.hide_options": "Nascondi le opzioni", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index e22d0f0b524..7bf3e42234e 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Gå til profil", "account.hide_reblogs": "Gøym framhevingar frå @{name}", "account.in_memoriam": "Til minne om.", + "account.joined_long": "Vart med {date}", "account.joined_short": "Vart med", "account.languages": "Endre språktingingar", "account.link_verified_on": "Eigarskap for denne lenkja vart sist sjekka {date}", @@ -90,6 +91,8 @@ "account.unmute": "Opphev demping av @{name}", "account.unmute_notifications_short": "Opphev demping av varslingar", "account.unmute_short": "Opphev demping", + "account_fields_modal.close": "Lukk", + "account_fields_modal.title": "{name} sine opplysingar", "account_note.placeholder": "Klikk for å leggja til merknad", "admin.dashboard.daily_retention": "Mengda brukarar aktive ved dagar etter registrering", "admin.dashboard.monthly_retention": "Mengda brukarar aktive ved månader etter registrering", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# nytt element} other {# nye element}}", "loading_indicator.label": "Lastar…", "media_gallery.hide": "Gøym", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Kontoen din, {disabledAccount} er for tida deaktivert fordi du har flytta til {movedToAccount}.", "mute_modal.hide_from_notifications": "Ikkje vis varslingar", "mute_modal.hide_options": "Gøym val", diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index 2520939eb77..e0ced02cbd3 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -49,6 +49,7 @@ "account.follows_you": "ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਦੇ ਹਨ", "account.go_to_profile": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਜਾਓ", "account.hide_reblogs": "{name} ਵਲੋਂ ਬੂਸਟ ਨੂੰ ਲੁਕਾਓ", + "account.joined_long": "{date} ਨੂੰ ਜੁਆਇਨ ਕੀਤਾ", "account.joined_short": "ਜੁਆਇਨ ਕੀਤਾ", "account.media": "ਮੀਡੀਆ", "account.mention": "@{name} ਦਾ ਜ਼ਿਕਰ", @@ -76,6 +77,8 @@ "account.unmute": "@{name} ਲਈ ਮੌਨ ਹਟਾਓ", "account.unmute_notifications_short": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਅਣ-ਮੌਨ ਕਰੋ", "account.unmute_short": "ਮੌਨ-ਰਹਿਤ ਕਰੋ", + "account_fields_modal.close": "ਬੰਦ ਕਰੋ", + "account_fields_modal.title": "{name} ਦੀ ਜਾਣਕਾਰੀ", "account_note.placeholder": "Click to add a note", "admin.dashboard.retention.average": "ਔਸਤ", "admin.dashboard.retention.cohort_size": "ਨਵੇਂ ਵਰਤੋਂਕਾਰ", @@ -85,6 +88,13 @@ "alt_text_modal.cancel": "ਰੱਦ ਕਰੋ", "alt_text_modal.done": "ਮੁਕੰਮਲ", "announcement.announcement": "ਹੋਕਾ", + "annual_report.announcement.action_dismiss": "ਨਹੀਂ, ਧੰਨਵਾਦ", + "annual_report.nav_item.badge": "ਨਵਾਂ", + "annual_report.shared_page.donate": "ਦਾਨ ਕਰੋ", + "annual_report.summary.archetype.die_drei_fragezeichen": "???", + "annual_report.summary.close": "ਬੰਦ ਕਰੋ", + "annual_report.summary.copy_link": "ਲਿੰਕ ਨੂੰ ਕਾਪੀ ਕਰੋ", + "annual_report.summary.highlighted_post.title": "ਸਭ ਤੋਂ ਵੱਧ ਹਰਮਨਪਿਆਰੀ ਪੋਸਟ", "annual_report.summary.most_used_app.most_used_app": "ਸਭ ਤੋਂ ਵੱਧ ਵਰਤੀ ਐਪ", "annual_report.summary.new_posts.new_posts": "ਨਵੀਆਂ ਪੋਸਟਾਂ", "audio.hide": "ਆਡੀਓ ਨੂੰ ਲੁਕਾਓ", @@ -273,6 +283,8 @@ "follow_suggestions.who_to_follow": "ਕਿਸ ਨੂੰ ਫ਼ਾਲੋ ਕਰੀਏ", "followed_tags": "ਫ਼ਾਲੋ ਕੀਤੇ ਹੈਸ਼ਟੈਗ", "footer.about": "ਸਾਡੇ ਬਾਰੇ", + "footer.about_mastodon": "Mastodon ਬਾਰੇ", + "footer.about_server": "{domain} ਬਾਰੇ", "footer.about_this_server": "ਇਸ ਬਾਰੇ", "footer.directory": "ਪਰੋਫਾਇਲ ਡਾਇਰੈਕਟਰੀ", "footer.get_app": "ਐਪ ਲਵੋ", @@ -293,6 +305,7 @@ "hashtag.column_settings.tag_mode.any": "ਇਹਨਾਂ ਵਿੱਚੋਂ ਕੋਈ", "hashtag.column_settings.tag_mode.none": "ਇਹਨਾਂ ਵਿੱਚੋਂ ਕੋਈ ਨਹੀਂ", "hashtag.column_settings.tag_toggle": "Include additional tags in this column", + "hashtag.feature": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ", "hashtag.follow": "ਹੈਸ਼ਟੈਗ ਨੂੰ ਫ਼ਾਲੋ ਕਰੋ", "hashtag.mute": "#{hashtag} ਨੂੰ ਮੌਨ ਕਰੋ", "hashtag.unfollow": "ਹੈਸ਼ਟੈਗ ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰੋ", @@ -438,6 +451,7 @@ "notification_requests.exit_selection": "ਮੁਕੰਮਲ", "notification_requests.notifications_from": "{name} ਵਲੋਂ ਨੋਟੀਫਿਕੇਸ਼ਨ", "notification_requests.title": "ਫਿਲਟਰ ਕੀਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ", + "notification_requests.view": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਵੇਖੋ", "notifications.clear": "ਸੂਚਨਾਵਾਂ ਨੂੰ ਮਿਟਾਓ", "notifications.clear_confirmation": "ਕੀ ਤੁਸੀਂ ਆਪਣੇ ਸਾਰੇ ਨੋਟੀਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਪੱਕੇ ਤੌਰ ਉੱਤੇ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?", "notifications.clear_title": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਮਿਟਾਉਣਾ ਹੈ?", @@ -481,6 +495,7 @@ "onboarding.follows.back": "ਪਿੱਛੇ", "onboarding.follows.done": "ਮੁਕੰਮਲ", "onboarding.follows.search": "ਖੋਜੋ", + "onboarding.profile.display_name": "ਦਿਖਾਇਆ ਜਾਣ ਵਾਲਾ ਨਾਂ", "onboarding.profile.note": "ਜਾਣਕਾਰੀ", "onboarding.profile.save_and_continue": "ਸੰਭਾਲੋ ਅਤੇ ਜਾਰੀ ਰੱਖੋ", "onboarding.profile.title": "ਪਰੋਫਾਈਲ ਸੈਟਅੱਪ", @@ -584,6 +599,7 @@ "status.edited": "ਆਖਰੀ ਸੋਧ ਦੀ ਤਾਰੀਖ {date}", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", "status.favourite": "ਪਸੰਦ", + "status.filter": "ਇਸ ਪੋਸਟ ਨੂੰ ਫਿਲਟਰ ਕਰੋ", "status.history.created": "{name} ਨੇ {date} ਨੂੰ ਬਣਾਇਆ", "status.history.edited": "{name} ਨੇ {date} ਨੂੰ ਸੋਧਿਆ", "status.load_more": "ਹੋਰ ਦਿਖਾਓ", @@ -598,6 +614,7 @@ "status.pin": "ਪਰੋਫਾਈਲ ਉੱਤੇ ਟੰਗੋ", "status.quote": "ਹਵਾਲਾ", "status.quote.cancel": "ਹਵਾਲੇ ਨੂੰ ਰੱਦ ਕਰੋ", + "status.quote_noun": "ਹਵਾਲਾ", "status.quotes_count": "{count, plural, one {{counter} ਹਵਾਲਾ} other {{counter} ਹਵਾਲੇ}}", "status.read_more": "ਹੋਰ ਪੜ੍ਹੋ", "status.reblog": "ਬੂਸਟ", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 72b97b22cb9..2738e364100 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Ir para o perfil", "account.hide_reblogs": "Esconder partilhas de @{name}", "account.in_memoriam": "Em Memória.", + "account.joined_long": "Juntou-se em {date}", "account.joined_short": "Juntou-se a", "account.languages": "Alterar idiomas subscritos", "account.link_verified_on": "O proprietário desta hiperligação foi verificado em {date}", @@ -90,6 +91,8 @@ "account.unmute": "Desocultar @{name}", "account.unmute_notifications_short": "Desocultar notificações", "account.unmute_short": "Desocultar", + "account_fields_modal.close": "Fechar", + "account_fields_modal.title": "Info de {name}", "account_note.placeholder": "Clicar para adicionar nota", "admin.dashboard.daily_retention": "Taxa de retenção de utilizadores por dia após a inscrição", "admin.dashboard.monthly_retention": "Taxa de retenção de utilizadores por mês após a inscrição", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# novo item} other {# novos itens}}", "loading_indicator.label": "A carregar…", "media_gallery.hide": "Esconder", - "minicard.more_items": "+ {count} mais", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "A tua conta {disabledAccount} está neste momento desativada porque migraste para {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar das notificações", "mute_modal.hide_options": "Ocultar opções", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 4e401c5c882..c7f2b15d12d 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -38,8 +38,10 @@ "account.follow": "Sledi", "account.follow_back": "Sledi nazaj", "account.follow_back_short": "Sledi nazaj", + "account.follow_request": "Zaprosi za sledenje", "account.follow_request_cancel": "Prekliči zahtevo", "account.follow_request_cancel_short": "Prekliči", + "account.follow_request_short": "Zaprosi", "account.followers": "Sledilci", "account.followers.empty": "Nihče še ne sledi temu uporabniku.", "account.followers_counter": "{count, plural, one {{counter} sledilec} two {{counter} sledilca} few {{counter} sledilci} other {{counter} sledilcev}}", @@ -50,6 +52,7 @@ "account.go_to_profile": "Pojdi na profil", "account.hide_reblogs": "Skrij izpostavitve od @{name}", "account.in_memoriam": "V spomin.", + "account.joined_long": "Pridružen/a {date}", "account.joined_short": "Pridružil/a", "account.languages": "Spremeni naročene jezike", "account.link_verified_on": "Lastništvo te povezave je bilo preverjeno {date}", @@ -61,13 +64,16 @@ "account.mute_notifications_short": "Utišaj obvestila", "account.mute_short": "Utišaj", "account.muted": "Utišan", + "account.muting": "Izklop zvoka", "account.mutual": "Drug drugemu sledita", "account.no_bio": "Ni opisa.", "account.open_original_page": "Odpri izvirno stran", "account.posts": "Objave", "account.posts_with_replies": "Objave in odgovori", + "account.remove_from_followers": "Odstrani {name} iz sledilcev", "account.report": "Prijavi @{name}", "account.requested_follow": "{name} vam želi slediti", + "account.requests_to_follow_you": "Vas prosi za sledenje", "account.share": "Deli profil osebe @{name}", "account.show_reblogs": "Pokaži izpostavitve osebe @{name}", "account.statuses_counter": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objave} other {{counter} objav}}", @@ -80,6 +86,7 @@ "account.unmute": "Povrni glas @{name}", "account.unmute_notifications_short": "Izklopi utišanje obvestil", "account.unmute_short": "Povrni glas", + "account_fields_modal.close": "Zapri", "account_note.placeholder": "Kliknite, da dodate opombo", "admin.dashboard.daily_retention": "Mera ohranjanja uporabnikov po dnevih od registracije", "admin.dashboard.monthly_retention": "Mera ohranjanja uporabnikov po mesecih od registracije", @@ -103,11 +110,24 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Podaj opis za slabovidne ...", "alt_text_modal.done": "Opravljeno", "announcement.announcement": "Oznanilo", + "annual_report.announcement.action_dismiss": "Ne, hvala", + "annual_report.nav_item.badge": "Nov", + "annual_report.shared_page.donate": "Prispevaj", + "annual_report.summary.archetype.replier.name": "Metulj", + "annual_report.summary.archetype.reveal": "Razkrij moj arhetip", + "annual_report.summary.archetype.title_public": "Arhetip {name}", + "annual_report.summary.archetype.title_self": "Vaš arhetip", + "annual_report.summary.close": "Zapri", + "annual_report.summary.copy_link": "Kopiraj povezavo", + "annual_report.summary.followers.new_followers": "{count, plural, one {{counter} nov sledilec} two {{counter} nova sledilca} few {{counter} novi sledilci} other {{counter} novih sledilcev}}", + "annual_report.summary.highlighted_post.title": "Najbolj priljubljena objava", "annual_report.summary.most_used_app.most_used_app": "najpogosteje uporabljena aplikacija", "annual_report.summary.most_used_hashtag.most_used_hashtag": "največkrat uporabljen ključnik", "annual_report.summary.new_posts.new_posts": "nove objave", "annual_report.summary.percentile.text": "S tem ste se uvrstili med zgornjih uporabnikov domene {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "Živi duši ne bomo povedali.", + "annual_report.summary.share_elsewhere": "Deli drugje", + "annual_report.summary.share_on_mastodon": "Deli na Mastodonu", "attachments_list.unprocessed": "(neobdelano)", "audio.hide": "Skrij zvok", "block_modal.remote_users_caveat": "Strežnik {domain} bomo pozvali, naj spoštuje vašo odločitev. Kljub temu pa ni gotovo, da bo strežnik prošnjo upošteval, saj nekateri strežniki blokiranja obravnavajo drugače. Javne objave bodo morda še vedno vidne neprijavljenim uporabnikom.", @@ -368,6 +388,8 @@ "follow_suggestions.who_to_follow": "Komu slediti", "followed_tags": "Sledeni ključniki", "footer.about": "O Mastodonu", + "footer.about_mastodon": "O Mastodonu", + "footer.about_server": "O {domain}", "footer.directory": "Imenik profilov", "footer.get_app": "Prenesite aplikacijo", "footer.keyboard_shortcuts": "Tipkovne bližnjice", @@ -812,6 +834,10 @@ "status.cancel_reblog_private": "Prekliči izpostavitev", "status.cannot_reblog": "Te objave ni mogoče izpostaviti", "status.contains_quote": "Vsebuje citat", + "status.context.loading": "Nalaganje več odgovorov", + "status.context.loading_error": "Novih odgovorov ni bilo možno naložiti", + "status.context.loading_success": "Novi odgovori naloženi", + "status.context.more_replies_found": "Najdenih več odgovorov", "status.context.retry": "Poskusi znova", "status.context.show": "Pokaži", "status.continued_thread": "Nadaljevanje niti", @@ -847,6 +873,9 @@ "status.quote_followers_only": "Samo sledilci lahko citirajo to objavo", "status.quote_policy_change": "Spremenite, kdo lahko citira", "status.quote_private": "Zasebnih objav ni možno citirati", + "status.quotes.empty": "Nihče še ni citiral te objave. Ko se bo to zgodilo, se bodo pojavile tukaj.", + "status.quotes.local_other_disclaimer": "Citati, ki jih je avtor zavrnil, ne bodo prikazani.", + "status.quotes_count": "{count, plural, one {{counter} citat} two {{counter} citata} few {{counter} citati} other {{counter} citatov}}", "status.read_more": "Preberi več", "status.reblog": "Izpostavi", "status.reblogged_by": "{name} je izpostavil/a", @@ -920,7 +949,9 @@ "video.unmute": "Odtišaj", "video.volume_down": "Zmanjšaj glasnost", "video.volume_up": "Povečaj glasnost", + "visibility_modal.button_title": "Določi vidnost", "visibility_modal.header": "Vidnost in interakcija", + "visibility_modal.helper.privacy_editing": "Vidnosti ni moč spremeniti, ko je objava objavljena.", "visibility_modal.privacy_label": "Vidnost", "visibility_modal.quote_followers": "Samo sledilci", "visibility_modal.quote_label": "Kdo lahko citira", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 897cbd8722b..b1bf08af1bc 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Kalo te profili", "account.hide_reblogs": "Fshih përforcime nga @{name}", "account.in_memoriam": "In Memoriam.", + "account.joined_long": "U bë pjesë më {date}", "account.joined_short": "U bë pjesë", "account.languages": "Ndryshoni gjuhë pajtimesh", "account.link_verified_on": "Pronësia e kësaj lidhjeje qe kontrolluar më {date}", @@ -90,6 +91,8 @@ "account.unmute": "Ktheji zërin @{name}", "account.unmute_notifications_short": "Shfaqi njoftimet", "account.unmute_short": "Çheshtoje", + "account_fields_modal.close": "Mbylle", + "account_fields_modal.title": "Hollësi për {name}", "account_note.placeholder": "Klikoni për të shtuar shënim", "admin.dashboard.daily_retention": "Shkallë mbajtjeje përdoruesi, në ditë, pas regjistrimit", "admin.dashboard.monthly_retention": "Shkallë mbajtjeje përdoruesi, në muaj, pas regjistrimit", @@ -587,7 +590,7 @@ "load_pending": "{count, plural,one {# objekt i ri }other {# objekte të rinj }}", "loading_indicator.label": "Po ngarkohet…", "media_gallery.hide": "Fshihe", - "minicard.more_items": "+ {count} më tepër", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Llogaria juaj {disabledAccount} aktualisht është e çaktivizuar, ngaqë kaluat te {movedToAccount}.", "mute_modal.hide_from_notifications": "Fshihe prej njoftimeve", "mute_modal.hide_options": "Fshihi mundësitë", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 6b1138ec087..59bd543b9d2 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Xem hồ sơ", "account.hide_reblogs": "Ẩn tút @{name} đăng lại", "account.in_memoriam": "Tưởng Niệm.", + "account.joined_long": "Tham gia {date}", "account.joined_short": "Tham gia", "account.languages": "Đổi ngôn ngữ mong muốn", "account.link_verified_on": "Liên kết này đã được xác minh vào {date}", @@ -90,6 +91,8 @@ "account.unmute": "Bỏ ẩn @{name}", "account.unmute_notifications_short": "Mở lại thông báo", "account.unmute_short": "Bỏ ẩn", + "account_fields_modal.close": "Đóng", + "account_fields_modal.title": "Thông tin {name}", "account_note.placeholder": "Nhấn để thêm", "admin.dashboard.daily_retention": "Tỉ lệ người dùng sau đăng ký ở lại theo ngày", "admin.dashboard.monthly_retention": "Tỉ lệ người dùng ở lại sau khi đăng ký", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, one {# tút mới} other {# tút mới}}", "loading_indicator.label": "Đang tải…", "media_gallery.hide": "Ẩn", - "minicard.more_items": "+ {count} khác", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Tài khoản {disabledAccount} của bạn hiện không khả dụng vì bạn đã chuyển sang {movedToAccount}.", "mute_modal.hide_from_notifications": "Ẩn thông báo", "mute_modal.hide_options": "Ẩn tùy chọn", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 92c0eaa3f40..fecfde2c375 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -57,6 +57,7 @@ "account.go_to_profile": "前往个人资料页", "account.hide_reblogs": "隐藏来自 @{name} 的转嘟", "account.in_memoriam": "谨此悼念。", + "account.joined_long": "加入于 {date}", "account.joined_short": "加入于", "account.languages": "更改订阅语言", "account.link_verified_on": "此链接的所有权已在 {date} 检查", @@ -90,6 +91,8 @@ "account.unmute": "不再隐藏 @{name}", "account.unmute_notifications_short": "恢复通知", "account.unmute_short": "取消隐藏", + "account_fields_modal.close": "关闭", + "account_fields_modal.title": "{name} 的信息", "account_note.placeholder": "点击添加备注", "admin.dashboard.daily_retention": "注册后用户留存率(按日计算)", "admin.dashboard.monthly_retention": "注册后用户留存率(按月计算)", @@ -589,7 +592,7 @@ "load_pending": "{count} 项", "loading_indicator.label": "加载中…", "media_gallery.hide": "隐藏", - "minicard.more_items": "+ 还有 {count} 项", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "你的账号 {disabledAccount} 已禁用,因为你已迁移到 {movedToAccount}。", "mute_modal.hide_from_notifications": "从通知中隐藏", "mute_modal.hide_options": "隐藏选项", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index f6e4e3967b1..d9799ee4393 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -57,6 +57,7 @@ "account.go_to_profile": "前往個人檔案", "account.hide_reblogs": "隱藏來自 @{name} 之轉嘟", "account.in_memoriam": "謹此悼念。", + "account.joined_long": "加入於 {date}", "account.joined_short": "加入時間", "account.languages": "變更訂閱的語言", "account.link_verified_on": "已於 {date} 檢查此連結的擁有者權限", @@ -90,6 +91,8 @@ "account.unmute": "解除靜音 @{name}", "account.unmute_notifications_short": "解除靜音推播通知", "account.unmute_short": "解除靜音", + "account_fields_modal.close": "關閉", + "account_fields_modal.title": "{name} 之資訊", "account_note.placeholder": "點擊以新增備註", "admin.dashboard.daily_retention": "註冊後使用者存留率(日)", "admin.dashboard.monthly_retention": "註冊後使用者存留率(月)", @@ -589,7 +592,7 @@ "load_pending": "{count, plural, other {# 個新項目}}", "loading_indicator.label": "正在載入...", "media_gallery.hide": "隱藏", - "minicard.more_items": "+ 其餘 {count} 個", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "您的帳號 {disabledAccount} 目前已停用,因為您已搬家至 {movedToAccount}。", "mute_modal.hide_from_notifications": "於推播通知中隱藏", "mute_modal.hide_options": "隱藏選項", diff --git a/config/locales/activerecord.sl.yml b/config/locales/activerecord.sl.yml index e4c4fe598f9..cecd0444d72 100644 --- a/config/locales/activerecord.sl.yml +++ b/config/locales/activerecord.sl.yml @@ -32,6 +32,12 @@ sl: attributes: url: invalid: ni veljaven URL + collection: + attributes: + collection_items: + too_many: je preveč, dovoljenih je največ %{count} + tag: + unusable: ni možno uporabiti doorkeeper/application: attributes: website: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 62346c68597..f0ede70b9d5 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1748,7 +1748,7 @@ ko: unrecognized_emoji: 인식 되지 않은 에모지입니다 redirects: prompt: 이 링크를 믿을 수 있다면, 클릭해서 계속하세요. - title: "%{instance}를 떠나려고 합니다." + title: "%{instance}을(를) 떠나려고 합니다." relationships: activity: 계정 활동 confirm_follow_selected_followers: 정말로 선택된 팔로워들을 팔로우하시겠습니까? diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 9d92e42a541..792a4e28666 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -2190,6 +2190,7 @@ nn: error: Det oppsto et problem med å slette sikkerhetsnøkkelen. Prøv igjen. success: Sikkerhetsnøkkelen din ble vellykket slettet. invalid_credential: Ugyldig sikkerhetsnøkkel + nickname: Kallenamn nickname_hint: Skriv inn kallenavnet til din nye sikkerhetsnøkkel not_enabled: Du har ikke aktivert WebAuthn ennå not_supported: Denne nettleseren støtter ikke sikkerhetsnøkler diff --git a/config/locales/simple_form.sl.yml b/config/locales/simple_form.sl.yml index e87dd799d2a..7cddf46aaf8 100644 --- a/config/locales/simple_form.sl.yml +++ b/config/locales/simple_form.sl.yml @@ -73,6 +73,7 @@ sl: featured_tag: name: 'Tukaj je nekaj ključnikov, ki ste jih nedavno uporabili:' filters: + action: Izberite, kako naj se program vede, ko se objava sklada s filtrom actions: hide: Povsem skrij filtrirano vsebino, kot da ne bi obstajala warn: Skrij filtrirano vsebino za opozorilom, ki pomenja naslov filtra @@ -220,6 +221,8 @@ sl: setting_always_send_emails: Vedno pošlji e-obvestila setting_auto_play_gif: Samodejno predvajanje animiranih GIF-ov setting_boost_modal: Nadziraj vidnost objav + setting_color_scheme: Način + setting_contrast: Kontrast setting_default_language: Jezik objavljanja setting_default_privacy: Vidnost objav setting_default_quote_policy: Kdo lahko citira @@ -231,6 +234,7 @@ sl: setting_display_media_default: Privzeto setting_display_media_hide_all: Skrij vse setting_display_media_show_all: Prikaži vse + setting_emoji_style: Slog čustvenih simbolov setting_expand_spoilers: Vedno razširi objave, označene z opozorili o vsebini setting_hide_network: Skrij svoje omrežje setting_reduce_motion: Zmanjšanje premikanja v animacijah @@ -285,6 +289,7 @@ sl: thumbnail: Sličica strežnika trendable_by_default: Dovoli trende brez predhodnega pregleda trends: Omogoči trende + wrapstodon: Omogoči Wrapstodon interactions: must_be_follower: Blokiraj obvestila nesledilcev must_be_following: Blokiraj obvestila oseb, ki jim ne sledite diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 2fc26113588..2abd3600e70 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -7,6 +7,8 @@ sl: hosted_on: Mastodon gostuje na %{domain} title: O programu accounts: + errors: + cannot_be_added_to_collections: Tega računa ni možno dodati v zbirke. followers: few: Sledilci one: Sledilec @@ -196,6 +198,7 @@ sl: create_relay: Ustvari rele create_unavailable_domain: Ustvari domeno, ki ni na voljo create_user_role: Ustvari vlogo + create_username_block: Ustvari pravilo uporabniškega imena demote_user: Ponižaj uporabnika destroy_announcement: Izbriši obvestilo destroy_canonical_email_block: Izbriši blokado e-naslova @@ -209,6 +212,7 @@ sl: destroy_status: Izbriši objavo destroy_unavailable_domain: Izbriši nedosegljivo domeno destroy_user_role: Uniči vlogo + destroy_username_block: Izbriši pravilo uporabniškega imena disable_2fa_user: Onemogoči disable_custom_emoji: Onemogoči emotikon po meri disable_relay: Onemogoči rele @@ -243,6 +247,7 @@ sl: update_report: Posodobi poročilo update_status: Posodobi objavo update_user_role: Posodobi vlogo + update_username_block: Posodobi pravilo uporabniškega imena actions: approve_appeal_html: "%{name} je ugodil pritožbi uporabnika %{target} na moderatorsko odločitev" approve_user_html: "%{name} je odobril/a registracijo iz %{target}" @@ -498,21 +503,30 @@ sl: fasp: debug: callbacks: + created_at: Ustvarjeno delete: Izbriši ip: Naslov IP + request_body: Telo zahteve providers: + active: Dejaven base_url: Osnovna povezava callback: Povratni klic delete: Izbriši edit: Uredi ponudnika finish_registration: Dokončaj registracijo name: Ime + providers: Ponudniki + public_key_fingerprint: Prstni odtis javnega ključa + registration_requested: Registracija je obvezna registrations: confirm: Potrdi reject: Zavrni + title: Potrdri registracijo FASP save: Shrani sign_in: Prijava status: Stanje + title: Ponudniki dodatnih storitev Fediverse + title: FASP follow_recommendations: description_html: "Sledi priporočilom pomaga novim uporabnikom, da hitro najdejo zanimivo vsebino. Če uporabnik ni dovolj komuniciral z drugimi, da bi oblikoval prilagojena priporočila za sledenje, se namesto tega priporočajo ti računi. Dnevno se ponovno izračunajo iz kombinacije računov z najvišjimi nedavnimi angažiranostmi in najvišjim številom krajevnih sledilcev za določen jezik." language: Za jezik @@ -844,10 +858,15 @@ sl: publish_statistics: Objavi statistiko title: Razkrivanje trends: Trendi + wrapstodon: Wrapstodon domain_blocks: all: Vsem disabled: Nikomur users: Prijavljenim krajevnim uporabnikom + feed_access: + modes: + authenticated: Samo overjeni uporabniki + public: Vsi landing_page: values: trends: Trendi @@ -1103,13 +1122,23 @@ sl: trending: V porastu username_blocks: add_new: Dodaj novo + block_registrations: Blokiraj registracije comparison: contains: Vsebuje + equals: Je enako contains_html: Vsebuje %{string} + created_msg: Pravilo uporabniškega imena uspešno ustvarjeno delete: Izbriši + edit: + title: Uredi pravilo uporabniškega imena + matches_exactly_html: Je enako %{string} new: create: Ustvari pravilo + title: Ustvari novo pravilo uporabniškega imena + no_username_block_selected: Nobeno pravilo uporabniškega imena ni bilo spremenjeno, ker nobeno ni bilo izbrano not_permitted: Ni dovoljeno + title: Pravila uporabniškega imena + updated_msg: Pravilo uporabniškega imena uspešno posodobljeno warning_presets: add_new: Dodaj novo delete: Izbriši @@ -1302,6 +1331,10 @@ sl: hint_html: "Namig: naslednjo uro vas ne bomo več vprašali po vašem geslu." invalid_password: Neveljavno geslo prompt: Potrdite geslo za nadaljevanje + color_scheme: + auto: Samodejno + contrast: + auto: Samodejno crypto: errors: invalid_key: ni veljaven ključ Ed25519 ali Curve25519 @@ -1629,6 +1662,7 @@ sl: author_html: Avtor/ica %{name} potentially_sensitive_content: action: Kliknite za prikaz + confirm_visit: Ali ste prepričani, da želite odpreti to povezavo? hide_button: Skrij lists: errors: @@ -1727,10 +1761,13 @@ sl: body: "%{name} vas je omenil/a v:" subject: "%{name} vas je omenil/a" title: Nova omemba + moderation_warning: + subject: Prejeli ste opozorilo moderatorjev poll: subject: Anketa, ki jo je pripravil/a %{name}, se je iztekla quote: body: 'Vašo objavo je citiral/a %{name}:' + subject: "%{name} je citiral/a vašo objavo" title: Nov citat reblog: body: 'Vašo objavo je izpostavil/a %{name}:' @@ -1962,6 +1999,7 @@ sl: reblog: Izpostavitev ne more biti pripeta quote_error: not_available: Objava ni na voljo + revoked: Avtor je umaknil objavo quote_policies: followers: Samo sledilci nobody: Samo jaz @@ -2191,8 +2229,11 @@ sl: error: Pri brisanju vašega varnostnega ključa je prišlo do težav. Poskusite znova. success: Vaš varnostni ključ je bil uspešno izbrisan. invalid_credential: Neveljaven varnostni ključ + nickname: Vzdevek nickname_hint: Vnesite vzdevek svojega novega varnostnega ključa not_enabled: Niste še omogočili WebAuthn not_supported: Ta brskalnik ne podpira varnostnih ključev otp_required: Za uporabo varnostnih ključev morate najprej omogočiti 2FA (dvostopenjsko overjanje). registered_on: Datum registracije %{date} + wrapstodon: + title: Wrapstodon %{year} za %{name} From 9b6500f74a391c8de80c67c2227bf99091cac762 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 19 Jan 2026 11:36:58 +0100 Subject: [PATCH 037/145] Skip tombstone creation on deleting from 404 (#37533) --- app/services/activitypub/fetch_remote_status_service.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index 0473bb5939f..e08f82f7d9a 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -92,7 +92,6 @@ class ActivityPub::FetchRemoteStatusService < BaseService existing_status = Status.remote.find_by(uri: uri) if existing_status&.distributable? Rails.logger.debug { "FetchRemoteStatusService - Got 404 for orphaned status with URI #{uri}, deleting" } - Tombstone.find_or_create_by(uri: uri, account: existing_status.account) RemoveStatusService.new.call(existing_status, redraft: false) end end From 220115757d45f1bb3f520df150023af8b5e61071 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 19 Jan 2026 14:47:27 +0100 Subject: [PATCH 038/145] Fix potential duplicate handling of quote accept/reject/delete (#37537) --- app/lib/activitypub/activity/accept.rb | 2 +- app/lib/activitypub/activity/delete.rb | 2 +- app/models/quote.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/lib/activitypub/activity/accept.rb b/app/lib/activitypub/activity/accept.rb index 144ba9645c5..92a8190c038 100644 --- a/app/lib/activitypub/activity/accept.rb +++ b/app/lib/activitypub/activity/accept.rb @@ -46,7 +46,7 @@ class ActivityPub::Activity::Accept < ActivityPub::Activity def accept_quote!(quote) approval_uri = value_or_id(first_of_value(@json['result'])) - return if unsupported_uri_scheme?(approval_uri) || quote.quoted_account != @account || !quote.status.local? + return if unsupported_uri_scheme?(approval_uri) || quote.quoted_account != @account || !quote.status.local? || !quote.pending? # NOTE: we are not going through `ActivityPub::VerifyQuoteService` as the `Accept` is as authoritative # as the stamp, but this means we are not checking the stamp, which may lead to inconsistencies diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index 3e77f9b9556..f606d9520f0 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -56,7 +56,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity end def revoke_quote - @quote = Quote.find_by(approval_uri: object_uri, quoted_account: @account) + @quote = Quote.find_by(approval_uri: object_uri, quoted_account: @account, state: [:pending, :accepted]) return if @quote.nil? ActivityPub::Forwarder.new(@account, @json, @quote.status).forward! if @quote.status.present? diff --git a/app/models/quote.rb b/app/models/quote.rb index e81d427089d..4ad393e3a57 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -51,9 +51,9 @@ class Quote < ApplicationRecord def reject! if accepted? - update!(state: :revoked) + update!(state: :revoked, approval_uri: nil) elsif !revoked? - update!(state: :rejected) + update!(state: :rejected, approval_uri: nil) end end From ad77ee7f8ba6aa25ee36e3e21c38d9bbc252b097 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 19 Jan 2026 10:53:52 -0500 Subject: [PATCH 039/145] Add coverage for misc "policy" classes (#37525) --- spec/policies/announcement_policy_spec.rb | 37 +++++++++++++--- .../instance_moderation_note_policy_spec.rb | 42 +++++++++++++++++++ spec/policies/media_attachment_policy_spec.rb | 36 ++++++++++++++++ spec/policies/quote_policy_spec.rb | 29 +++++++++++++ spec/policies/username_block_policy_spec.rb | 20 +++++++++ 5 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 spec/policies/instance_moderation_note_policy_spec.rb create mode 100644 spec/policies/media_attachment_policy_spec.rb create mode 100644 spec/policies/quote_policy_spec.rb create mode 100644 spec/policies/username_block_policy_spec.rb diff --git a/spec/policies/announcement_policy_spec.rb b/spec/policies/announcement_policy_spec.rb index 2fec34f8e4b..a78b016a61c 100644 --- a/spec/policies/announcement_policy_spec.rb +++ b/spec/policies/announcement_policy_spec.rb @@ -3,20 +3,45 @@ require 'rails_helper' RSpec.describe AnnouncementPolicy do - let(:policy) { described_class } + subject { described_class } + let(:admin) { Fabricate(:admin_user).account } let(:john) { Fabricate(:account) } permissions :index?, :create?, :update?, :destroy? do context 'with an admin' do - it 'permits' do - expect(policy).to permit(admin, Announcement) - end + it { is_expected.to permit(admin, Announcement) } end context 'with a non-admin' do - it 'denies' do - expect(policy).to_not permit(john, Announcement) + it { is_expected.to_not permit(john, Announcement) } + end + end + + permissions :distribute? do + let(:announcement) { Fabricate :announcement } + + context 'with non admin role' do + it { is_expected.to_not permit(john, announcement) } + end + + context 'with admin role' do + context 'with unpublished announcement' do + let(:announcement) { Fabricate :announcement, published: false, scheduled_at: 5.days.from_now } + + it { is_expected.to_not permit(admin, announcement) } + end + + context 'with published already sent announcement' do + let(:announcement) { Fabricate :announcement, notification_sent_at: 3.days.ago } + + it { is_expected.to_not permit(admin, announcement) } + end + + context 'with published not sent announcement' do + let(:announcement) { Fabricate :announcement } + + it { is_expected.to permit(admin, announcement) } end end end diff --git a/spec/policies/instance_moderation_note_policy_spec.rb b/spec/policies/instance_moderation_note_policy_spec.rb new file mode 100644 index 00000000000..66b4e7f937f --- /dev/null +++ b/spec/policies/instance_moderation_note_policy_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe InstanceModerationNotePolicy do + subject { described_class } + + let(:admin) { Fabricate(:admin_user).account } + let(:account) { Fabricate(:account) } + + permissions :create? do + context 'when admin' do + it { is_expected.to permit(admin, InstanceModerationNote.new) } + end + + context 'when not admin' do + it { is_expected.to_not permit(account, InstanceModerationNote.new) } + end + end + + permissions :destroy? do + context 'when owner of note' do + let(:note) { Fabricate :instance_moderation_note, account: account } + + it { is_expected.to permit(account, note) } + end + + context 'when not owner of note' do + context 'when admin and overrides' do + let(:note) { Fabricate :instance_moderation_note } + + it { is_expected.to permit(admin, note) } + end + + context 'when admin and does not override' do + let(:note) { Fabricate :instance_moderation_note, account: Fabricate(:admin_user).account } + + it { is_expected.to_not permit(admin, note) } + end + end + end +end diff --git a/spec/policies/media_attachment_policy_spec.rb b/spec/policies/media_attachment_policy_spec.rb new file mode 100644 index 00000000000..d194cd819c6 --- /dev/null +++ b/spec/policies/media_attachment_policy_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe MediaAttachmentPolicy do + subject { described_class } + + let(:admin) { Fabricate(:admin_user).account } + let(:account) { Fabricate(:account) } + + permissions :download? do + context 'when attachment is on private discarded status' do + let(:media_attachment) { Fabricate.build :media_attachment, status: Fabricate.build(:status, deleted_at: 5.days.ago, visibility: :private) } + + context 'when admin' do + it { is_expected.to permit(admin, media_attachment) } + end + + context 'when not admin' do + it { is_expected.to_not permit(account, media_attachment) } + end + end + + context 'when attachment is on public status' do + let(:media_attachment) { Fabricate.build :media_attachment, status: Fabricate.build(:status, visibility: :public) } + + context 'when admin' do + it { is_expected.to permit(admin, media_attachment) } + end + + context 'when not admin' do + it { is_expected.to permit(account, media_attachment) } + end + end + end +end diff --git a/spec/policies/quote_policy_spec.rb b/spec/policies/quote_policy_spec.rb new file mode 100644 index 00000000000..71708a9b715 --- /dev/null +++ b/spec/policies/quote_policy_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe QuotePolicy do + subject { described_class } + + let(:account) { Fabricate(:account) } + + permissions :revoke? do + context 'when quote matches the revoking account' do + let(:quote) { Fabricate.build :quote, quoted_account_id: account.id } + + it { is_expected.to permit(account, quote) } + end + + context 'when quote does not match the revoking account' do + let(:quote) { Fabricate.build :quote, quoted_account_id: Fabricate(:account).id } + + it { is_expected.to_not permit(account, quote) } + end + + context 'when quote does not have quoted account id' do + let(:quote) { Fabricate.build :quote } + + it { is_expected.to_not permit(account, quote) } + end + end +end diff --git a/spec/policies/username_block_policy_spec.rb b/spec/policies/username_block_policy_spec.rb new file mode 100644 index 00000000000..5092f71274d --- /dev/null +++ b/spec/policies/username_block_policy_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe UsernameBlockPolicy do + subject { described_class } + + let(:admin) { Fabricate(:admin_user).account } + let(:account) { Fabricate(:account) } + + permissions :index?, :create?, :update?, :destroy? do + context 'when admin' do + it { is_expected.to permit(admin, UsernameBlock.new) } + end + + context 'when not admin' do + it { is_expected.to_not permit(account, UsernameBlock.new) } + end + end +end From 51224bb437f195922d9bfa0680e011dcc5a54220 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Tue, 20 Jan 2026 09:27:05 +0100 Subject: [PATCH 040/145] Add "featured collections" collection to actors (#37512) --- .../featured_collections_controller.rb | 77 +++++++++ app/models/concerns/account/interactions.rb | 7 + app/policies/account_policy.rb | 4 + app/policies/collection_policy.rb | 14 +- .../activitypub/actor_serializer.rb | 7 + .../activitypub/collection_serializer.rb | 2 + config/routes.rb | 2 + .../concerns/account/interactions_spec.rb | 38 +++++ spec/policies/account_policy_spec.rb | 20 +++ .../activitypub/featured_collections_spec.rb | 153 ++++++++++++++++++ .../activitypub/collection_serializer_spec.rb | 6 + 11 files changed, 317 insertions(+), 13 deletions(-) create mode 100644 app/controllers/activitypub/featured_collections_controller.rb create mode 100644 spec/requests/activitypub/featured_collections_spec.rb diff --git a/app/controllers/activitypub/featured_collections_controller.rb b/app/controllers/activitypub/featured_collections_controller.rb new file mode 100644 index 00000000000..872d03423d2 --- /dev/null +++ b/app/controllers/activitypub/featured_collections_controller.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +class ActivityPub::FeaturedCollectionsController < ApplicationController + include SignatureAuthentication + include Authorization + include AccountOwnedConcern + + PER_PAGE = 5 + + vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' } + + before_action :check_feature_enabled + before_action :require_account_signature!, if: -> { authorized_fetch_mode? } + before_action :set_collections + + skip_around_action :set_locale + skip_before_action :require_functional!, unless: :limited_federation_mode? + + def index + respond_to do |format| + format.json do + expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?) + + render json: collection_presenter, + serializer: ActivityPub::CollectionSerializer, + adapter: ActivityPub::Adapter, + content_type: 'application/activity+json' + end + end + end + + private + + def set_collections + authorize @account, :index_collections? + @collections = @account.collections.page(params[:page]).per(PER_PAGE) + rescue Mastodon::NotPermittedError + not_found + end + + def page_requested? + params[:page].present? + end + + def next_page_url + ap_account_featured_collections_url(@account, page: @collections.next_page) if @collections.respond_to?(:next_page) + end + + def prev_page_url + ap_account_featured_collections_url(@account, page: @collections.prev_page) if @collections.respond_to?(:prev_page) + end + + def collection_presenter + if page_requested? + ActivityPub::CollectionPresenter.new( + id: ap_account_featured_collections_url(@account, page: params.fetch(:page, 1)), + type: :unordered, + size: @account.collections.count, + items: @collections, + part_of: ap_account_featured_collections_url(@account), + next: next_page_url, + prev: prev_page_url + ) + else + ActivityPub::CollectionPresenter.new( + id: ap_account_featured_collections_url(@account), + type: :unordered, + size: @account.collections.count, + first: ap_account_featured_collections_url(@account, page: 1) + ) + end + end + + def check_feature_enabled + raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled? + end +end diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb index c51ccf1229e..d4a415ee31b 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -164,6 +164,13 @@ module Account::Interactions end end + def blocking_or_domain_blocking?(other_account) + return true if blocking?(other_account) + return false if other_account.domain.blank? + + domain_blocking?(other_account.domain) + end + def muting?(other_account) other_id = other_account.is_a?(Account) ? other_account.id : other_account diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index ab3b41d6280..1fef35714cd 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -68,4 +68,8 @@ class AccountPolicy < ApplicationPolicy def feature? record.featureable? && !current_account.blocking?(record) && !current_account.blocked_by?(record) end + + def index_collections? + current_account.nil? || !record.blocking_or_domain_blocking?(current_account) + end end diff --git a/app/policies/collection_policy.rb b/app/policies/collection_policy.rb index 70a869d16ad..4d100c0e32f 100644 --- a/app/policies/collection_policy.rb +++ b/app/policies/collection_policy.rb @@ -6,7 +6,7 @@ class CollectionPolicy < ApplicationPolicy end def show? - current_account.nil? || (!owner_blocking? && !owner_blocking_domain?) + current_account.nil? || !owner.blocking_or_domain_blocking?(current_account) end def create? @@ -27,18 +27,6 @@ class CollectionPolicy < ApplicationPolicy current_account == owner end - def owner_blocking_domain? - return false if current_account.nil? || current_account.domain.nil? - - owner.domain_blocking?(current_account.domain) - end - - def owner_blocking? - return false if current_account.nil? - - current_account.blocked_by?(owner) - end - def owner record.account end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index c19d42bfb43..ff1a70104b8 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -19,6 +19,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer :discoverable, :indexable, :published, :memorial attribute :interaction_policy, if: -> { Mastodon::Feature.collections_enabled? } + attribute :featured_collections, if: -> { Mastodon::Feature.collections_enabled? } has_one :public_key, serializer: ActivityPub::PublicKeySerializer @@ -177,6 +178,12 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer } end + def featured_collections + return nil if instance_actor? + + ap_account_featured_collections_url(object.id) + end + class CustomEmojiSerializer < ActivityPub::EmojiSerializer end diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb index 1b410cecaef..ba0d17f5408 100644 --- a/app/serializers/activitypub/collection_serializer.rb +++ b/app/serializers/activitypub/collection_serializer.rb @@ -18,6 +18,8 @@ class ActivityPub::CollectionSerializer < ActivityPub::Serializer ActivityPub::HashtagSerializer when 'ActivityPub::CollectionPresenter' ActivityPub::CollectionSerializer + when 'Collection' + ActivityPub::FeaturedCollectionSerializer when 'String' StringSerializer else diff --git a/config/routes.rb b/config/routes.rb index b3338a725eb..bf50b67fe13 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -124,6 +124,8 @@ Rails.application.routes.draw do scope path: 'ap', as: 'ap' do resources :accounts, path: 'users', only: [:show], param: :id, concerns: :account_resources do + resources :featured_collections, only: [:index], module: :activitypub + resources :statuses, only: [:show] do member do get :activity diff --git a/spec/models/concerns/account/interactions_spec.rb b/spec/models/concerns/account/interactions_spec.rb index cc50c465517..5bca7959080 100644 --- a/spec/models/concerns/account/interactions_spec.rb +++ b/spec/models/concerns/account/interactions_spec.rb @@ -450,6 +450,44 @@ RSpec.describe Account::Interactions do end end + describe '#blocking_or_domain_blocking?' do + subject { account.blocking_or_domain_blocking?(target_account) } + + context 'when blocking target_account' do + before do + account.block_relationships.create(target_account: target_account) + end + + it 'returns true' do + result = nil + expect { result = subject }.to execute_queries + + expect(result).to be true + end + end + + context 'when blocking the domain' do + let(:target_account) { Fabricate(:remote_account) } + + before do + account_domain_block = Fabricate(:account_domain_block, domain: target_account.domain) + account.domain_blocks << account_domain_block + end + + it 'returns true' do + result = nil + expect { result = subject }.to execute_queries + expect(result).to be true + end + end + + context 'when blocking neither target_account nor its domain' do + it 'returns false' do + expect(subject).to be false + end + end + end + describe '#muting?' do subject { account.muting?(target_account) } diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb index f877bded252..96fcbdb4d82 100644 --- a/spec/policies/account_policy_spec.rb +++ b/spec/policies/account_policy_spec.rb @@ -188,4 +188,24 @@ RSpec.describe AccountPolicy do end end end + + permissions :index_collections? do + it 'permits when no user is given' do + expect(subject).to permit(nil, john) + end + + it 'permits unblocked users' do + expect(subject).to permit(john, john) + expect(subject).to permit(alice, john) + end + + it 'denies blocked users' do + domain_blocked_user = Fabricate(:remote_account) + john.block_domain!(domain_blocked_user.domain) + john.block!(alice) + + expect(subject).to_not permit(domain_blocked_user, john) + expect(subject).to_not permit(alice, john) + end + end end diff --git a/spec/requests/activitypub/featured_collections_spec.rb b/spec/requests/activitypub/featured_collections_spec.rb new file mode 100644 index 00000000000..09a17c53bea --- /dev/null +++ b/spec/requests/activitypub/featured_collections_spec.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Collections' do + describe 'GET /ap/users/@:account_id/featured_collections', feature: :collections do + subject { get ap_account_featured_collections_path(account.id, format: :json) } + + let(:collection) { Fabricate(:collection) } + let(:account) { collection.account } + + context 'when signed out' do + context 'when account is permanently suspended' do + before do + account.suspend! + account.deletion_request.destroy + end + + it 'returns http gone' do + subject + + expect(response) + .to have_http_status(410) + end + end + + context 'when account is temporarily suspended' do + before { account.suspend! } + + it 'returns http forbidden' do + subject + + expect(response) + .to have_http_status(403) + end + end + + context 'when account is accessible' do + it 'renders ActivityPub Collection successfully', :aggregate_failures do + subject + + expect(response) + .to have_http_status(200) + .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie') + + expect(response.headers).to include( + 'Content-Type' => include('application/activity+json') + ) + expect(response.parsed_body) + .to include({ + 'type' => 'Collection', + 'totalItems' => 1, + 'first' => match(%r{^https://.*page=1.*$}), + }) + end + + context 'when requesting the first page' do + subject { get ap_account_featured_collections_path(account.id, page: 1, format: :json) } + + context 'when account has many collections' do + before do + Fabricate.times(5, :collection, account:) + end + + it 'includes a link to the next page', :aggregate_failures do + subject + + expect(response) + .to have_http_status(200) + + expect(response.parsed_body) + .to include({ + 'type' => 'CollectionPage', + 'totalItems' => 6, + 'next' => match(%r{^https://.*page=2.*$}), + }) + end + end + end + end + end + + context 'when signed in' do + let(:user) { Fabricate(:user) } + + before do + post user_session_path, params: { user: { email: user.email, password: user.password } } + end + + context 'when account blocks user' do + before { account.block!(user.account) } + + it 'returns http not found' do + subject + + expect(response) + .to have_http_status(404) + end + end + end + + context 'with "HTTP Signature" access signed by a remote account' do + subject do + get ap_account_featured_collections_path(account.id, format: :json), + headers: nil, + sign_with: remote_account + end + + let(:remote_account) { Fabricate(:account, domain: 'host.example') } + + context 'when account blocks the remote account' do + before { account.block!(remote_account) } + + it 'returns http not found' do + subject + + expect(response) + .to have_http_status(404) + end + end + + context 'when account domain blocks the domain of the remote account' do + before { account.block_domain!(remote_account.domain) } + + it 'returns http not found' do + subject + + expect(response) + .to have_http_status(404) + end + end + + context 'with JSON' do + it 'renders ActivityPub FeaturedCollection object successfully', :aggregate_failures do + subject + + expect(response) + .to have_http_status(200) + .and have_cacheable_headers.with_vary('Accept, Accept-Language, Cookie') + + expect(response.headers).to include( + 'Content-Type' => include('application/activity+json') + ) + expect(response.parsed_body) + .to include({ + 'type' => 'Collection', + 'totalItems' => 1, + }) + end + end + end + end +end diff --git a/spec/serializers/activitypub/collection_serializer_spec.rb b/spec/serializers/activitypub/collection_serializer_spec.rb index 7726df914f2..d7099ba3d5a 100644 --- a/spec/serializers/activitypub/collection_serializer_spec.rb +++ b/spec/serializers/activitypub/collection_serializer_spec.rb @@ -35,5 +35,11 @@ RSpec.describe ActivityPub::CollectionSerializer do it { is_expected.to eq(ActiveModel::Serializer::CollectionSerializer) } end + + context 'with a Collection' do + let(:model) { Collection.new } + + it { is_expected.to eq(ActivityPub::FeaturedCollectionSerializer) } + end end end From 8ff7662918dbe33ffe414a7bcbc3eac42dac11cd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 08:31:49 +0000 Subject: [PATCH 041/145] New Crowdin Translations (automated) (#37545) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/et.json | 46 ++++++++++++++----------- app/javascript/mastodon/locales/tr.json | 4 +++ config/locales/devise.et.yml | 12 +++---- config/locales/et.yml | 18 +++++----- config/locales/simple_form.et.yml | 8 ++--- 5 files changed, 48 insertions(+), 40 deletions(-) diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 854d70b0ce1..e4c303153d8 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -2,15 +2,15 @@ "about.blocks": "Modereeritavad serverid", "about.contact": "Kontakt:", "about.default_locale": "Vaikimisi", - "about.disclaimer": "Mastodon on tasuta ja vaba tarkvara ning Mastodon gGmbH kaubamärk.", + "about.disclaimer": "Mastodon on vaba, tasuta ja avatud lähtekoodiga tarkvara ning Mastodon gGmbH kaubamärk.", "about.domain_blocks.no_reason_available": "Põhjus on teadmata", - "about.domain_blocks.preamble": "Mastodon lubab tavaliselt vaadata sisu ning suhelda kasutajatega ükskõik millisest teisest fediversumi serverist. Need on erandid, mis on paika pandud sellel kindlal serveril.", - "about.domain_blocks.silenced.explanation": "Sa ei näe üldiselt profiile ja sisu sellelt serverilt, kui sa just tahtlikult seda ei otsi või jälgimise moel nõusolekut ei anna.", + "about.domain_blocks.preamble": "Mastodon lubab üldiselt vaadata sisu ning suhelda kasutajatega ükskõik millisest teisest födiversumi serverist. Need on erandid, mis kehtivad selles kindlas serveris.", + "about.domain_blocks.silenced.explanation": "Sa üldjuhul ei näe profiile ja sisu sellest serverist, kui sa just tahtlikult neid ei otsi või jälgimise moel nõusolekut ei anna.", "about.domain_blocks.silenced.title": "Piiratud", - "about.domain_blocks.suspended.explanation": "Mitte mingeid andmeid sellelt serverilt ei töödelda, salvestata ega vahetata, tehes igasuguse interaktsiooni või kirjavahetuse selle serveri kasutajatega võimatuks.", + "about.domain_blocks.suspended.explanation": "Mitte mingeid andmeid sellelt serverilt ei töödelda, salvestata ega vahetata, tehes igasuguse suhestumise või infovahetuse selle serveri kasutajatega võimatuks.", "about.domain_blocks.suspended.title": "Peatatud", "about.language_label": "Keel", - "about.not_available": "See info ei ole sellel serveril saadavaks tehtud.", + "about.not_available": "See info ei ole selles serveris saadavaks tehtud.", "about.powered_by": "Hajutatud sotsiaalmeedia, mille taga on {mastodon}", "about.rules": "Serveri reeglid", "account.account_note_header": "Isiklik märge", @@ -18,19 +18,19 @@ "account.badges.bot": "Robot", "account.badges.group": "Grupp", "account.block": "Blokeeri @{name}", - "account.block_domain": "Peida kõik domeenist {domain}", + "account.block_domain": "Blokeeri kõik domeenist {domain}", "account.block_short": "Blokeerimine", "account.blocked": "Blokeeritud", "account.blocking": "Blokeeritud kasutaja", "account.cancel_follow_request": "Võta jälgimistaotlus tagasi", "account.copy": "Kopeeri profiili link", "account.direct": "Maini privaatselt @{name}", - "account.disable_notifications": "Peata teavitused @{name} postitustest", + "account.disable_notifications": "Ära teavita, kui @{name} postitab", "account.domain_blocking": "Blokeeritud domeen", "account.edit_profile": "Muuda profiili", "account.edit_profile_short": "Muuda", - "account.enable_notifications": "Teavita mind @{name} postitustest", - "account.endorse": "Too profiilil esile", + "account.enable_notifications": "Teavita mind, kui {name} postitab", + "account.endorse": "Too profiilis esile", "account.familiar_followers_many": "Jälgijateks {name1}, {name2} ja veel {othersCount, plural, one {üks kasutaja, keda tead} other {# kasutajat, keda tead}}", "account.familiar_followers_one": "Jälgijaks {name1}", "account.familiar_followers_two": "Jälgijateks {name1} ja {name2}", @@ -57,11 +57,12 @@ "account.go_to_profile": "Mine profiilile", "account.hide_reblogs": "Peida @{name} jagamised", "account.in_memoriam": "In Memoriam.", + "account.joined_long": "Liitus {date}", "account.joined_short": "Liitus", "account.languages": "Muuda tellitud keeli", "account.link_verified_on": "Selle lingi autorsust kontrolliti {date}", "account.locked_info": "Selle konto privaatsussätteks on lukustatud. Omanik vaatab käsitsi üle, kes teda jälgida saab.", - "account.media": "Meedia", + "account.media": "Meedium", "account.mention": "Maini @{name}", "account.moved_to": "{name} on teada andnud, et ta uus konto on nüüd:", "account.mute": "Summuta @{name}", @@ -81,20 +82,22 @@ "account.share": "Jaga @{name} profiili", "account.show_reblogs": "Näita @{name} jagamisi", "account.statuses_counter": "{count, plural, one {{counter} postitus} other {{counter} postitust}}", - "account.unblock": "Eemalda blokeering @{name}", - "account.unblock_domain": "Tee {domain} nähtavaks", + "account.unblock": "Lõpeta {name} kasutaja blokeerimine", + "account.unblock_domain": "Lõpeta {domain} domeeni blokeerimine", "account.unblock_domain_short": "Lõpeta blokeerimine", - "account.unblock_short": "Eemalda blokeering", + "account.unblock_short": "Lõpeta blokeerimine", "account.unendorse": "Ära kuva profiilil", - "account.unfollow": "Jälgid", + "account.unfollow": "Ära jälgi", "account.unmute": "Lõpeta {name} kasutaja summutamine", "account.unmute_notifications_short": "Lõpeta teavituste summutamine", "account.unmute_short": "Lõpeta summutamine", + "account_fields_modal.close": "Sulge", + "account_fields_modal.title": "Kasutaja teave: {name}", "account_note.placeholder": "Klõpsa märke lisamiseks", "admin.dashboard.daily_retention": "Kasutajate päevane allesjäämine peale registreerumist", "admin.dashboard.monthly_retention": "Kasutajate kuine allesjäämine peale registreerumist", "admin.dashboard.retention.average": "Keskmine", - "admin.dashboard.retention.cohort": "Registreerumiskuu", + "admin.dashboard.retention.cohort": "Liitumiskuu", "admin.dashboard.retention.cohort_size": "Uued kasutajad", "admin.impact_report.instance_accounts": "Kontode profiilid, mille see kustutaks", "admin.impact_report.instance_followers": "Jälgijad, kelle meie kasutajad kaotaks", @@ -103,11 +106,11 @@ "alert.rate_limited.message": "Palun proovi uuesti pärast {retry_time, time, medium}.", "alert.rate_limited.title": "Kiiruspiirang", "alert.unexpected.message": "Tekkis ootamatu viga.", - "alert.unexpected.title": "Oih!", - "alt_text_badge.title": "Alternatiivtekst", - "alt_text_modal.add_alt_text": "Lisa alt-tekst", + "alert.unexpected.title": "Vaat kus lops!", + "alt_text_badge.title": "Selgitustekst", + "alt_text_modal.add_alt_text": "Lisa selgitustekst", "alt_text_modal.add_text_from_image": "Lisa tekst pildilt", - "alt_text_modal.cancel": "Tühista", + "alt_text_modal.cancel": "Katkesta", "alt_text_modal.change_thumbnail": "Muuda pisipilti", "alt_text_modal.describe_for_people_with_hearing_impairments": "Kirjelda seda kuulmispuudega inimeste jaoks…", "alt_text_modal.describe_for_people_with_visual_impairments": "Kirjelda seda nägemispuudega inimeste jaoks…", @@ -119,7 +122,7 @@ "annual_report.announcement.description": "Vaata teavet oma suhestumise kohta Mastodonis eelmisel aastal.", "annual_report.announcement.title": "{year}. aasta Mastodoni kokkuvõte on valmis", "annual_report.nav_item.badge": "Uus", - "annual_report.shared_page.donate": "Anneta", + "annual_report.shared_page.donate": "Toeta rahaliselt", "annual_report.shared_page.footer": "Loodud {heart} Mastodoni meeskonna poolt", "annual_report.shared_page.footer_server_info": "{username} kasutab {domain}-i, üht paljudest kogukondadest, mis toimivad Mastodonil.", "annual_report.summary.archetype.booster.desc_public": "{name} jätkas postituste otsimist, et neid edendada, tugevdades teisi loojaid täiusliku täpsusega.", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}", "loading_indicator.label": "Laadimine…", "media_gallery.hide": "Peida", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.", "mute_modal.hide_from_notifications": "Peida teavituste hulgast", "mute_modal.hide_options": "Peida valikud", @@ -767,7 +771,7 @@ "onboarding.profile.upload_avatar": "Laadi üles profiilipilt", "onboarding.profile.upload_header": "Laadi üles profiili päis", "password_confirmation.exceeds_maxlength": "Salasõnakinnitus on pikem kui salasõna maksimumpikkus", - "password_confirmation.mismatching": "Salasõnakinnitus ei sobi kokku", + "password_confirmation.mismatching": "Salasõnad ei klapi", "picture_in_picture.restore": "Pane tagasi", "poll.closed": "Suletud", "poll.refresh": "Värskenda", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 4356fce6c5d..1d8a0a247f4 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Profile git", "account.hide_reblogs": "@{name} kişisinin yeniden paylaşımlarını gizle", "account.in_memoriam": "Hatırasına.", + "account.joined_long": "{date} tarihinde katıldı", "account.joined_short": "Katıldı", "account.languages": "Abone olunan dilleri değiştir", "account.link_verified_on": "Bu bağlantının sahipliği {date} tarihinde denetlendi", @@ -90,6 +91,8 @@ "account.unmute": "@{name} adlı kişinin sesini aç", "account.unmute_notifications_short": "Bildirimlerin sesini aç", "account.unmute_short": "Susturmayı kaldır", + "account_fields_modal.close": "Kapat", + "account_fields_modal.title": "{name} bilgileri", "account_note.placeholder": "Not eklemek için tıklayın", "admin.dashboard.daily_retention": "Kayıttan sonra günlük kullanıcı saklama oranı", "admin.dashboard.monthly_retention": "Kayıttan sonra aylık kullanıcı saklama oranı", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# yeni öğe} other {# yeni öğe}}", "loading_indicator.label": "Yükleniyor…", "media_gallery.hide": "Gizle", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "{disabledAccount} hesabınız, {movedToAccount} hesabına taşıdığınız için şu an devre dışı.", "mute_modal.hide_from_notifications": "Bildirimlerde gizle", "mute_modal.hide_options": "Seçenekleri gizle", diff --git a/config/locales/devise.et.yml b/config/locales/devise.et.yml index 5843761ddba..6ed4c2dd701 100644 --- a/config/locales/devise.et.yml +++ b/config/locales/devise.et.yml @@ -29,23 +29,23 @@ et: title: Kinnita e-postiaadress email_changed: explanation: 'Sinu konto e-postiaadress muudetakse:' - extra: Kui sa ei muutnud oma e-posti, on tõenäoline, et kellelgi on ligipääs su kontole. Palun muuda koheselt oma salasõna. Kui oled aga oma kontost välja lukustatud, võta ühendust oma serveri administraatoriga. + extra: Kui sa ei muutnud oma e-posti, on tõenäoline, et kellelgi on ligipääs su kontole. Palun muuda koheselt oma salasõna. Kui oled aga oma kontole ligipääsu kaotanud, palun võta kohe ühendust oma serveri haldajaga. subject: 'Mastodon: e-post muudetud' title: Uus e-postiaadress password_change: explanation: Konto salasõna on vahetatud. extra: Kui sa ei muutnud oma salasõna, on tõenäoline, et keegi on su kontole ligi pääsenud. Palun muuda viivitamata oma salasõna. Kui sa oma kontole ligi ei pääse, võta ühendust serveri haldajaga. - subject: 'Mastodon: salasõna muudetud' - title: Salasõna muudetud + subject: 'Mastodon: salasõna on muudetud' + title: Salasõna on muudetud reconfirmation_instructions: explanation: Kinnita uus aadress, et oma e-posti aadress muuta. extra: Kui see muudatus pole sinu poolt algatatud, palun eira seda kirja. Selle Mastodoni konto e-postiaadress ei muutu enne, kui vajutad üleval olevale lingile. subject: 'Mastodon: kinnita e-postiaadress %{instance} jaoks' title: Kinnita e-postiaadress reset_password_instructions: - action: Salasõna muutmine - explanation: Kontole on küsitud uut salasõna. - extra: Kui see tuleb üllatusena, võib seda kirja eirata. Salasõna ei muutu enne ülaoleva lingi külastamist ja uue salasõna määramist. + action: Muuda salasõna + explanation: Sa palusid oma kasutajakontole luua uus salasõna. + extra: Kui see tuleb üllatusena, võid seda kirja eirata. Salasõna ei muutu enne ülaoleva lingi külastamist ja uue salasõna sisestamist. subject: 'Mastodon: salasõna lähtestamisjuhendid' title: Salasõna lähtestamine two_factor_disabled: diff --git a/config/locales/et.yml b/config/locales/et.yml index 508329e996f..58df54c8dfb 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -142,7 +142,7 @@ et: security: Turvalisus security_measures: only_password: Ainult salasõna - password_and_2fa: Salasõna ja 2-etapine autentimine (2FA) + password_and_2fa: Salasõna ja kahefaktoriline autentimine (2FA) sensitive: Tundlik sisu sensitized: Märgitud kui tundlik sisu shared_inbox_url: Jagatud sisendkausta URL @@ -292,7 +292,7 @@ et: remove_avatar_user_html: "%{name} eemaldas %{target} avatari" reopen_report_html: "%{name} taasavas raporti %{target}" resend_user_html: "%{name} lähtestas %{target} kinnituskirja e-posti" - reset_password_user_html: "%{name} lähtestas %{target} salasõna" + reset_password_user_html: "%{name} lähtestas %{target} kasutaja salasõna" resolve_report_html: "%{name} lahendas raporti %{target}" sensitive_account_html: "%{name} märkis %{target} meedia kui tundlik sisu" silence_account_html: "%{name} piiras %{target} konto" @@ -787,7 +787,7 @@ et: manage_taxonomies: Halda taksonoomiaid manage_taxonomies_description: Luba kasutajatel populaarset sisu üle vaadata ning uuendada teemaviidete seadistusi manage_user_access: Halda kasutajate ligipääsu - manage_user_access_description: Võimaldab kasutajatel keelata teiste kasutajate kaheastmelise autentimise, muuta oma e-posti aadressi ja lähtestada oma parooli + manage_user_access_description: Võimaldab kasutajatel keelata teiste kasutajate kaheastmelise autentimise, muuta nende e-posti aadressi ja lähtestada oma salasõna manage_users: Kasutajate haldamine manage_users_description: Lubab kasutajail näha teiste kasutajate üksikasju ja teha nende suhtes modereerimisotsuseid manage_webhooks: Halda webhook'e @@ -1249,7 +1249,7 @@ et: suffix: Kasutajakontoga saad jälgida inimesi, postitada uudiseid ning pidada kirjavahetust ükskõik millise Mastodoni serveri kasutajatega ja muudki! didnt_get_confirmation: Ei saanud kinnituslinki? dont_have_your_security_key: Pole turvavõtit? - forgot_password: Salasõna ununenud? + forgot_password: Kas unustasid oma salasõna? invalid_reset_password_token: Salasõna lähtestusvõti on vale või aegunud. Palun taotle uus. link_to_otp: Kaheastmeline kood telefonist või taastekood link_to_webauth: Turvavõtmete seadme kasutamine @@ -1270,7 +1270,7 @@ et: register: Loo konto registration_closed: "%{instance} ei võta vastu uusi liikmeid" resend_confirmation: Saada kinnituslink uuesti - reset_password: Salasõna lähtestamine + reset_password: Lähtesta salasõna rules: accept: Nõus back: Tagasi @@ -1280,7 +1280,7 @@ et: title: Mõned põhireeglid. title_invited: Oled kutsutud. security: Turvalisus - set_new_password: Uue salasõna määramine + set_new_password: Sisesta uus salasõna setup: email_below_hint_html: Kontrolli rämpsposti kausta või palu uue kirja saatmist. Kui sinu e-posti aadress on vale, siis saad seda parandada. email_settings_hint_html: Klõpsa aadressile %{email} saadetud linki, et alustada Mastodoni kasutamist. Me oleme ootel. @@ -1316,9 +1316,9 @@ et: title: Autori tunnustamine challenge: confirm: Jätka - hint_html: "Nõuanne: Me ei küsi salasõna uuesti järgmise tunni jooksul." + hint_html: "Nõuanne: Me ei küsi sinu salasõna uuesti järgmise tunni jooksul." invalid_password: Vigane salasõna - prompt: Jätkamiseks salasõna veelkord + prompt: Jätkamiseks korda salasõna color_scheme: auto: Auto dark: Tume @@ -1627,7 +1627,7 @@ et: password: salasõna sign_in_token: e-posti turvvakood webauthn: turvavõtmed - description_html: Kui paistab tundmatuid tegevusi, tuleks vahetada salasõna ja aktiveerida kaheastmeline autentimine. + description_html: Kui paistab tundmatuid tegevusi, palun vaheta salasõna ja aktiveeri kaheastmeline autentimine. empty: Autentimisajalugu pole saadaval failed_sign_in_html: Nurjunud sisenemine meetodiga %{method} aadressilt %{ip} (%{browser}) successful_sign_in_html: Edukas sisenemine meetodiga %{method} aadressilt %{ip} (%{browser}) diff --git a/config/locales/simple_form.et.yml b/config/locales/simple_form.et.yml index d51e816d2e5..2e45c6ffa07 100644 --- a/config/locales/simple_form.et.yml +++ b/config/locales/simple_form.et.yml @@ -51,7 +51,7 @@ et: inbox_url: Kopeeri soovitud sõnumivahendusserveri avalehe võrguaadress irreversible: Filtreeritud postitused kaovad taastamatult, isegi kui filter on hiljem eemaldatud locale: Kasutajaliidese, e-kirjade ja tõuketeadete keel - password: Vajalik on vähemalt 8 märki + password: Vajalik on vähemalt 8 tähemärki phrase: Kattub olenemata postituse teksti suurtähtedest või sisuhoiatusest scopes: Milliseid API-sid see rakendus tohib kasutada. Kui valid kõrgeima taseme, ei pea üksikuid eraldi valima. setting_advanced_layout: Näita Mastodoni mitme veeruga paigutuses, mispuhul näed korraga nii ajajoont, teavitusi, kui sinu valitud kolmandat veergu. Ei sobi kasutamiseks väikeste ekraanide puhul. @@ -113,7 +113,7 @@ et: trends: Trendid näitavad, millised postitused, teemaviited ja uudislood koguvad sinu serveris tähelepanu. wrapstodon: Paku kohalikele kasutajatele luua nende Mastodoni kasutamise aastast mänguline kokkuvõte. See võimalus on saadaval igal aastal 10. ja 31. detsembri vahel ja seda pakutakse kasutajatele, kes tegid vähemalt ühe avaliku või vaikse avaliku postituse ja kes kasutas aasta jooksul vähemalt ühte silti. form_challenge: - current_password: Turvalisse alasse sisenemine + current_password: Sisened turvalisse alasse imports: data: CSV fail eksporditi teisest Mastodoni serverist invite_request: @@ -214,8 +214,8 @@ et: avatar: Profiilipilt bot: See konto on robot chosen_languages: Keelte filtreerimine - confirm_new_password: Uue salasõna kinnitamine - confirm_password: Salasõna kinnitamine + confirm_new_password: Korda uut salasõna + confirm_password: Korda salasõna context: Filtreeri kontekste current_password: Kehtiv salasõna data: Andmed From fa9b905fdf110afc2e0e710283bea8e81c1161aa Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 20 Jan 2026 12:04:06 +0100 Subject: [PATCH 042/145] Improve `apiRequest` helper types (#37544) --- app/javascript/mastodon/api.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts index 1820e00a537..2af29c783e0 100644 --- a/app/javascript/mastodon/api.ts +++ b/app/javascript/mastodon/api.ts @@ -128,15 +128,18 @@ export default function api(withAuthorization = true) { } type ApiUrl = `v${1 | '1_alpha' | 2}/${string}`; -type RequestParamsOrData = Record; +type RequestParamsOrData = T | Record; -export async function apiRequest( +export async function apiRequest< + ApiResponse = unknown, + ApiParamsOrData = unknown, +>( method: Method, url: string, args: { signal?: AbortSignal; - params?: RequestParamsOrData; - data?: RequestParamsOrData; + params?: RequestParamsOrData; + data?: RequestParamsOrData; timeout?: number; } = {}, ) { @@ -149,30 +152,30 @@ export async function apiRequest( return data; } -export async function apiRequestGet( +export async function apiRequestGet( url: ApiUrl, - params?: RequestParamsOrData, + params?: RequestParamsOrData, ) { return apiRequest('GET', url, { params }); } -export async function apiRequestPost( +export async function apiRequestPost( url: ApiUrl, - data?: RequestParamsOrData, + data?: RequestParamsOrData, ) { return apiRequest('POST', url, { data }); } -export async function apiRequestPut( +export async function apiRequestPut( url: ApiUrl, - data?: RequestParamsOrData, + data?: RequestParamsOrData, ) { return apiRequest('PUT', url, { data }); } -export async function apiRequestDelete( - url: ApiUrl, - params?: RequestParamsOrData, -) { +export async function apiRequestDelete< + ApiResponse = unknown, + ApiParams = unknown, +>(url: ApiUrl, params?: RequestParamsOrData) { return apiRequest('DELETE', url, { params }); } From a1c17fef3aa863fc18eaa0941fa32f15222f859c Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 20 Jan 2026 12:10:46 +0100 Subject: [PATCH 043/145] Profile redesign: Account name (#37527) --- .../account/components/domain_pill.tsx | 10 ++- .../components/account_header.tsx | 62 +++++++---------- .../components/account_name.tsx | 68 +++++++++++++++++++ .../components/redesign.module.scss | 41 +++++++++++ 4 files changed, 140 insertions(+), 41 deletions(-) create mode 100644 app/javascript/mastodon/features/account_timeline/components/account_name.tsx diff --git a/app/javascript/mastodon/features/account/components/domain_pill.tsx b/app/javascript/mastodon/features/account/components/domain_pill.tsx index 13f5ebacf1e..1f334bc004a 100644 --- a/app/javascript/mastodon/features/account/components/domain_pill.tsx +++ b/app/javascript/mastodon/features/account/components/domain_pill.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import { useState, useRef, useCallback, useId } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -15,7 +16,9 @@ export const DomainPill: React.FC<{ domain: string; username: string; isSelf: boolean; -}> = ({ domain, username, isSelf }) => { + children?: ReactNode; + className?: string; +}> = ({ domain, username, isSelf, children, className }) => { const accessibilityId = useId(); const [open, setOpen] = useState(false); const [expanded, setExpanded] = useState(false); @@ -32,7 +35,9 @@ export const DomainPill: React.FC<{ return ( <> { @@ -47,7 +45,6 @@ export const AccountHeader: React.FC<{ hideTabs?: boolean; }> = ({ accountId, hideTabs }) => { const dispatch = useAppDispatch(); - const intl = useIntl(); const account = useAppSelector((state) => state.accounts.get(accountId)); const relationship = useAppSelector((state) => state.relationships.get(accountId), @@ -85,8 +82,6 @@ export const AccountHeader: React.FC<{ const suspendedOrHidden = hidden || account.suspended; const isLocal = !account.acct.includes('@'); - const username = account.acct.split('@')[0]; - const domain = isLocal ? localDomain : account.acct.split('@')[1]; return (
@@ -133,38 +128,27 @@ export const AccountHeader: React.FC<{ /> - + {!isRedesignEnabled() && ( + + )}
-
-

- - - - @{username} - @{domain} - - - {account.locked && ( - - )} - -

+
+ + {isRedesignEnabled() && }
diff --git a/app/javascript/mastodon/features/account_timeline/components/account_name.tsx b/app/javascript/mastodon/features/account_timeline/components/account_name.tsx new file mode 100644 index 00000000000..90ccf7486d3 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/account_name.tsx @@ -0,0 +1,68 @@ +import type { FC } from 'react'; + +import { useIntl } from 'react-intl'; + +import { DisplayName } from '@/mastodon/components/display_name'; +import { Icon } from '@/mastodon/components/icon'; +import { useAccount } from '@/mastodon/hooks/useAccount'; +import { useAppSelector } from '@/mastodon/store'; +import InfoIcon from '@/material-icons/400-24px/info.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; + +import { DomainPill } from '../../account/components/domain_pill'; +import { isRedesignEnabled } from '../common'; + +import classes from './redesign.module.scss'; + +export const AccountName: FC<{ accountId: string; className?: string }> = ({ + accountId, + className, +}) => { + const intl = useIntl(); + const account = useAccount(accountId); + const me = useAppSelector((state) => state.meta.get('me') as string); + const localDomain = useAppSelector( + (state) => state.meta.get('domain') as string, + ); + + if (!account) { + return null; + } + + const [username = '', domain = localDomain] = account.acct.split('@'); + + return ( +

+ + + + @{username} + {isRedesignEnabled() && '@'} + + {!isRedesignEnabled() && '@'} + {domain} + + + + {isRedesignEnabled() && } + + {!isRedesignEnabled() && account.locked && ( + + )} + +

+ ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss index dd09f199e56..757f5a42314 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -1,3 +1,44 @@ +.nameWrapper { + display: flex; + gap: 16px; +} + +.name { + flex-grow: 1; + font-size: 22px; + white-space: initial; + text-overflow: initial; + line-height: normal; + + :global(.icon-info) { + margin-left: 2px; + width: 1em; + height: 1em; + align-self: center; + } +} + +// Overrides .account__header__tabs__name h1 small +h1.name > small { + gap: 0; +} + +.domainPill { + appearance: none; + border: none; + background: none; + padding: 0; + text-decoration: underline; + color: inherit; + font-size: 1em; + font-weight: initial; + + &:global(.active) { + background: none; + color: inherit; + } +} + .fieldList { margin-top: 16px; } From 7cdc05b9a8b098ca0d369d1ba75396e4d6150c33 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Tue, 20 Jan 2026 14:22:54 +0100 Subject: [PATCH 044/145] Add `language` attribute to collections (#37549) --- .../api/v1_alpha/collections_controller.rb | 4 ++-- app/models/collection.rb | 2 ++ .../featured_collection_serializer.rb | 13 ++++++++++++- .../rest/base_collection_serializer.rb | 2 +- ...20260119153538_add_language_to_collections.rb | 7 +++++++ db/schema.rb | 3 ++- spec/models/collection_spec.rb | 6 ++++++ spec/requests/api/v1_alpha/collections_spec.rb | 1 + .../featured_collection_serializer_spec.rb | 16 ++++++++++++++++ .../rest/collection_serializer_spec.rb | 2 ++ 10 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20260119153538_add_language_to_collections.rb diff --git a/app/controllers/api/v1_alpha/collections_controller.rb b/app/controllers/api/v1_alpha/collections_controller.rb index d0c4e0f3f04..9d6b2f9a381 100644 --- a/app/controllers/api/v1_alpha/collections_controller.rb +++ b/app/controllers/api/v1_alpha/collections_controller.rb @@ -81,11 +81,11 @@ class Api::V1Alpha::CollectionsController < Api::BaseController end def collection_creation_params - params.permit(:name, :description, :sensitive, :discoverable, :tag_name, account_ids: []) + params.permit(:name, :description, :language, :sensitive, :discoverable, :tag_name, account_ids: []) end def collection_update_params - params.permit(:name, :description, :sensitive, :discoverable, :tag_name) + params.permit(:name, :description, :language, :sensitive, :discoverable, :tag_name) end def check_feature_enabled diff --git a/app/models/collection.rb b/app/models/collection.rb index b732a3d220b..334318b73d3 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -8,6 +8,7 @@ # description :text not null # discoverable :boolean not null # item_count :integer default(0), not null +# language :string # local :boolean not null # name :string not null # original_number_of_items :integer @@ -36,6 +37,7 @@ class Collection < ApplicationRecord presence: true, numericality: { greater_than_or_equal: 0 }, if: :remote? + validates :language, language: { if: :local?, allow_nil: true } validate :tag_is_usable validate :items_do_not_exceed_limit diff --git a/app/serializers/activitypub/featured_collection_serializer.rb b/app/serializers/activitypub/featured_collection_serializer.rb index e70d155a1a0..af4c5548514 100644 --- a/app/serializers/activitypub/featured_collection_serializer.rb +++ b/app/serializers/activitypub/featured_collection_serializer.rb @@ -17,9 +17,12 @@ class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer end end - attributes :id, :type, :total_items, :name, :summary, :attributed_to, + attributes :id, :type, :total_items, :name, :attributed_to, :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_many :collection_items, key: :ordered_items, serializer: FeaturedItemSerializer @@ -36,6 +39,10 @@ class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer object.description end + def summary_map + { object.language => object.description } + end + def attributed_to ActivityPub::TagManager.instance.uri_for(object.account) end @@ -51,4 +58,8 @@ class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer def updated object.updated_at.iso8601 end + + def language_present? + object.language.present? + end end diff --git a/app/serializers/rest/base_collection_serializer.rb b/app/serializers/rest/base_collection_serializer.rb index be26aac6fe2..6bb75e99a39 100644 --- a/app/serializers/rest/base_collection_serializer.rb +++ b/app/serializers/rest/base_collection_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class REST::BaseCollectionSerializer < ActiveModel::Serializer - attributes :id, :uri, :name, :description, :local, :sensitive, + attributes :id, :uri, :name, :description, :language, :local, :sensitive, :discoverable, :item_count, :created_at, :updated_at belongs_to :tag, serializer: REST::StatusSerializer::TagSerializer diff --git a/db/migrate/20260119153538_add_language_to_collections.rb b/db/migrate/20260119153538_add_language_to_collections.rb new file mode 100644 index 00000000000..066288b070a --- /dev/null +++ b/db/migrate/20260119153538_add_language_to_collections.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddLanguageToCollections < ActiveRecord::Migration[8.0] + def change + add_column :collections, :language, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 8dc1e88fd17..8801882808d 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.0].define(version: 2026_01_15_153219) do +ActiveRecord::Schema[8.0].define(version: 2026_01_19_153538) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -382,6 +382,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_01_15_153219) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "item_count", default: 0, null: false + t.string "language" t.index ["account_id"], name: "index_collections_on_account_id" t.index ["tag_id"], name: "index_collections_on_tag_id" end diff --git a/spec/models/collection_spec.rb b/spec/models/collection_spec.rb index bcc31fd0877..b50969b68a9 100644 --- a/spec/models/collection_spec.rb +++ b/spec/models/collection_spec.rb @@ -16,12 +16,18 @@ RSpec.describe Collection do it { is_expected.to_not allow_value(nil).for(:discoverable) } + it { is_expected.to allow_value('en').for(:language) } + + it { is_expected.to_not allow_value('randomstuff').for(:language) } + context 'when collection is remote' do subject { Fabricate.build :collection, local: false } it { is_expected.to validate_presence_of(:uri) } it { is_expected.to validate_presence_of(:original_number_of_items) } + + it { is_expected.to allow_value('randomstuff').for(:language) } end context 'when using a hashtag as category' do diff --git a/spec/requests/api/v1_alpha/collections_spec.rb b/spec/requests/api/v1_alpha/collections_spec.rb index 3921fabfde8..b529fc2d92f 100644 --- a/spec/requests/api/v1_alpha/collections_spec.rb +++ b/spec/requests/api/v1_alpha/collections_spec.rb @@ -115,6 +115,7 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do { name: 'Low-traffic bots', description: 'Really nice bots, please follow', + language: 'en', sensitive: '0', discoverable: '1', } diff --git a/spec/serializers/activitypub/featured_collection_serializer_spec.rb b/spec/serializers/activitypub/featured_collection_serializer_spec.rb index b01cce12d81..e6bb4ea4b0f 100644 --- a/spec/serializers/activitypub/featured_collection_serializer_spec.rb +++ b/spec/serializers/activitypub/featured_collection_serializer_spec.rb @@ -45,4 +45,20 @@ RSpec.describe ActivityPub::FeaturedCollectionSerializer do 'updated' => match_api_datetime_format, }) end + + context 'when a language is set' do + before do + collection.language = 'en' + end + + it 'uses "summaryMap" to include the language' do + expect(subject).to include({ + 'summaryMap' => { + 'en' => 'These are really amazing', + }, + }) + + expect(subject).to_not have_key('summary') + end + end end diff --git a/spec/serializers/rest/collection_serializer_spec.rb b/spec/serializers/rest/collection_serializer_spec.rb index f0baf7dff87..80ed6a559ed 100644 --- a/spec/serializers/rest/collection_serializer_spec.rb +++ b/spec/serializers/rest/collection_serializer_spec.rb @@ -18,6 +18,7 @@ RSpec.describe REST::CollectionSerializer do id: 2342, name: 'Exquisite follows', description: 'Always worth a follow', + language: 'en', local: true, sensitive: true, discoverable: false, @@ -31,6 +32,7 @@ RSpec.describe REST::CollectionSerializer do 'id' => '2342', 'name' => 'Exquisite follows', 'description' => 'Always worth a follow', + 'language' => 'en', 'local' => true, 'sensitive' => true, 'discoverable' => false, From c2f776b74704409d8ba299f67f140367e3f1c4ad Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 20 Jan 2026 15:10:38 +0100 Subject: [PATCH 045/145] Merge commit from fork --- app/controllers/api/web/push_subscriptions_controller.rb | 2 +- spec/requests/api/web/push_subscriptions_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index ced68d39fc7..2edd92dbc7b 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -62,7 +62,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController end def set_push_subscription - @push_subscription = ::Web::PushSubscription.find(params[:id]) + @push_subscription = ::Web::PushSubscription.where(user_id: active_session.user_id).find(params[:id]) end def subscription_params diff --git a/spec/requests/api/web/push_subscriptions_spec.rb b/spec/requests/api/web/push_subscriptions_spec.rb index 21830d1b1c1..88c0302f860 100644 --- a/spec/requests/api/web/push_subscriptions_spec.rb +++ b/spec/requests/api/web/push_subscriptions_spec.rb @@ -163,9 +163,10 @@ RSpec.describe 'API Web Push Subscriptions' do end describe 'PUT /api/web/push_subscriptions/:id' do - before { sign_in Fabricate :user } + before { sign_in user } - let(:subscription) { Fabricate :web_push_subscription } + let(:user) { Fabricate(:user) } + let(:subscription) { Fabricate(:web_push_subscription, user: user) } it 'gracefully handles invalid nested params' do put api_web_push_subscription_path(subscription), params: { data: 'invalid' } From 7a0d3d3bf851429c044d07cee250b367d809b496 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 20 Jan 2026 15:13:10 +0100 Subject: [PATCH 046/145] Merge commit from fork --- app/models/custom_filter.rb | 3 +++ app/models/custom_filter_keyword.rb | 4 +++- app/models/list.rb | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb index 07bbfd43733..1151c7de985 100644 --- a/app/models/custom_filter.rb +++ b/app/models/custom_filter.rb @@ -30,6 +30,8 @@ class CustomFilter < ApplicationRecord EXPIRATION_DURATIONS = [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].freeze + TITLE_LENGTH_LIMIT = 256 + include Expireable include Redisable @@ -41,6 +43,7 @@ class CustomFilter < ApplicationRecord accepts_nested_attributes_for :keywords, reject_if: :all_blank, allow_destroy: true validates :title, :context, presence: true + validates :title, length: { maximum: TITLE_LENGTH_LIMIT } validate :context_must_be_valid normalizes :context, with: ->(context) { context.map(&:strip).filter_map(&:presence) } diff --git a/app/models/custom_filter_keyword.rb b/app/models/custom_filter_keyword.rb index 112798b10a9..1abec4ddc4d 100644 --- a/app/models/custom_filter_keyword.rb +++ b/app/models/custom_filter_keyword.rb @@ -17,7 +17,9 @@ class CustomFilterKeyword < ApplicationRecord belongs_to :custom_filter - validates :keyword, presence: true + KEYWORD_LENGTH_LIMIT = 512 + + validates :keyword, presence: true, length: { maximum: KEYWORD_LENGTH_LIMIT } alias_attribute :phrase, :keyword diff --git a/app/models/list.rb b/app/models/list.rb index 8fd1953ab31..49ead642ac9 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -17,6 +17,7 @@ class List < ApplicationRecord include Paginable PER_ACCOUNT_LIMIT = 50 + TITLE_LENGTH_LIMIT = 256 enum :replies_policy, { list: 0, followed: 1, none: 2 }, prefix: :show, validate: true @@ -26,7 +27,7 @@ class List < ApplicationRecord has_many :accounts, through: :list_accounts has_many :active_accounts, -> { merge(ListAccount.active) }, through: :list_accounts, source: :account - validates :title, presence: true + validates :title, presence: true, length: { maximum: TITLE_LENGTH_LIMIT } validate :validate_account_lists_limit, on: :create From 841dfce9f76ef5ff6542cc6f98ec9c6df3fab5a1 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 20 Jan 2026 15:13:43 +0100 Subject: [PATCH 047/145] Merge commit from fork --- app/lib/activitypub/activity/update.rb | 3 ++- app/services/fan_out_on_write_service.rb | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb index d94f8767618..e22bea2c64f 100644 --- a/app/lib/activitypub/activity/update.rb +++ b/app/lib/activitypub/activity/update.rb @@ -30,7 +30,8 @@ class ActivityPub::Activity::Update < ActivityPub::Activity @status = Status.find_by(uri: object_uri, account_id: @account.id) # Ignore updates for old unknown objects, since those are updates we are not interested in - return if @status.nil? && object_too_old? + # Also ignore unknown objects from suspended users for the same reasons + return if @status.nil? && (@account.suspended? || object_too_old?) # We may be getting `Create` and `Update` out of order @status ||= ActivityPub::Activity::Create.new(@json, @account, **@options).perform diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 64769230b7c..428077b11fb 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -14,6 +14,8 @@ class FanOutOnWriteService < BaseService @account = status.account @options = options + return if @status.proper.account.suspended? + check_race_condition! warm_payload_cache! From 028e76f7703eecbbbf6e953c7d8d38184969e5dc Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 20 Jan 2026 15:14:45 +0100 Subject: [PATCH 048/145] Merge commit from fork * Add limit on inbox payload size The 1MB limit is consistent with the limit we use when fetching remote resources * Add limit to number of options from federated polls * Add a limit to the number of federated profile fields * Add limit on federated username length * Add hard limits for federated display name and account bio * Add hard limits for `alsoKnownAs` and `attributionDomains` * Add hard limit on federated custom emoji shortcode * Highlight most destructive limits and expand on their reasoning --- FEDERATION.md | 19 +++++++++++++++++++ .../activitypub/inboxes_controller.rb | 5 +++++ app/lib/activitypub/activity.rb | 1 + app/lib/activitypub/parser/poll_parser.rb | 6 +++++- app/models/account.rb | 9 ++++++++- app/models/custom_emoji.rb | 5 ++++- .../activitypub/process_account_service.rb | 14 +++++++++----- 7 files changed, 51 insertions(+), 8 deletions(-) diff --git a/FEDERATION.md b/FEDERATION.md index 03ea5449de3..eb91d9545fe 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -48,3 +48,22 @@ Mastodon requires all `POST` requests to be signed, and MAY require `GET` reques ### Additional documentation - [Mastodon documentation](https://docs.joinmastodon.org/) + +## Size limits + +Mastodon imposes a few hard limits on federated content. +These limits are intended to be very generous and way above what the Mastodon user experience is optimized for, so as to accomodate future changes and unusual or unforeseen usage patterns, while still providing some limits for performance reasons. +The following table attempts to summary those limits. + +| Limited property | Size limit | Consequence of exceeding the limit | +| ------------------------------------------------------------- | ---------- | ---------------------------------- | +| Serialized JSON-LD | 1MB | **Activity is rejected/dropped** | +| Profile fields (actor `PropertyValue` attachments) name/value | 2047 | Field name/value is truncated | +| Number of profile fields (actor `PropertyValue` attachments) | 50 | Fields list is truncated | +| Poll options (number of `anyOf`/`oneOf` in a `Question`) | 500 | Items list is truncated | +| Account username (actor `preferredUsername`) length | 2048 | **Actor will be rejected** | +| Account display name (actor `name`) length | 2048 | Display name will be truncated | +| Account note (actor `summary`) length | 20kB | Account note will be truncated | +| Account `attributionDomains` | 256 | List will be truncated | +| Account aliases (actor `alsoKnownAs`) | 256 | List will be truncated | +| Custom emoji shortcode (`Emoji` `name`) | 2048 | Emoji will be rejected | diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb index 1f7abb97fa5..cf46bf21b5e 100644 --- a/app/controllers/activitypub/inboxes_controller.rb +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -3,6 +3,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController include JsonLdHelper + before_action :skip_large_payload before_action :skip_unknown_actor_activity before_action :require_actor_signature! skip_before_action :authenticate_user! @@ -16,6 +17,10 @@ class ActivityPub::InboxesController < ActivityPub::BaseController private + def skip_large_payload + head 413 if request.content_length > ActivityPub::Activity::MAX_JSON_SIZE + end + def skip_unknown_actor_activity head 202 if unknown_affected_account? end diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index d07d1c2f241..eab345ce457 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -5,6 +5,7 @@ class ActivityPub::Activity include Redisable include Lockable + MAX_JSON_SIZE = 1.megabyte SUPPORTED_TYPES = %w(Note Question).freeze CONVERTED_TYPES = %w(Image Audio Video Article Page Event).freeze diff --git a/app/lib/activitypub/parser/poll_parser.rb b/app/lib/activitypub/parser/poll_parser.rb index 758c03f07ec..d43eaf6cfb4 100644 --- a/app/lib/activitypub/parser/poll_parser.rb +++ b/app/lib/activitypub/parser/poll_parser.rb @@ -3,6 +3,10 @@ class ActivityPub::Parser::PollParser include JsonLdHelper + # Limit the number of items for performance purposes. + # We truncate rather than error out to avoid missing the post entirely. + MAX_ITEMS = 500 + def initialize(json) @json = json end @@ -48,6 +52,6 @@ class ActivityPub::Parser::PollParser private def items - @json['anyOf'] || @json['oneOf'] + (@json['anyOf'] || @json['oneOf'])&.take(MAX_ITEMS) end end diff --git a/app/models/account.rb b/app/models/account.rb index deb1589a090..32f6e39840f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -81,6 +81,13 @@ class Account < ApplicationRecord DISPLAY_NAME_LENGTH_LIMIT = 30 NOTE_LENGTH_LIMIT = 500 + # Hard limits for federated content + USERNAME_LENGTH_HARD_LIMIT = 2048 + DISPLAY_NAME_LENGTH_HARD_LIMIT = 2048 + NOTE_LENGTH_HARD_LIMIT = 20.kilobytes + ATTRIBUTION_DOMAINS_HARD_LIMIT = 256 + ALSO_KNOWN_AS_HARD_LIMIT = 256 + AUTOMATED_ACTOR_TYPES = %w(Application Service).freeze include Attachmentable # Load prior to Avatar & Header concerns @@ -114,7 +121,7 @@ class Account < ApplicationRecord validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? } # Remote user validations, also applies to internal actors - validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (remote? || actor_type_application?) && will_save_change_to_username? } + validates :username, format: { with: USERNAME_ONLY_RE }, length: { maximum: USERNAME_LENGTH_HARD_LIMIT }, if: -> { (remote? || actor_type_application?) && will_save_change_to_username? } # Remote user validations validates :uri, presence: true, unless: :local?, on: :create diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 65a2faa9fd5..5c39e053be1 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -26,6 +26,8 @@ class CustomEmoji < ApplicationRecord LIMIT = 256.kilobytes MINIMUM_SHORTCODE_SIZE = 2 + MAX_SHORTCODE_SIZE = 128 + MAX_FEDERATED_SHORTCODE_SIZE = 2048 SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}' @@ -45,7 +47,8 @@ class CustomEmoji < ApplicationRecord normalizes :domain, with: ->(domain) { domain.downcase.strip } validates_attachment :image, content_type: { content_type: IMAGE_MIME_TYPES }, presence: true, size: { less_than: LIMIT } - validates :shortcode, uniqueness: { scope: :domain }, format: { with: SHORTCODE_ONLY_RE }, length: { minimum: MINIMUM_SHORTCODE_SIZE } + validates :shortcode, uniqueness: { scope: :domain }, format: { with: SHORTCODE_ONLY_RE }, length: { minimum: MINIMUM_SHORTCODE_SIZE, maximum: MAX_FEDERATED_SHORTCODE_SIZE } + validates :shortcode, length: { maximum: MAX_SHORTCODE_SIZE }, if: :local? scope :local, -> { where(domain: nil) } scope :remote, -> { where.not(domain: nil) } diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index f133fbc84ae..6f4aa2fdb6e 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -6,6 +6,7 @@ class ActivityPub::ProcessAccountService < BaseService include Redisable include Lockable + MAX_PROFILE_FIELDS = 50 SUBDOMAINS_RATELIMIT = 10 DISCOVERIES_PER_REQUEST = 400 @@ -124,15 +125,15 @@ class ActivityPub::ProcessAccountService < BaseService def set_immediate_attributes! @account.featured_collection_url = valid_collection_uri(@json['featured']) - @account.display_name = @json['name'] || '' - @account.note = @json['summary'] || '' + @account.display_name = (@json['name'] || '')[0...(Account::DISPLAY_NAME_LENGTH_HARD_LIMIT)] + @account.note = (@json['summary'] || '')[0...(Account::NOTE_LENGTH_HARD_LIMIT)] @account.locked = @json['manuallyApprovesFollowers'] || false @account.fields = property_values || {} - @account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) } + @account.also_known_as = as_array(@json['alsoKnownAs'] || []).take(Account::ALSO_KNOWN_AS_HARD_LIMIT).map { |item| value_or_id(item) } @account.discoverable = @json['discoverable'] || false @account.indexable = @json['indexable'] || false @account.memorial = @json['memorial'] || false - @account.attribution_domains = as_array(@json['attributionDomains'] || []).map { |item| value_or_id(item) } + @account.attribution_domains = as_array(@json['attributionDomains'] || []).take(Account::ATTRIBUTION_DOMAINS_HARD_LIMIT).map { |item| value_or_id(item) } end def set_fetchable_key! @@ -253,7 +254,10 @@ class ActivityPub::ProcessAccountService < BaseService def property_values return unless @json['attachment'].is_a?(Array) - as_array(@json['attachment']).select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') } + as_array(@json['attachment']) + .select { |attachment| attachment['type'] == 'PropertyValue' } + .take(MAX_PROFILE_FIELDS) + .map { |attachment| attachment.slice('name', 'value') } end def mismatching_origin?(url) From 400c1f3e8ec0ffd33ad30d9334b9210cdb89b14c Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 20 Jan 2026 16:00:10 +0100 Subject: [PATCH 049/145] Bump version to v4.5.5 (#37551) --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ docker-compose.yml | 6 +++--- lib/mastodon/version.rb | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c5ec67d854..39e975479e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ All notable changes to this project will be documented in this file. +## [4.5.5] - 2026-01-20 + +### Security + +- Fix missing limits on various federated properties [GHSA-gg8q-rcg7-p79g](https://github.com/mastodon/mastodon/security/advisories/GHSA-gg8q-rcg7-p79g) +- Fix remote user suspension bypass [GHSA-5h2f-wg8j-xqwp](https://github.com/mastodon/mastodon/security/advisories/GHSA-5h2f-wg8j-xqwp) +- Fix missing length limits on some user-provided fields [GHSA-6x3w-9g92-gvf3](https://github.com/mastodon/mastodon/security/advisories/GHSA-6x3w-9g92-gvf3) +- Fix missing access check for push notification settings update [GHSA-f3q8-7vw3-69v4](https://github.com/mastodon/mastodon/security/advisories/GHSA-f3q8-7vw3-69v4) + +### Changed + +- Skip tombstone creation on deleting from 404 (#37533 by @ClearlyClaire) + +### Fixed + +- Fix potential duplicate handling of quote accept/reject/delete (#37537 by @ClearlyClaire) +- Fix `FeedManager#filter_from_home` error when handling a reblog of a deleted status (#37486 by @ClearlyClaire) +- Fix needlessly complicated SQL query in status batch removal (#37469 by @ClearlyClaire) +- Fix `quote_approval_policy` being reset to user defaults when omitted in status update (#37436 and #37474 by @mjankowski and @shleeable) +- Fix `Vary` parsing in cache control enforcement (#37426 by @MegaManSec) +- Fix missing URI scheme test in `QuoteRequest` handling (#37425 by @MegaManSec) +- Fix thread-unsafe ActivityPub activity dispatch (#37423 by @MegaManSec) +- Fix URI generation for reblogs by accounts with numerical ActivityPub identifiers (#37415 by @oneiros) +- Fix SignatureParser accepting duplicate parameters in HTTP Signature header (#37375 by @shleeable) +- Fix emoji with variant selector not being rendered properly (#37320 by @ChaosExAnima) +- Fix mobile admin sidebar displaying under batch table toolbar (#37307 by @diondiondion) + ## [4.5.4] - 2026-01-07 ### Security diff --git a/docker-compose.yml b/docker-compose.yml index d4974eb1bdd..52d2a83f443 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: web: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.5.4 + image: ghcr.io/mastodon/mastodon:v4.5.5 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: # build: # dockerfile: ./streaming/Dockerfile # context: . - image: ghcr.io/mastodon/mastodon-streaming:v4.5.4 + image: ghcr.io/mastodon/mastodon-streaming:v4.5.5 restart: always env_file: .env.production command: node ./streaming/index.js @@ -102,7 +102,7 @@ services: sidekiq: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.5.4 + image: ghcr.io/mastodon/mastodon:v4.5.5 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 235ac92cbd6..f532276f85e 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -17,7 +17,7 @@ module Mastodon end def default_prerelease - 'alpha.2' + 'alpha.3' end def prerelease From 22ec36857415db40c3c0af843bda5217f893494a Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 20 Jan 2026 16:15:49 +0100 Subject: [PATCH 050/145] Profile redesign: Badges (#37550) --- app/javascript/images/icons/icon_admin.svg | 10 ++++ app/javascript/mastodon/components/badge.jsx | 31 ------------- app/javascript/mastodon/components/badge.tsx | 46 +++++++++++++++++++ .../account_timeline/components/badges.tsx | 38 +++++++++++---- .../components/redesign.module.scss | 21 +++++++++ 5 files changed, 107 insertions(+), 39 deletions(-) create mode 100644 app/javascript/images/icons/icon_admin.svg delete mode 100644 app/javascript/mastodon/components/badge.jsx create mode 100644 app/javascript/mastodon/components/badge.tsx diff --git a/app/javascript/images/icons/icon_admin.svg b/app/javascript/images/icons/icon_admin.svg new file mode 100644 index 00000000000..7e40dc46437 --- /dev/null +++ b/app/javascript/images/icons/icon_admin.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/javascript/mastodon/components/badge.jsx b/app/javascript/mastodon/components/badge.jsx deleted file mode 100644 index 2a335d7f506..00000000000 --- a/app/javascript/mastodon/components/badge.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; - -import { FormattedMessage } from 'react-intl'; - -import GroupsIcon from '@/material-icons/400-24px/group.svg?react'; -import PersonIcon from '@/material-icons/400-24px/person.svg?react'; -import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react'; - - -export const Badge = ({ icon = , label, domain, roleId }) => ( -
- {icon} - {label} - {domain && {domain}} -
-); - -Badge.propTypes = { - icon: PropTypes.node, - label: PropTypes.node, - domain: PropTypes.node, - roleId: PropTypes.string -}; - -export const GroupBadge = () => ( - } label={} /> -); - -export const AutomatedBadge = () => ( - } label={} /> -); diff --git a/app/javascript/mastodon/components/badge.tsx b/app/javascript/mastodon/components/badge.tsx new file mode 100644 index 00000000000..b7dc169edbc --- /dev/null +++ b/app/javascript/mastodon/components/badge.tsx @@ -0,0 +1,46 @@ +import type { FC, ReactNode } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import GroupsIcon from '@/material-icons/400-24px/group.svg?react'; +import PersonIcon from '@/material-icons/400-24px/person.svg?react'; +import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react'; + +export const Badge: FC<{ + label: ReactNode; + icon?: ReactNode; + className?: string; + domain?: ReactNode; + roleId?: string; +}> = ({ icon = , label, className, domain, roleId }) => ( +
+ {icon} + {label} + {domain && {domain}} +
+); + +export const GroupBadge: FC<{ className?: string }> = ({ className }) => ( + } + label={ + + } + className={className} + /> +); + +export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => ( + } + label={ + + } + className={className} + /> +); diff --git a/app/javascript/mastodon/features/account_timeline/components/badges.tsx b/app/javascript/mastodon/features/account_timeline/components/badges.tsx index cc6c456e9c1..1c5942d90d3 100644 --- a/app/javascript/mastodon/features/account_timeline/components/badges.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/badges.tsx @@ -1,9 +1,16 @@ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; +import IconAdmin from '@/images/icons/icon_admin.svg?react'; import { AutomatedBadge, Badge, GroupBadge } from '@/mastodon/components/badge'; +import { Icon } from '@/mastodon/components/icon'; import { useAccount } from '@/mastodon/hooks/useAccount'; +import type { AccountRole } from '@/mastodon/models/account'; import { useAppSelector } from '@/mastodon/store'; +import { isRedesignEnabled } from '../common'; + +import classes from './redesign.module.scss'; + export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { const account = useAccount(accountId); const localDomain = useAppSelector( @@ -15,22 +22,32 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { return null; } + const className = isRedesignEnabled() ? classes.badge : ''; + if (account.bot) { - badges.push(); + badges.push(); } else if (account.group) { - badges.push(); + badges.push(); } const domain = account.acct.includes('@') ? account.acct.split('@')[1] : localDomain; account.roles.forEach((role) => { + let icon: ReactNode = undefined; + if (isAdminBadge(role)) { + icon = ( + + ); + } badges.push( {role.get('name')}} - domain={domain} - roleId={role.get('id')} + key={role.id} + label={role.name} + className={className} + domain={isRedesignEnabled() ? `(${domain})` : domain} + roleId={role.id} + icon={icon} />, ); }); @@ -39,5 +56,10 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { return null; } - return
{badges}
; + return
{badges}
; }; + +function isAdminBadge(role: AccountRole) { + const name = role.name.toLowerCase(); + return isRedesignEnabled() && (name === 'admin' || name === 'owner'); +} diff --git a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss index 757f5a42314..5ccdb1f3100 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -39,6 +39,27 @@ h1.name > small { } } +.badge { + background-color: var(--color-bg-secondary); + border: none; + color: var(--color-text-secondary); + font-weight: 600; + + > span { + font-weight: unset; + opacity: 1; + } +} + +svg.badgeIcon { + opacity: 1; + fill: revert-layer; + + path { + fill: revert-layer; + } +} + .fieldList { margin-top: 16px; } From e65103bd3de034b89196f7f0d86a6e9eb157971e Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 21 Jan 2026 10:18:16 +0100 Subject: [PATCH 051/145] Fix rendering of initial state when `collections` feature is enabled (#37556) --- app/serializers/initial_state_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index fe2a857d509..a8e4b1d7f79 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -140,7 +140,7 @@ class InitialStateSerializer < ActiveModel::Serializer end def serialized_account(account) - ActiveModelSerializers::SerializableResource.new(account, serializer: REST::AccountSerializer) + ActiveModelSerializers::SerializableResource.new(account, serializer: REST::AccountSerializer, scope_name: :current_user, scope: object.current_account&.user) end def instance_presenter From e7c6600d83d043a91769142e249a8818cba855d6 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 21 Jan 2026 13:02:41 +0100 Subject: [PATCH 052/145] Fix cross-server conversation tracking (#37559) --- app/lib/activitypub/activity/create.rb | 1 + app/lib/activitypub/tag_manager.rb | 16 +++++++----- app/lib/ostatus/tag_manager.rb | 10 +++----- spec/lib/activitypub/activity/create_spec.rb | 26 +++++++++++++++++++- spec/lib/activitypub/tag_manager_spec.rb | 8 ------ 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 43c7bb1fe71..a7d2be35ed0 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -379,6 +379,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def conversation_from_uri(uri) return nil if uri.nil? return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri) + return ActivityPub::TagManager.instance.uri_to_resource(uri, Conversation) if ActivityPub::TagManager.instance.local_uri?(uri) begin Conversation.find_or_create_by!(uri: uri) diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index e6714c51abb..f9cb90f548c 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -243,12 +243,6 @@ class ActivityPub::TagManager !host.nil? && (::TagManager.instance.local_domain?(host) || ::TagManager.instance.web_domain?(host)) end - def uri_to_local_id(uri, param = :id) - path_params = Rails.application.routes.recognize_path(uri) - path_params[:username] = Rails.configuration.x.local_domain if path_params[:controller] == 'instance_actors' - path_params[param] - end - def uris_to_local_accounts(uris) usernames = [] ids = [] @@ -266,6 +260,14 @@ class ActivityPub::TagManager uri_to_resource(uri, Account) end + def uri_to_local_conversation(uri) + path_params = Rails.application.routes.recognize_path(uri) + return unless path_params[:controller] == 'activitypub/contexts' + + account_id, conversation_id = path_params[:id].split('-') + Conversation.find_by(parent_account_id: account_id, id: conversation_id) + end + def uri_to_resource(uri, klass) return if uri.nil? @@ -273,6 +275,8 @@ class ActivityPub::TagManager case klass.name when 'Account' uris_to_local_accounts([uri]).first + when 'Conversation' + uri_to_local_conversation(uri) else StatusFinder.new(uri).status end diff --git a/app/lib/ostatus/tag_manager.rb b/app/lib/ostatus/tag_manager.rb index cb0c9f89668..7d0f23c4dc1 100644 --- a/app/lib/ostatus/tag_manager.rb +++ b/app/lib/ostatus/tag_manager.rb @@ -11,16 +11,12 @@ class OStatus::TagManager def unique_tag_to_local_id(tag, expected_type) return nil unless local_id?(tag) - if ActivityPub::TagManager.instance.local_uri?(tag) - ActivityPub::TagManager.instance.uri_to_local_id(tag) - else - matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag) - matches[1] unless matches.nil? - end + matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag) + matches[1] unless matches.nil? end def local_id?(id) - id.start_with?("tag:#{Rails.configuration.x.local_domain}") || ActivityPub::TagManager.instance.local_uri?(id) + id.start_with?("tag:#{Rails.configuration.x.local_domain}") end def uri_for(target) diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 1e8a2a29db4..19b6014af15 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -471,7 +471,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'with a reply' do + context 'with a reply without explicitly setting a conversation' do let(:original_status) { Fabricate(:status) } let(:object_json) do @@ -493,6 +493,30 @@ RSpec.describe ActivityPub::Activity::Create do end end + context 'with a reply explicitly setting a conversation' do + let(:original_status) { Fabricate(:status) } + + let(:object_json) do + build_object( + inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status), + conversation: ActivityPub::TagManager.instance.uri_for(original_status.conversation), + context: ActivityPub::TagManager.instance.uri_for(original_status.conversation) + ) + end + + it 'creates status' do + expect { subject.perform }.to change(sender.statuses, :count).by(1) + + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.thread).to eq original_status + expect(status.reply?).to be true + expect(status.in_reply_to_account).to eq original_status.account + expect(status.conversation).to eq original_status.conversation + end + end + context 'with mentions' do let(:recipient) { Fabricate(:account) } diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb index 55e54ede5e1..a15529057cb 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -629,14 +629,6 @@ RSpec.describe ActivityPub::TagManager do end end - describe '#uri_to_local_id' do - let(:account) { Fabricate(:account, id_scheme: :username_ap_id) } - - it 'returns the local ID' do - expect(subject.uri_to_local_id(subject.uri_for(account), :username)).to eq account.username - end - end - describe '#uris_to_local_accounts' do it 'returns the expected local accounts' do account = Fabricate(:account) From 783504f36a394a7eaa4e34552116cfeb0c7cd1c6 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 21 Jan 2026 13:30:07 +0100 Subject: [PATCH 053/145] Do not return undiscoverable collections (#37560) --- .../api/v1_alpha/collections_controller.rb | 1 + app/models/collection.rb | 1 + .../requests/api/v1_alpha/collections_spec.rb | 26 +++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/app/controllers/api/v1_alpha/collections_controller.rb b/app/controllers/api/v1_alpha/collections_controller.rb index 9d6b2f9a381..4b07b5012a2 100644 --- a/app/controllers/api/v1_alpha/collections_controller.rb +++ b/app/controllers/api/v1_alpha/collections_controller.rb @@ -74,6 +74,7 @@ class Api::V1Alpha::CollectionsController < Api::BaseController .order(created_at: :desc) .offset(offset_param) .limit(limit_param(DEFAULT_COLLECTIONS_LIMIT)) + @collections = @collections.discoverable unless @account == current_account end def set_collection diff --git a/app/models/collection.rb b/app/models/collection.rb index 334318b73d3..3681c41d84f 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -43,6 +43,7 @@ class Collection < ApplicationRecord scope :with_items, -> { includes(:collection_items).merge(CollectionItem.with_accounts) } scope :with_tag, -> { includes(:tag) } + scope :discoverable, -> { where(discoverable: true) } def remote? !local? diff --git a/spec/requests/api/v1_alpha/collections_spec.rb b/spec/requests/api/v1_alpha/collections_spec.rb index b529fc2d92f..de79dcf7230 100644 --- a/spec/requests/api/v1_alpha/collections_spec.rb +++ b/spec/requests/api/v1_alpha/collections_spec.rb @@ -55,6 +55,32 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do ) end end + + context 'when some collections are not discoverable' do + before do + Fabricate(:collection, account:, discoverable: false) + end + + context 'when requesting user is a third party' do + it 'hides the collections that are not discoverable' do + subject + + expect(response).to have_http_status(200) + expect(response.parsed_body.size).to eq 3 + end + end + + context 'when requesting user owns the collection' do + let(:account) { user.account } + + it 'returns all collections, including the ones that are not discoverable' do + subject + + expect(response).to have_http_status(200) + expect(response.parsed_body.size).to eq 4 + end + end + end end describe 'GET /api/v1_alpha/collections/:id' do From 24ffa00bca165e8c180a5e45ba0380a2d2b37f81 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:31:19 +0000 Subject: [PATCH 054/145] Update dependency pino to v10.2.1 (#37543) 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 f37750a4cc7..d5efe483cba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10681,8 +10681,8 @@ __metadata: linkType: hard "pino@npm:^10.0.0": - version: 10.2.0 - resolution: "pino@npm:10.2.0" + version: 10.2.1 + resolution: "pino@npm:10.2.1" dependencies: "@pinojs/redact": "npm:^0.4.0" atomic-sleep: "npm:^1.0.0" @@ -10697,7 +10697,7 @@ __metadata: thread-stream: "npm:^4.0.0" bin: pino: bin.js - checksum: 10c0/8f88a2e205508d47ef04d2a6ec26ec450abb4b344d2d998d2e24b9e624e1a1ef7184f260ca5be06bc3733aa1ad76704657e373b359c7b71489a11709227e26da + checksum: 10c0/2eaed48bb7fb8865e27ac6d6709383f5c117f1e59c818734c7cc22b362e9aa5846a0547e7fd9cde64088a3b48aa314e1dab07ee16da8dc3b87897970eb56843e languageName: node linkType: hard From 5d82d48af3e6cdeabe2559e1e17ca48c4d716837 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:31:35 +0000 Subject: [PATCH 055/145] Update dependency stylelint-config-standard-scss to v17 (#37511) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 89 ++++++++++++++++++++++++---------------------------- 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 701719d7cff..987467458d3 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "storybook": "^10.0.5", "stylelint": "^16.19.1", "stylelint-config-prettier-scss": "^1.0.0", - "stylelint-config-standard-scss": "^16.0.0", + "stylelint-config-standard-scss": "^17.0.0", "typescript": "~5.9.0", "typescript-eslint": "^8.45.0", "typescript-plugin-css-modules": "^5.2.0", diff --git a/yarn.lock b/yarn.lock index d5efe483cba..e36a0cca262 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2990,7 +2990,7 @@ __metadata: stringz: "npm:^2.1.0" stylelint: "npm:^16.19.1" stylelint-config-prettier-scss: "npm:^1.0.0" - stylelint-config-standard-scss: "npm:^16.0.0" + stylelint-config-standard-scss: "npm:^17.0.0" substring-trie: "npm:^1.0.2" tesseract.js: "npm:^7.0.0" tiny-queue: "npm:^0.2.1" @@ -9311,13 +9311,6 @@ __metadata: languageName: node linkType: hard -"known-css-properties@npm:^0.36.0": - version: 0.36.0 - resolution: "known-css-properties@npm:0.36.0" - checksum: 10c0/098c8f956408a7ce26a639c2354e0184fb2bb2772bb7d1ba23192b6b6cf5818cbb8a0acfb4049705ea103d9916065703bc540fa084a6349fdb41bf745aada4bc - languageName: node - linkType: hard - "known-css-properties@npm:^0.37.0": version: 0.37.0 resolution: "known-css-properties@npm:0.37.0" @@ -9679,10 +9672,10 @@ __metadata: languageName: node linkType: hard -"mdn-data@npm:^2.21.0": - version: 2.21.0 - resolution: "mdn-data@npm:2.21.0" - checksum: 10c0/cd26902551af2cc29f06f130893cb04bca9ee278939fce3ffbcb759497cc80d53a6f4abdef2ae2f3ed3c95ac8d651f53fc141defd580ebf4ae2f93aea325957b +"mdn-data@npm:^2.25.0": + version: 2.26.0 + resolution: "mdn-data@npm:2.26.0" + checksum: 10c0/e5f17f4dac247f3e260c081761628d371e23659a7ff13413f83f5bd7fd0f2d8317e72277bb77f0e13115041334ff728a5363db64aabaf376c0e1b0b31016d0b8 languageName: node linkType: hard @@ -13185,74 +13178,74 @@ __metadata: languageName: node linkType: hard -"stylelint-config-recommended-scss@npm:^16.0.1": - version: 16.0.2 - resolution: "stylelint-config-recommended-scss@npm:16.0.2" +"stylelint-config-recommended-scss@npm:^17.0.0": + version: 17.0.0 + resolution: "stylelint-config-recommended-scss@npm:17.0.0" dependencies: postcss-scss: "npm:^4.0.9" - stylelint-config-recommended: "npm:^17.0.0" - stylelint-scss: "npm:^6.12.1" + stylelint-config-recommended: "npm:^18.0.0" + stylelint-scss: "npm:^7.0.0" peerDependencies: postcss: ^8.3.3 - stylelint: ^16.24.0 + stylelint: ^17.0.0 peerDependenciesMeta: postcss: optional: true - checksum: 10c0/d4e30a881e248d8b039347bf967526f6afe6d6a07f18e2747e14568de32273e819ba478be7a61a0dd63178931b4e891050a34e73d296ab533aa434209a7f3146 + checksum: 10c0/05b2e8d4316c2a8cc66eed0a2a8f01237e0ee8966a2e73d0b3c6706694f7630be165daa5a0cef511bc51f7e3fcb07a84c55d948c15fe6193a7e13cf9bb67c913 languageName: node linkType: hard -"stylelint-config-recommended@npm:^17.0.0": +"stylelint-config-recommended@npm:^18.0.0": + version: 18.0.0 + resolution: "stylelint-config-recommended@npm:18.0.0" + peerDependencies: + stylelint: ^17.0.0 + checksum: 10c0/c7f8ff45c76ec23f4c8c0438894726976fd5e872c59d489f959b728d9879bba20dbf0040cd29ad3bbc00eb32befd95f5b6ca150002bb8aea74b0797bc42ccc17 + languageName: node + linkType: hard + +"stylelint-config-standard-scss@npm:^17.0.0": version: 17.0.0 - resolution: "stylelint-config-recommended@npm:17.0.0" - peerDependencies: - stylelint: ^16.23.0 - checksum: 10c0/49e5d1c0f58197b2c5585b85fad814fed9bdec44c9870368c46a762664c5ff158c1145b6337456ae194409d692992b5b87421d62880422f71d8a3360417f5ad1 - languageName: node - linkType: hard - -"stylelint-config-standard-scss@npm:^16.0.0": - version: 16.0.0 - resolution: "stylelint-config-standard-scss@npm:16.0.0" + resolution: "stylelint-config-standard-scss@npm:17.0.0" dependencies: - stylelint-config-recommended-scss: "npm:^16.0.1" - stylelint-config-standard: "npm:^39.0.0" + stylelint-config-recommended-scss: "npm:^17.0.0" + stylelint-config-standard: "npm:^40.0.0" peerDependencies: postcss: ^8.3.3 - stylelint: ^16.23.1 + stylelint: ^17.0.0 peerDependenciesMeta: postcss: optional: true - checksum: 10c0/eb77f23824c5d649b193cb71d7f9b538b32b8cc1769451b2993270361127243d4011baf891ec265711b8e34e69ce28acb57ab6c3947b51fa3713ac26f4276439 + checksum: 10c0/0506537ba896f3d5e0fb002608090fcb41aa8ba7b65f1de8533702ce7c70e3f92b275782788a8356b5b687c86c53468c223e082226dda62780294b1cba324a36 languageName: node linkType: hard -"stylelint-config-standard@npm:^39.0.0": - version: 39.0.1 - resolution: "stylelint-config-standard@npm:39.0.1" +"stylelint-config-standard@npm:^40.0.0": + version: 40.0.0 + resolution: "stylelint-config-standard@npm:40.0.0" dependencies: - stylelint-config-recommended: "npm:^17.0.0" + stylelint-config-recommended: "npm:^18.0.0" peerDependencies: - stylelint: ^16.23.0 - checksum: 10c0/70a9862a2cedcc2a1807bd92fc91c40877270cf8a39576b91ae056d6de51d3b68104b26f71056ff22461b4319e9ec988d009abf10ead513b2ec15569d82e865a + stylelint: ^17.0.0 + checksum: 10c0/d8942552d53a3afda59b64d0c49503bb626fe5cef39a9e8c9583fcd60869f21431125ef4480ff27a59f7f2cf0da8af810d377129ef1d670ddc5def4defe2880c languageName: node linkType: hard -"stylelint-scss@npm:^6.12.1": - version: 6.12.1 - resolution: "stylelint-scss@npm:6.12.1" +"stylelint-scss@npm:^7.0.0": + version: 7.0.0 + resolution: "stylelint-scss@npm:7.0.0" dependencies: css-tree: "npm:^3.0.1" is-plain-object: "npm:^5.0.0" - known-css-properties: "npm:^0.36.0" - mdn-data: "npm:^2.21.0" + known-css-properties: "npm:^0.37.0" + mdn-data: "npm:^2.25.0" postcss-media-query-parser: "npm:^0.2.3" postcss-resolve-nested-selector: "npm:^0.1.6" - postcss-selector-parser: "npm:^7.1.0" + postcss-selector-parser: "npm:^7.1.1" postcss-value-parser: "npm:^4.2.0" peerDependencies: - stylelint: ^16.0.2 - checksum: 10c0/9a0903d34be3c75a72bef32402899db5f6b94c0823c5944fdf1acb2c3dc61c1f70fbb322558f8cb7e42dd01ed5e0dec22ed298f03b7bacc9f467c28330acae71 + stylelint: ^16.8.2 || ^17.0.0 + checksum: 10c0/07d0f20c6bcb34b8b0b6bfb1d4367b4825b52a7eef7dde2adfbaec11ebc67242e6b99dccf70dfbef1eb0a9bf8712fe0ab49d183ff6e4cca9c7f89752f7e27027 languageName: node linkType: hard From e79d51ce19274504dc85f4bb3fbf8502c0cfdc58 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 21 Jan 2026 14:08:08 +0100 Subject: [PATCH 056/145] Profile redesign: verified badges (#37538) --- app/javascript/images/icons/icon_verified.svg | 10 ++++ .../mastodon/components/mini_card/list.tsx | 5 +- .../account_timeline/components/fields.tsx | 57 ++++++++++++------- .../components/fields_modal.tsx | 28 ++++++--- .../components/redesign.module.scss | 38 +++++++++++++ .../styles/mastodon/theme/_dark.scss | 1 + .../styles/mastodon/theme/_light.scss | 1 + 7 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 app/javascript/images/icons/icon_verified.svg diff --git a/app/javascript/images/icons/icon_verified.svg b/app/javascript/images/icons/icon_verified.svg new file mode 100644 index 00000000000..65873b9dc43 --- /dev/null +++ b/app/javascript/images/icons/icon_verified.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/javascript/mastodon/components/mini_card/list.tsx b/app/javascript/mastodon/components/mini_card/list.tsx index f775e70aac8..318c5849538 100644 --- a/app/javascript/mastodon/components/mini_card/list.tsx +++ b/app/javascript/mastodon/components/mini_card/list.tsx @@ -10,7 +10,9 @@ import type { MiniCardProps } from '.'; import classes from './styles.module.css'; interface MiniCardListProps { - cards?: (Pick & { key?: Key })[]; + cards?: (Pick & { + key?: Key; + })[]; className?: string; onOverflowClick?: MouseEventHandler; } @@ -42,6 +44,7 @@ export const MiniCardList: FC = ({ label={card.label} value={card.value} hidden={hasOverflow && index >= hiddenIndex} + className={card.className} /> ))} diff --git a/app/javascript/mastodon/features/account_timeline/components/fields.tsx b/app/javascript/mastodon/features/account_timeline/components/fields.tsx index a73d92c1b69..ab29a8299e4 100644 --- a/app/javascript/mastodon/features/account_timeline/components/fields.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/fields.tsx @@ -3,10 +3,14 @@ import type { FC } from 'react'; import { FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; + +import IconVerified from '@/images/icons/icon_verified.svg?react'; import { openModal } from '@/mastodon/actions/modal'; import { AccountFields } from '@/mastodon/components/account_fields'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; import { FormattedDateWrapper } from '@/mastodon/components/formatted_date'; +import { Icon } from '@/mastodon/components/icon'; import { MiniCardList } from '@/mastodon/components/mini_card/list'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; import { useAccount } from '@/mastodon/hooks/useAccount'; @@ -55,25 +59,40 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => { const htmlHandlers = useElementHandledLink(); const cards = useMemo( () => - account.fields.toArray().map(({ value_emojified, name_emojified }) => ({ - label: ( - - ), - value: ( - - ), - })), + account.fields + .toArray() + .map(({ value_emojified, name_emojified, verified_at }) => ({ + label: ( + <> + + {!!verified_at && ( + + )} + + ), + value: ( + + ), + className: classNames( + classes.fieldCard, + !!verified_at && classes.fieldCardVerified, + ), + })), [account.emojis, account.fields, htmlHandlers], ); diff --git a/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx b/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx index 715f6097f45..103fffca505 100644 --- a/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/fields_modal.tsx @@ -2,9 +2,11 @@ import type { FC } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; +import IconVerified from '@/images/icons/icon_verified.svg?react'; import { DisplayName } from '@/mastodon/components/display_name'; import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { Icon } from '@/mastodon/components/icon'; import { IconButton } from '@/mastodon/components/icon_button'; import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; @@ -56,7 +58,10 @@ export const AccountFieldsModal: FC<{
{account.fields.map((field, index) => ( -
+
- +
+ + {!!field.verified_at && ( + + )} +
))}
diff --git a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss index 5ccdb1f3100..4bc64d05a98 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -64,6 +64,39 @@ svg.badgeIcon { margin-top: 16px; } +.fieldCard { + position: relative; + + a { + color: var(--color-text-brand); + text-decoration: none; + } +} + +.fieldCardVerified { + background-color: var(--color-bg-brand-softer); + + dt { + padding-right: 1rem; + } + + .fieldIconVerified { + position: absolute; + top: 4px; + right: 4px; + } +} + +.fieldIconVerified { + width: 1rem; + height: 1rem; + + // Need to override .icon path. + path { + fill: revert-layer; + } +} + .fieldNumbersWrapper { a { font-weight: unset; @@ -106,4 +139,9 @@ svg.badgeIcon { font-weight: 600; font-size: 15px; } + + .fieldIconVerified { + vertical-align: middle; + margin-left: 4px; + } } diff --git a/app/javascript/styles/mastodon/theme/_dark.scss b/app/javascript/styles/mastodon/theme/_dark.scss index e6fd6d3cc14..9485464e099 100644 --- a/app/javascript/styles/mastodon/theme/_dark.scss +++ b/app/javascript/styles/mastodon/theme/_dark.scss @@ -142,6 +142,7 @@ var(--border-strength-primary) )}; --color-border-media: rgb(252 248 255 / 15%); + --color-border-verified: rgb(220, 3, 240); --color-border-on-bg-secondary: #{utils.css-alpha( var(--color-indigo-200), calc(var(--border-strength-primary) / 1.5) diff --git a/app/javascript/styles/mastodon/theme/_light.scss b/app/javascript/styles/mastodon/theme/_light.scss index f0dc1bdfbc3..534a18367ca 100644 --- a/app/javascript/styles/mastodon/theme/_light.scss +++ b/app/javascript/styles/mastodon/theme/_light.scss @@ -140,6 +140,7 @@ var(--color-grey-950) var(--border-strength-primary) ); --color-border-media: rgb(252 248 255 / 15%); + --color-border-verified: rgb(220, 3, 240); --color-border-on-bg-secondary: var(--color-grey-200); --color-border-on-bg-brand-softer: var(--color-indigo-200); --color-border-on-bg-error-softer: #{utils.css-alpha( From 1468f945093671f67c7444fcc2c56fc42858653d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:08:16 +0000 Subject: [PATCH 057/145] New Crowdin Translations (automated) (#37555) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/es.json | 4 ++++ config/locales/ca.yml | 1 + config/locales/simple_form.de.yml | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 2b7d1905350..8a3672ad4c9 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar impulsos de @{name}", "account.in_memoriam": "Cuenta conmemorativa.", + "account.joined_long": "Se unió el {date}", "account.joined_short": "Se unió", "account.languages": "Cambiar idiomas suscritos", "account.link_verified_on": "La propiedad de este enlace fue verificada el {date}", @@ -90,6 +91,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_fields_modal.close": "Cerrar", + "account_fields_modal.title": "Información de {name}", "account_note.placeholder": "Haz clic para añadir nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}", "loading_indicator.label": "Cargando…", "media_gallery.hide": "Ocultar", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.", "mute_modal.hide_from_notifications": "Ocultar de las notificaciones", "mute_modal.hide_options": "Ocultar opciones", diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 0cbbb08f83e..6adcf1871c7 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -2162,6 +2162,7 @@ ca: error: Hi ha hagut un problema al esborrar la teva clau de seguretat. Tornau-ho a provar. success: La teva clau de seguretat s'ha esborrat correctament. invalid_credential: Clau de seguretat invàlida + nickname: Sobrenom nickname_hint: Introdueix el sobrenom de la teva clau de seguretat nova not_enabled: Encara no has activat WebAuthn not_supported: Aquest navegador no suporta claus de seguretat diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 98defb79cdb..a39cf443594 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -175,7 +175,7 @@ de: labels: account: attribution_domains: Websites, die auf dich verweisen dürfen - discoverable: Profil und Beiträge in Suchalgorithmen berücksichtigen + discoverable: Profil und Beiträge in Empfehlungsalgorithmen berücksichtigen fields: name: Beschriftung value: Inhalt From 22e438d7bdefdb467ddb759acf2c5a5ef584af26 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:14:48 +0100 Subject: [PATCH 058/145] Update dependency @csstools/stylelint-formatter-github to v2 (#37515) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 987467458d3..acdd57e3fd6 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "private": true, "dependencies": { - "@csstools/stylelint-formatter-github": "^1.0.0", + "@csstools/stylelint-formatter-github": "^2.0.0", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", diff --git a/yarn.lock b/yarn.lock index e36a0cca262..9c14c9e85e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1926,12 +1926,12 @@ __metadata: languageName: node linkType: hard -"@csstools/stylelint-formatter-github@npm:^1.0.0": - version: 1.0.0 - resolution: "@csstools/stylelint-formatter-github@npm:1.0.0" +"@csstools/stylelint-formatter-github@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/stylelint-formatter-github@npm:2.0.0" peerDependencies: - stylelint: ^16.6.0 - checksum: 10c0/2052c4e4d89656b2b4176a6d07508ef73278d33c24a7408a3555d07f26ec853f85da95525590c51751fb3150a2ebb5e3083d8200dc6597af2cd8e93198695269 + stylelint: ^17.0.0 + checksum: 10c0/1eddcb749eb93efff2e2d7edb4405459bf558ceaa6d90e792408802f30c55e3482a4cead9e69fd651f04a927e863782fc6cf813c37433da9ff1f068910080a06 languageName: node linkType: hard @@ -2861,7 +2861,7 @@ __metadata: version: 0.0.0-use.local resolution: "@mastodon/mastodon@workspace:." dependencies: - "@csstools/stylelint-formatter-github": "npm:^1.0.0" + "@csstools/stylelint-formatter-github": "npm:^2.0.0" "@dnd-kit/core": "npm:^6.1.0" "@dnd-kit/sortable": "npm:^10.0.0" "@dnd-kit/utilities": "npm:^3.2.2" From 6897475f9be80d9f3f81d06087dec0a3b6d7cc44 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 21 Jan 2026 16:54:52 +0100 Subject: [PATCH 059/145] Adds theming to Storybook (#37562) --- .storybook/preview.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index abbd193c681..10d45acfe65 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -50,9 +50,19 @@ const preview: Preview = { dynamicTitle: true, }, }, + theme: { + description: 'Theme for the story', + toolbar: { + title: 'Theme', + icon: 'circlehollow', + items: [{ value: 'light' }, { value: 'dark' }], + dynamicTitle: true, + }, + }, }, initialGlobals: { locale: 'en', + theme: 'light', }, decorators: [ (Story, { parameters, globals, args, argTypes }) => { @@ -135,6 +145,13 @@ const preview: Preview = { ); }, + (Story, { globals }) => { + const theme = (globals.theme as string) || 'light'; + useEffect(() => { + document.body.setAttribute('data-color-scheme', theme); + }, [theme]); + return ; + }, (Story) => ( From 3219373d5600ee5c6f38b947eb0bae6a56647643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 21 Jan 2026 17:01:33 +0100 Subject: [PATCH 060/145] Add profile field limits to instance serializer (#37535) --- app/serializers/rest/instance_serializer.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index 75d3acfea58..1900330475c 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -71,6 +71,9 @@ class REST::InstanceSerializer < ActiveModel::Serializer accounts: { max_featured_tags: FeaturedTag::LIMIT, max_pinned_statuses: StatusPinValidator::PIN_LIMIT, + max_profile_fields: Account::DEFAULT_FIELDS_SIZE, + profile_field_name_limit: Account::Field::MAX_CHARACTERS_LOCAL, + profile_field_value_limit: Account::Field::MAX_CHARACTERS_LOCAL, }, statuses: { From 562ea656f495f0619e393b7d93bd07c5abd28e5a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 21 Jan 2026 11:11:38 -0500 Subject: [PATCH 061/145] Add coverage for `TagManager#normalize_domain` (#35994) --- app/lib/tag_manager.rb | 2 +- spec/lib/tag_manager_spec.rb | 40 ++++++++++++++++++-- spec/models/instance_moderation_note_spec.rb | 2 +- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb index c1bd2973ed1..5a6284cc5b6 100644 --- a/app/lib/tag_manager.rb +++ b/app/lib/tag_manager.rb @@ -18,7 +18,7 @@ class TagManager return if domain.nil? uri = Addressable::URI.new - uri.host = domain.delete_suffix('/') + uri.host = domain.strip.delete_suffix('/') uri.normalized_host end diff --git a/spec/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb index 38203a55f70..927214bb40c 100644 --- a/spec/lib/tag_manager_spec.rb +++ b/spec/lib/tag_manager_spec.rb @@ -54,12 +54,44 @@ RSpec.describe TagManager do end describe '#normalize_domain' do - it 'returns nil if the given parameter is nil' do - expect(described_class.instance.normalize_domain(nil)).to be_nil + subject { described_class.instance.normalize_domain(domain) } + + context 'with a nil value' do + let(:domain) { nil } + + it { is_expected.to be_nil } end - it 'returns normalized domain' do - expect(described_class.instance.normalize_domain('DoMaIn.Example.com/')).to eq 'domain.example.com' + context 'with a blank value' do + let(:domain) { '' } + + it { is_expected.to be_blank } + end + + context 'with a mixed case string' do + let(:domain) { 'DoMaIn.Example.com' } + + it { is_expected.to eq('domain.example.com') } + end + + context 'with a trailing slash string' do + let(:domain) { 'domain.example.com/' } + + it { is_expected.to eq('domain.example.com') } + end + + context 'with a space padded string' do + let(:domain) { ' domain.example.com ' } + + it { is_expected.to eq('domain.example.com') } + end + + context 'with an invalid domain string' do + let(:domain) { ' !@#$@#$@$@# ' } + + it 'raises invalid uri error' do + expect { subject }.to raise_error(Addressable::URI::InvalidURIError) + end end end diff --git a/spec/models/instance_moderation_note_spec.rb b/spec/models/instance_moderation_note_spec.rb index 4d77d497122..011b001cc72 100644 --- a/spec/models/instance_moderation_note_spec.rb +++ b/spec/models/instance_moderation_note_spec.rb @@ -5,7 +5,7 @@ require 'rails_helper' RSpec.describe InstanceModerationNote do describe 'chronological' do it 'returns the instance notes sorted by oldest first' do - instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain('mastodon.example')) + instance = Instance.find_or_initialize_by(domain: 'mastodon.example') note1 = Fabricate(:instance_moderation_note, domain: instance.domain) note2 = Fabricate(:instance_moderation_note, domain: instance.domain) From 42b2fdb0acc41b06a397b25ca095fa222b96b9bf Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 22 Jan 2026 13:04:15 +0100 Subject: [PATCH 062/145] Re-download Material Icons (#37571) --- app/javascript/mastodon/features/compose/components/upload.tsx | 2 +- app/javascript/material-icons/400-24px/audio.svg | 1 - app/javascript/material-icons/400-24px/block-fill.svg | 2 +- app/javascript/material-icons/400-24px/block.svg | 2 +- app/javascript/material-icons/400-24px/graphic_eq-fill.svg | 1 + app/javascript/material-icons/400-24px/graphic_eq.svg | 1 + 6 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 app/javascript/material-icons/400-24px/audio.svg create mode 100644 app/javascript/material-icons/400-24px/graphic_eq-fill.svg create mode 100644 app/javascript/material-icons/400-24px/graphic_eq.svg diff --git a/app/javascript/mastodon/features/compose/components/upload.tsx b/app/javascript/mastodon/features/compose/components/upload.tsx index 85fed0cbd3b..4190f3248e3 100644 --- a/app/javascript/mastodon/features/compose/components/upload.tsx +++ b/app/javascript/mastodon/features/compose/components/upload.tsx @@ -10,8 +10,8 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import CloseIcon from '@/material-icons/400-20px/close.svg?react'; -import SoundIcon from '@/material-icons/400-24px/audio.svg?react'; import EditIcon from '@/material-icons/400-24px/edit.svg?react'; +import SoundIcon from '@/material-icons/400-24px/graphic_eq.svg?react'; import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import { undoUploadCompose } from 'mastodon/actions/compose'; import { openModal } from 'mastodon/actions/modal'; diff --git a/app/javascript/material-icons/400-24px/audio.svg b/app/javascript/material-icons/400-24px/audio.svg deleted file mode 100644 index 417e47c18fe..00000000000 --- a/app/javascript/material-icons/400-24px/audio.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/block-fill.svg b/app/javascript/material-icons/400-24px/block-fill.svg index 20e9889ae80..2d3801613c9 100644 --- a/app/javascript/material-icons/400-24px/block-fill.svg +++ b/app/javascript/material-icons/400-24px/block-fill.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/block.svg b/app/javascript/material-icons/400-24px/block.svg index 20e9889ae80..e9df4cdd35d 100644 --- a/app/javascript/material-icons/400-24px/block.svg +++ b/app/javascript/material-icons/400-24px/block.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/graphic_eq-fill.svg b/app/javascript/material-icons/400-24px/graphic_eq-fill.svg new file mode 100644 index 00000000000..a83414784d1 --- /dev/null +++ b/app/javascript/material-icons/400-24px/graphic_eq-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/graphic_eq.svg b/app/javascript/material-icons/400-24px/graphic_eq.svg new file mode 100644 index 00000000000..a83414784d1 --- /dev/null +++ b/app/javascript/material-icons/400-24px/graphic_eq.svg @@ -0,0 +1 @@ + \ No newline at end of file From 958103368eaa3ab2a044087ca8cffac6ec9f4454 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 22 Jan 2026 13:38:00 +0100 Subject: [PATCH 063/145] Shorten caching of quote posts pending approval (#37570) --- app/controllers/statuses_controller.rb | 2 +- app/models/quote.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index e673faca045..65db807d187 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -29,7 +29,7 @@ class StatusesController < ApplicationController end format.json do - expires_in 3.minutes, public: true if @status.distributable? && public_fetch_mode? + expires_in @status.quote&.pending? ? 5.seconds : 3.minutes, public: true if @status.distributable? && public_fetch_mode? render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter end end diff --git a/app/models/quote.rb b/app/models/quote.rb index 4ad393e3a57..425cf630557 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -47,6 +47,8 @@ class Quote < ApplicationRecord def accept! update!(state: :accepted) + + reset_parent_cache! if attribute_changed?(:state) end def reject! @@ -75,6 +77,15 @@ class Quote < ApplicationRecord private + def reset_parent_cache! + return if status_id.nil? + + Rails.cache.delete("v3:statuses/#{status_id}") + + # This clears the web cache for the ActivityPub representation + Rails.cache.delete("statuses/show:v3:statuses/#{status_id}") + end + def set_accounts self.account = status.account self.quoted_account = quoted_status&.account From 52ca91c43d0134fd6a3600a208ce019fa0e22159 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:50:23 +0100 Subject: [PATCH 064/145] Update dependency pg-connection-string to v2.10.1 (#37558) 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 9c14c9e85e2..15f3d5048dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10512,9 +10512,9 @@ __metadata: linkType: hard "pg-connection-string@npm:^2.10.0, pg-connection-string@npm:^2.6.0": - version: 2.10.0 - resolution: "pg-connection-string@npm:2.10.0" - checksum: 10c0/6639d3e12f66bc3cc5fa1e5794b4b22a4212e30424cbf001422e041c77c598ee0cd19395a9d79cb99a962da612b03c021e8148c3437cf01a29a18437bad193eb + version: 2.10.1 + resolution: "pg-connection-string@npm:2.10.1" + checksum: 10c0/f218a72b59c661022caca9a7f2116655632b1d7e7d6dc9a8ee9f238744e0927e0d6f44e12f50d9767c6d9cd47d9b3766aa054b77504b15c6bf503400530e053e languageName: node linkType: hard From 8dcd3881890a70c0df396c5d69ab8ed9d5494150 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:50:59 +0000 Subject: [PATCH 065/145] Update dependency aws-sdk-s3 to v1.212.0 (#37536) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3ee1f77aa0b..646162cffe4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -96,8 +96,8 @@ GEM ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.4.0) - aws-partitions (1.1201.0) - aws-sdk-core (3.241.3) + aws-partitions (1.1206.0) + aws-sdk-core (3.241.4) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -105,11 +105,11 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.120.0) - aws-sdk-core (~> 3, >= 3.241.3) + aws-sdk-kms (1.121.0) + aws-sdk-core (~> 3, >= 3.241.4) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.211.0) - aws-sdk-core (~> 3, >= 3.241.3) + aws-sdk-s3 (1.212.0) + aws-sdk-core (~> 3, >= 3.241.4) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) From 7b9479239aa52fddba1d4949ceebebe274b194f3 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 22 Jan 2026 08:59:36 -0500 Subject: [PATCH 066/145] Typo fix in federation document (#37564) --- FEDERATION.md | 4 ++-- spec/models/account_spec.rb | 2 ++ spec/models/custom_emoji_spec.rb | 8 ++++++- spec/models/custom_filter_keyword_spec.rb | 5 +++++ spec/models/custom_filter_spec.rb | 3 ++- spec/models/list_spec.rb | 1 + spec/requests/activitypub/inboxes_spec.rb | 13 +++++++++++ .../api/web/push_subscriptions_spec.rb | 11 ++++++++++ .../services/fan_out_on_write_service_spec.rb | 22 +++++++++++++++++-- 9 files changed, 63 insertions(+), 6 deletions(-) diff --git a/FEDERATION.md b/FEDERATION.md index eb91d9545fe..d5a176807b2 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -52,8 +52,8 @@ Mastodon requires all `POST` requests to be signed, and MAY require `GET` reques ## Size limits Mastodon imposes a few hard limits on federated content. -These limits are intended to be very generous and way above what the Mastodon user experience is optimized for, so as to accomodate future changes and unusual or unforeseen usage patterns, while still providing some limits for performance reasons. -The following table attempts to summary those limits. +These limits are intended to be very generous and way above what the Mastodon user experience is optimized for, so as to accommodate future changes and unusual or unforeseen usage patterns, while still providing some limits for performance reasons. +The following table summarizes those limits. | Limited property | Size limit | Consequence of exceeding the limit | | ------------------------------------------------------------- | ---------- | ---------------------------------- | diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 89a2f78c2e1..c9adaaff397 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -564,6 +564,8 @@ RSpec.describe Account do it { is_expected.to_not allow_values('username', 'Username').for(:username) } end + it { is_expected.to validate_length_of(:username).is_at_most(described_class::USERNAME_LENGTH_HARD_LIMIT) } + it { is_expected.to allow_values('the-doctor', username_over_limit).for(:username) } it { is_expected.to_not allow_values('the doctor').for(:username) } diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb index 244d0d126f1..ab757d39683 100644 --- a/spec/models/custom_emoji_spec.rb +++ b/spec/models/custom_emoji_spec.rb @@ -90,8 +90,14 @@ RSpec.describe CustomEmoji, :attachment_processing do subject { Fabricate.build :custom_emoji } it { is_expected.to validate_uniqueness_of(:shortcode).scoped_to(:domain) } - it { is_expected.to validate_length_of(:shortcode).is_at_least(described_class::MINIMUM_SHORTCODE_SIZE) } + it { is_expected.to validate_length_of(:shortcode).is_at_least(described_class::MINIMUM_SHORTCODE_SIZE).is_at_most(described_class::MAX_SHORTCODE_SIZE) } it { is_expected.to allow_values('cats').for(:shortcode) } it { is_expected.to_not allow_values('@#$@#$', 'X').for(:shortcode) } + + context 'when remote' do + subject { Fabricate.build :custom_emoji, domain: 'host.example' } + + it { is_expected.to validate_length_of(:shortcode).is_at_most(described_class::MAX_FEDERATED_SHORTCODE_SIZE) } + end end end diff --git a/spec/models/custom_filter_keyword_spec.rb b/spec/models/custom_filter_keyword_spec.rb index 4e3ab060a04..cc6558ea693 100644 --- a/spec/models/custom_filter_keyword_spec.rb +++ b/spec/models/custom_filter_keyword_spec.rb @@ -3,6 +3,11 @@ require 'rails_helper' RSpec.describe CustomFilterKeyword do + describe 'Validations' do + it { is_expected.to validate_length_of(:keyword).is_at_most(described_class::KEYWORD_LENGTH_LIMIT) } + it { is_expected.to validate_presence_of(:keyword) } + end + describe '#to_regex' do context 'when whole_word is true' do it 'builds a regex with boundaries and the keyword' do diff --git a/spec/models/custom_filter_spec.rb b/spec/models/custom_filter_spec.rb index 03914fa6b48..8a60f1dd491 100644 --- a/spec/models/custom_filter_spec.rb +++ b/spec/models/custom_filter_spec.rb @@ -6,8 +6,9 @@ RSpec.describe CustomFilter do it_behaves_like 'Expireable' describe 'Validations' do - it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_length_of(:title).is_at_most(described_class::TITLE_LENGTH_LIMIT) } it { is_expected.to validate_presence_of(:context) } + it { is_expected.to validate_presence_of(:title) } it { is_expected.to_not allow_values([], %w(invalid)).for(:context) } it { is_expected.to allow_values(%w(home)).for(:context) } diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index e2d91835ec7..bc6e67d8bff 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -6,6 +6,7 @@ RSpec.describe List do describe 'Validations' do subject { Fabricate.build :list } + it { is_expected.to validate_length_of(:title).is_at_most(described_class::TITLE_LENGTH_LIMIT) } it { is_expected.to validate_presence_of(:title) } context 'when account has hit max list limit' do diff --git a/spec/requests/activitypub/inboxes_spec.rb b/spec/requests/activitypub/inboxes_spec.rb index b21881b10fd..e33afa53c9b 100644 --- a/spec/requests/activitypub/inboxes_spec.rb +++ b/spec/requests/activitypub/inboxes_spec.rb @@ -20,6 +20,19 @@ RSpec.describe 'ActivityPub Inboxes' do end end + context 'with an excessively large payload' do + subject { post inbox_path, params: { this: :that, those: :these }.to_json, sign_with: remote_account } + + before { stub_const('ActivityPub::Activity::MAX_JSON_SIZE', 1.byte) } + + it 'returns http content too large' do + subject + + expect(response) + .to have_http_status(413) + end + end + context 'with a specific account' do subject { post account_inbox_path(account_username: account.username), params: {}.to_json, sign_with: remote_account } diff --git a/spec/requests/api/web/push_subscriptions_spec.rb b/spec/requests/api/web/push_subscriptions_spec.rb index 88c0302f860..3c33f0d2d29 100644 --- a/spec/requests/api/web/push_subscriptions_spec.rb +++ b/spec/requests/api/web/push_subscriptions_spec.rb @@ -190,6 +190,17 @@ RSpec.describe 'API Web Push Subscriptions' do .to eq(alerts_payload[:data][:alerts][type.to_sym].to_s) end end + + context 'when using other user subscription' do + let(:subscription) { Fabricate(:web_push_subscription) } + + it 'does not change settings' do + put api_web_push_subscription_path(subscription), params: alerts_payload + + expect(response) + .to have_http_status(404) + end + end end def created_push_subscription diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index c6dd020cdff..9f488995606 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -23,18 +23,30 @@ RSpec.describe FanOutOnWriteService do Fabricate(:media_attachment, status: status, account: alice) allow(redis).to receive(:publish) - - subject.call(status) end def home_feed_of(account) HomeFeed.new(account).get(10).map(&:id) end + context 'when status account is suspended' do + let(:visibility) { 'public' } + + before { alice.suspend! } + + it 'does not execute or broadcast' do + expect(subject.call(status)) + .to be_nil + expect_no_broadcasting + end + end + context 'when status is public' do let(:visibility) { 'public' } it 'adds status to home feed of author and followers and broadcasts', :inline_jobs do + subject.call(status) + expect(status.id) .to be_in(home_feed_of(alice)) .and be_in(home_feed_of(bob)) @@ -52,6 +64,8 @@ RSpec.describe FanOutOnWriteService do let(:visibility) { 'limited' } it 'adds status to home feed of author and mentioned followers and does not broadcast', :inline_jobs do + subject.call(status) + expect(status.id) .to be_in(home_feed_of(alice)) .and be_in(home_feed_of(bob)) @@ -66,6 +80,8 @@ RSpec.describe FanOutOnWriteService do let(:visibility) { 'private' } it 'adds status to home feed of author and followers and does not broadcast', :inline_jobs do + subject.call(status) + expect(status.id) .to be_in(home_feed_of(alice)) .and be_in(home_feed_of(bob)) @@ -79,6 +95,8 @@ RSpec.describe FanOutOnWriteService do let(:visibility) { 'direct' } it 'is added to the home feed of its author and mentioned followers and does not broadcast', :inline_jobs do + subject.call(status) + expect(status.id) .to be_in(home_feed_of(alice)) .and be_in(home_feed_of(bob)) From 3a84b73d80b2ad8eeecf882f0c735e1182f40404 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:26:33 +0100 Subject: [PATCH 067/145] New Crowdin Translations (automated) (#37569) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/nl.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 1411a222f81..bec46ab52ed 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -57,6 +57,7 @@ "account.go_to_profile": "Ga naar profiel", "account.hide_reblogs": "Boosts van @{name} verbergen", "account.in_memoriam": "In memoriam.", + "account.joined_long": "Lid geworden op {date}", "account.joined_short": "Geregistreerd op", "account.languages": "Getoonde talen wijzigen", "account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}", @@ -90,6 +91,8 @@ "account.unmute": "@{name} niet langer negeren", "account.unmute_notifications_short": "Meldingen niet langer negeren", "account.unmute_short": "Niet langer negeren", + "account_fields_modal.close": "Sluiten", + "account_fields_modal.title": "{name}'s info", "account_note.placeholder": "Klik om een opmerking toe te voegen", "admin.dashboard.daily_retention": "Retentiegraad van gebruikers per dag, vanaf registratie", "admin.dashboard.monthly_retention": "Retentiegraad van gebruikers per maand, vanaf registratie", @@ -589,6 +592,7 @@ "load_pending": "{count, plural, one {# nieuw item} other {# nieuwe items}}", "loading_indicator.label": "Laden…", "media_gallery.hide": "Verberg", + "minicard.more_items": "+{count}", "moved_to_account_banner.text": "Omdat je naar {movedToAccount} bent verhuisd is jouw account {disabledAccount} momenteel uitgeschakeld.", "mute_modal.hide_from_notifications": "Onder meldingen verbergen", "mute_modal.hide_options": "Opties verbergen", From 157d8c0d9912c62c0e533d51ff41b2c552c2f5cd Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 22 Jan 2026 09:57:19 -0500 Subject: [PATCH 068/145] Remove deprecated usage of imagemagick (#37488) --- .devcontainer/Dockerfile | 2 +- .github/workflows/test-ruby.yml | 87 ------------------- Dockerfile | 2 - Vagrantfile | 1 - .../dimension/software_versions_dimension.rb | 26 +----- app/models/concerns/account/avatar.rb | 2 +- app/models/concerns/account/header.rb | 2 +- app/models/preview_card.rb | 4 +- config/application.rb | 8 +- config/imagemagick/policy.xml | 27 ------ config/initializers/deprecations.rb | 6 -- config/initializers/paperclip.rb | 7 -- config/initializers/vips.rb | 50 +++++------ lib/paperclip/blurhash_transcoder.rb | 10 +-- lib/paperclip/color_extractor.rb | 13 +-- spec/models/media_attachment_spec.rb | 4 +- spec/models/preview_card_spec.rb | 6 -- spec/requests/api/v1/media_spec.rb | 2 +- spec/requests/api/v2/media_spec.rb | 2 +- 19 files changed, 37 insertions(+), 224 deletions(-) delete mode 100644 config/imagemagick/policy.xml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3aa0bbf7da4..ed8484f5b80 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,7 +9,7 @@ RUN /bin/bash --login -i -c "nvm install" # Install additional OS packages RUN apt-get update && \ export DEBIAN_FRONTEND=noninteractive && \ - apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libvips42 libpam-dev + apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg libvips42 libpam-dev # Disable download prompt for Corepack ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 8f05812d600..316bf831b6f 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -173,93 +173,6 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - test-imagemagick: - name: ImageMagick tests - runs-on: ubuntu-latest - - needs: - - build - - services: - postgres: - image: postgres:14-alpine - env: - POSTGRES_PASSWORD: postgres - POSTGRES_USER: postgres - options: >- - --health-cmd pg_isready - --health-interval 10ms - --health-timeout 3s - --health-retries 50 - ports: - - 5432:5432 - - redis: - image: redis:7-alpine - options: >- - --health-cmd "redis-cli ping" - --health-interval 10ms - --health-timeout 3s - --health-retries 50 - ports: - - 6379:6379 - - env: - DB_HOST: localhost - DB_USER: postgres - DB_PASS: postgres - COVERAGE: ${{ matrix.ruby-version == '.ruby-version' }} - RAILS_ENV: test - ALLOW_NOPAM: true - PAM_ENABLED: true - PAM_DEFAULT_SERVICE: pam_test - PAM_CONTROLLED_SERVICE: pam_test_controlled - OIDC_ENABLED: true - OIDC_SCOPE: read - SAML_ENABLED: true - CAS_ENABLED: true - BUNDLE_WITH: 'pam_authentication test' - GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }} - MASTODON_USE_LIBVIPS: false - - strategy: - fail-fast: false - matrix: - ruby-version: - - '3.2' - - '3.3' - - '.ruby-version' - steps: - - uses: actions/checkout@v5 - - - uses: actions/download-artifact@v6 - with: - path: './' - name: ${{ github.sha }} - - - name: Expand archived asset artifacts - run: | - tar xvzf artifacts.tar.gz - - - name: Set up Ruby environment - uses: ./.github/actions/setup-ruby - with: - ruby-version: ${{ matrix.ruby-version}} - additional-system-dependencies: ffmpeg imagemagick libpam-dev - - - name: Load database schema - run: './bin/rails db:create db:schema:load db:seed' - - - run: bin/rspec --tag attachment_processing - - - name: Upload coverage reports to Codecov - if: matrix.ruby-version == '.ruby-version' - uses: codecov/codecov-action@v5 - with: - files: coverage/lcov/mastodon.lcov - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - test-e2e: name: End to End testing runs-on: ubuntu-latest diff --git a/Dockerfile b/Dockerfile index b9dcbe59fd2..c06bc84a339 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,8 +70,6 @@ ENV \ PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \ # Optimize jemalloc 5.x performance MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \ - # Enable libvips, should not be changed - MASTODON_USE_LIBVIPS=true \ # Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes MASTODON_SIDEKIQ_READY_FILENAME=sidekiq_process_has_started_and_will_begin_processing_jobs diff --git a/Vagrantfile b/Vagrantfile index 0a343670240..a2c0b13b146 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -29,7 +29,6 @@ sudo apt-get install \ libpq-dev \ libxml2-dev \ libxslt1-dev \ - imagemagick \ nodejs \ redis-server \ redis-tools \ diff --git a/app/lib/admin/metrics/dimension/software_versions_dimension.rb b/app/lib/admin/metrics/dimension/software_versions_dimension.rb index e64a6b1180a..032abb75250 100644 --- a/app/lib/admin/metrics/dimension/software_versions_dimension.rb +++ b/app/lib/admin/metrics/dimension/software_versions_dimension.rb @@ -10,7 +10,7 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim protected def perform_query - [mastodon_version, ruby_version, postgresql_version, redis_version, elasticsearch_version, libvips_version, imagemagick_version, ffmpeg_version].compact + [mastodon_version, ruby_version, postgresql_version, redis_version, elasticsearch_version, libvips_version, ffmpeg_version].compact end def mastodon_version @@ -70,8 +70,6 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim end def libvips_version - return unless Rails.configuration.x.use_vips - { key: 'libvips', human_key: 'libvips', @@ -80,28 +78,6 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim } end - def imagemagick_version - return if Rails.configuration.x.use_vips - - imagemagick_binary = Paperclip.options[:is_windows] ? 'magick convert' : 'convert' - - version_output = Terrapin::CommandLine.new(imagemagick_binary, '-version').run - version_match = version_output.match(/Version: ImageMagick (\S+)/)[1].strip - - return nil unless version_match - - version = version_match - - { - key: 'imagemagick', - human_key: 'ImageMagick', - value: version, - human_value: version, - } - rescue Terrapin::CommandNotFoundError, Terrapin::ExitStatusError, Paperclip::Errors::CommandNotFoundError, Paperclip::Errors::CommandFailedError - nil - end - def ffmpeg_version version_output = Terrapin::CommandLine.new(Rails.configuration.x.ffprobe_binary, '-show_program_version -v 0 -of json').run version = Oj.load(version_output, mode: :strict, symbol_keys: true).dig(:program_version, :version) diff --git a/app/models/concerns/account/avatar.rb b/app/models/concerns/account/avatar.rb index a60a289d5b0..0be46788818 100644 --- a/app/models/concerns/account/avatar.rb +++ b/app/models/concerns/account/avatar.rb @@ -4,7 +4,7 @@ module Account::Avatar extend ActiveSupport::Concern AVATAR_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze - AVATAR_LIMIT = Rails.configuration.x.use_vips ? 8.megabytes : 2.megabytes + AVATAR_LIMIT = 8.megabytes AVATAR_DIMENSIONS = [400, 400].freeze AVATAR_GEOMETRY = [AVATAR_DIMENSIONS.first, AVATAR_DIMENSIONS.last].join('x') diff --git a/app/models/concerns/account/header.rb b/app/models/concerns/account/header.rb index 662ee7caf78..066c42cb6cd 100644 --- a/app/models/concerns/account/header.rb +++ b/app/models/concerns/account/header.rb @@ -4,7 +4,7 @@ module Account::Header extend ActiveSupport::Concern HEADER_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze - HEADER_LIMIT = Rails.configuration.x.use_vips ? 8.megabytes : 2.megabytes + HEADER_LIMIT = 8.megabytes HEADER_DIMENSIONS = [1500, 500].freeze HEADER_GEOMETRY = [HEADER_DIMENSIONS.first, HEADER_DIMENSIONS.last].join('x') HEADER_MAX_PIXELS = HEADER_DIMENSIONS.first * HEADER_DIMENSIONS.last diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 8e0e13cdb94..644be2671a9 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -39,7 +39,7 @@ class PreviewCard < ApplicationRecord include Attachmentable IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze - LIMIT = Rails.configuration.x.use_vips ? 8.megabytes : 2.megabytes + LIMIT = 8.megabytes BLURHASH_OPTIONS = { x_comp: 4, @@ -63,7 +63,7 @@ class PreviewCard < ApplicationRecord belongs_to :author_account, class_name: 'Account', optional: true has_attached_file :image, - processors: [Rails.configuration.x.use_vips ? :lazy_thumbnail : :thumbnail, :blurhash_transcoder], + processors: [:lazy_thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false diff --git a/config/application.rb b/config/application.rb index 90cfe47428f..4e58bd9f6c2 100644 --- a/config/application.rb +++ b/config/application.rb @@ -94,13 +94,7 @@ module Mastodon require 'mastodon/redis_configuration' ::REDIS_CONFIGURATION = Mastodon::RedisConfiguration.new - config.x.use_vips = ENV['MASTODON_USE_LIBVIPS'] != 'false' - - if config.x.use_vips - require_relative '../lib/paperclip/vips_lazy_thumbnail' - else - require_relative '../lib/paperclip/lazy_thumbnail' - end + require_relative '../lib/paperclip/vips_lazy_thumbnail' end config.x.cache_buster = config_for(:cache_buster) diff --git a/config/imagemagick/policy.xml b/config/imagemagick/policy.xml deleted file mode 100644 index e2aa202f274..00000000000 --- a/config/imagemagick/policy.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb index 520707e59ff..2c1057e5058 100644 --- a/config/initializers/deprecations.rb +++ b/config/initializers/deprecations.rb @@ -17,12 +17,6 @@ if ENV['REDIS_NAMESPACE'] abort message # rubocop:disable Rails/Exit end -if ENV['MASTODON_USE_LIBVIPS'] == 'false' - warn <<~MESSAGE - WARNING: Mastodon support for ImageMagick is deprecated and will be removed in future versions. Please consider using libvips instead. - MESSAGE -end - if ENV.key?('WHITELIST_MODE') warn(<<~MESSAGE.squish) WARNING: The environment variable WHITELIST_MODE has been replaced with diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index b444c5611b0..18d35a27f18 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -182,10 +182,3 @@ unless defined?(Seahorse) end end end - -# Set our ImageMagick security policy, but allow admins to override it -ENV['MAGICK_CONFIGURE_PATH'] = begin - imagemagick_config_paths = ENV.fetch('MAGICK_CONFIGURE_PATH', '').split(File::PATH_SEPARATOR) - imagemagick_config_paths << Rails.root.join('config', 'imagemagick').expand_path.to_s - imagemagick_config_paths.join(File::PATH_SEPARATOR) -end diff --git a/config/initializers/vips.rb b/config/initializers/vips.rb index 09210d60ebe..cd8d2410b92 100644 --- a/config/initializers/vips.rb +++ b/config/initializers/vips.rb @@ -1,35 +1,33 @@ # frozen_string_literal: true -if Rails.configuration.x.use_vips - ENV['VIPS_BLOCK_UNTRUSTED'] = 'true' +ENV['VIPS_BLOCK_UNTRUSTED'] = 'true' - require 'vips' +require 'vips' - unless Vips.at_least_libvips?(8, 13) - abort <<~ERROR.squish # rubocop:disable Rails/Exit - Incompatible libvips version (#{Vips.version_string}), please install libvips >= 8.13 - ERROR - end - - Vips.block('VipsForeign', true) - - %w( - VipsForeignLoadNsgif - VipsForeignLoadJpeg - VipsForeignLoadPng - VipsForeignLoadWebp - VipsForeignLoadHeif - VipsForeignSavePng - VipsForeignSaveSpng - VipsForeignSaveJpeg - VipsForeignSaveWebp - ).each do |operation| - Vips.block(operation, false) - end - - Vips.block_untrusted(true) +unless Vips.at_least_libvips?(8, 13) + abort <<~ERROR.squish # rubocop:disable Rails/Exit + Incompatible libvips version (#{Vips.version_string}), please install libvips >= 8.13 + ERROR end +Vips.block('VipsForeign', true) + +%w( + VipsForeignLoadNsgif + VipsForeignLoadJpeg + VipsForeignLoadPng + VipsForeignLoadWebp + VipsForeignLoadHeif + VipsForeignSavePng + VipsForeignSaveSpng + VipsForeignSaveJpeg + VipsForeignSaveWebp +).each do |operation| + Vips.block(operation, false) +end + +Vips.block_untrusted(true) + # In some places of the code, we rescue this exception, but we don't always # load libvips, so it may be an undefined constant: unless defined?(Vips) diff --git a/lib/paperclip/blurhash_transcoder.rb b/lib/paperclip/blurhash_transcoder.rb index b4ff4a12a0e..e9ff1dd9dd8 100644 --- a/lib/paperclip/blurhash_transcoder.rb +++ b/lib/paperclip/blurhash_transcoder.rb @@ -19,14 +19,8 @@ module Paperclip private def blurhash_params - if Rails.configuration.x.use_vips - image = Vips::Image.thumbnail(@file.path, 100) - [image.width, image.height, image.colourspace(:srgb).extract_band(0, n: 3).to_a.flatten] - else - pixels = convert(':source -depth 8 RGB:-', source: File.expand_path(@file.path)).unpack('C*') - geometry = options.fetch(:file_geometry_parser).from_file(@file) - [geometry.width, geometry.height, pixels] - end + image = Vips::Image.thumbnail(@file.path, 100) + [image.width, image.height, image.colourspace(:srgb).extract_band(0, n: 3).to_a.flatten] end end end diff --git a/lib/paperclip/color_extractor.rb b/lib/paperclip/color_extractor.rb index 62daa077952..1c9ef4bd3d6 100644 --- a/lib/paperclip/color_extractor.rb +++ b/lib/paperclip/color_extractor.rb @@ -10,7 +10,7 @@ module Paperclip BINS = 10 def make - background_palette, foreground_palette = Rails.configuration.x.use_vips ? palettes_from_libvips : palettes_from_imagemagick + background_palette, foreground_palette = palettes_from_libvips background_color = background_palette.first || foreground_palette.first foreground_colors = [] @@ -93,17 +93,6 @@ module Paperclip [background_palette, foreground_palette] end - def palettes_from_imagemagick - depth = 8 - - # Determine background palette by getting colors close to the image's edge only - background_palette = palette_from_im_histogram(convert(':source -alpha set -gravity Center -region 75%x75% -fill None -colorize 100% -alpha transparent +region -format %c -colors :quantity -depth :depth histogram:info:', source: File.expand_path(@file.path), quantity: 10, depth: depth), 10) - - # Determine foreground palette from the whole image - foreground_palette = palette_from_im_histogram(convert(':source -format %c -colors :quantity -depth :depth histogram:info:', source: File.expand_path(@file.path), quantity: 10, depth: depth), 10) - [background_palette, foreground_palette] - end - def downscaled_image image = Vips::Image.new_from_file(@file.path, access: :random).thumbnail_image(100) diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index a712cdde1dc..cbabb1d0dd3 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -219,9 +219,7 @@ RSpec.describe MediaAttachment, :attachment_processing do describe 'ogg with cover art' do let(:media) { Fabricate(:media_attachment, file: attachment_fixture('boop.ogg')) } let(:expected_media_duration) { 0.235102 } - - # The libvips and ImageMagick implementations produce different results - let(:expected_background_color) { Rails.configuration.x.use_vips ? '#268cd9' : '#3088d4' } + let(:expected_background_color) { '#268cd9' } it 'sets correct file metadata' do expect(media) diff --git a/spec/models/preview_card_spec.rb b/spec/models/preview_card_spec.rb index 0fe76c37b07..bac29046eac 100644 --- a/spec/models/preview_card_spec.rb +++ b/spec/models/preview_card_spec.rb @@ -3,12 +3,6 @@ require 'rails_helper' RSpec.describe PreviewCard do - describe 'file size limit', :attachment_processing do - it 'is set differently whether vips is enabled or not' do - expect(described_class::LIMIT).to eq(Rails.configuration.x.use_vips ? 8.megabytes : 2.megabytes) - end - end - describe 'Validations' do describe 'url' do it { is_expected.to allow_values('http://example.host/path', 'https://example.host/path').for(:url) } diff --git a/spec/requests/api/v1/media_spec.rb b/spec/requests/api/v1/media_spec.rb index 347ff4b2797..89e34998db7 100644 --- a/spec/requests/api/v1/media_spec.rb +++ b/spec/requests/api/v1/media_spec.rb @@ -102,7 +102,7 @@ RSpec.describe 'Media' do allow(user.account).to receive(:media_attachments).and_return(media_attachments) end - context 'when imagemagick cannot identify the file type' do + context 'when file type cannot be identified' do it 'returns http unprocessable entity' do allow(media_attachments).to receive(:create!).and_raise(Paperclip::Errors::NotIdentifiedByImageMagickError) diff --git a/spec/requests/api/v2/media_spec.rb b/spec/requests/api/v2/media_spec.rb index 04e48bc02c3..1f149b9634d 100644 --- a/spec/requests/api/v2/media_spec.rb +++ b/spec/requests/api/v2/media_spec.rb @@ -71,7 +71,7 @@ RSpec.describe 'Media API', :attachment_processing do allow(user.account).to receive(:media_attachments).and_return(media_attachments) end - context 'when imagemagick cannot identify the file type' do + context 'when file type cannot be identified' do before do allow(media_attachments).to receive(:create!).and_raise(Paperclip::Errors::NotIdentifiedByImageMagickError) end From 3806d15d99c468394f56494fcf1d3a152e23f780 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:00:53 +0100 Subject: [PATCH 069/145] Update dependency pg to v8.17.2 (#37557) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 15f3d5048dc..eaf9fb1eda3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10511,7 +10511,7 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.10.0, pg-connection-string@npm:^2.6.0": +"pg-connection-string@npm:^2.10.1, pg-connection-string@npm:^2.6.0": version: 2.10.1 resolution: "pg-connection-string@npm:2.10.1" checksum: 10c0/f218a72b59c661022caca9a7f2116655632b1d7e7d6dc9a8ee9f238744e0927e0d6f44e12f50d9767c6d9cd47d9b3766aa054b77504b15c6bf503400530e053e @@ -10555,11 +10555,11 @@ __metadata: linkType: hard "pg@npm:^8.5.0": - version: 8.17.1 - resolution: "pg@npm:8.17.1" + version: 8.17.2 + resolution: "pg@npm:8.17.2" dependencies: pg-cloudflare: "npm:^1.3.0" - pg-connection-string: "npm:^2.10.0" + pg-connection-string: "npm:^2.10.1" pg-pool: "npm:^3.11.0" pg-protocol: "npm:^1.11.0" pg-types: "npm:2.2.0" @@ -10572,7 +10572,7 @@ __metadata: peerDependenciesMeta: pg-native: optional: true - checksum: 10c0/39a92391adfc73f793d195b4062bc2d21aa3537073e3973f3979d72901d92a59e129f31f42577ff916038a6c3f9fe423b6024717529609ae8548fda21248cfe7 + checksum: 10c0/74b022587f92953f498dba747ccf9c7c90767af70326595d30c7ab0e2f00b2b468226c8abae54caef63ab444a8ac6f1597d859174386c7ba7c318c225d711c5f languageName: node linkType: hard From 1809048105a7e04095f6a0d1d466adeb0fdcffa5 Mon Sep 17 00:00:00 2001 From: Shlee Date: Fri, 23 Jan 2026 02:31:44 +1030 Subject: [PATCH 070/145] Safefy: Updated Admin::AccountDeletionWorker to match AccountDeletionWorker (#37577) --- app/workers/admin/account_deletion_worker.rb | 5 ++++- spec/workers/admin/account_deletion_worker_spec.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/workers/admin/account_deletion_worker.rb b/app/workers/admin/account_deletion_worker.rb index 5dfdfb6e73c..cd0d9c1f5e5 100644 --- a/app/workers/admin/account_deletion_worker.rb +++ b/app/workers/admin/account_deletion_worker.rb @@ -6,7 +6,10 @@ class Admin::AccountDeletionWorker sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.week.to_i def perform(account_id) - DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: true) + delete_account = Account.find(account_id) + return unless delete_account.unavailable? + + DeleteAccountService.new.call(delete_account, reserve_username: true, reserve_email: true) rescue ActiveRecord::RecordNotFound true end diff --git a/spec/workers/admin/account_deletion_worker_spec.rb b/spec/workers/admin/account_deletion_worker_spec.rb index e41b734f214..7fc56e42891 100644 --- a/spec/workers/admin/account_deletion_worker_spec.rb +++ b/spec/workers/admin/account_deletion_worker_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Admin::AccountDeletionWorker do let(:worker) { described_class.new } describe 'perform' do - let(:account) { Fabricate(:account) } + let(:account) { Fabricate(:account, suspended: true) } let(:service) { instance_double(DeleteAccountService, call: true) } it 'calls delete account service' do From 0924171c0f108f59c858c73362367f0f13cccc08 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 22 Jan 2026 17:08:57 +0100 Subject: [PATCH 071/145] Add form field components: `TextInputField`, `TextAreaField`, `SelectField` (#37578) --- .../mastodon/components/form_fields/index.ts | 3 + .../form_fields/select_field.stories.tsx | 55 ++++++++++ .../components/form_fields/select_field.tsx | 38 +++++++ .../form_fields/text_area_field.stories.tsx | 45 ++++++++ .../form_fields/text_area_field.tsx | 31 ++++++ .../form_fields/text_input_field.stories.tsx | 45 ++++++++ .../form_fields/text_input_field.tsx | 36 +++++++ .../components/form_fields/wrapper.tsx | 100 ++++++++++++++++++ .../mastodon/features/lists/new.tsx | 100 +++++++----------- .../mastodon/features/onboarding/profile.tsx | 60 ++++------- app/javascript/mastodon/locales/en.json | 1 + 11 files changed, 418 insertions(+), 96 deletions(-) create mode 100644 app/javascript/mastodon/components/form_fields/index.ts create mode 100644 app/javascript/mastodon/components/form_fields/select_field.stories.tsx create mode 100644 app/javascript/mastodon/components/form_fields/select_field.tsx create mode 100644 app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx create mode 100644 app/javascript/mastodon/components/form_fields/text_area_field.tsx create mode 100644 app/javascript/mastodon/components/form_fields/text_input_field.stories.tsx create mode 100644 app/javascript/mastodon/components/form_fields/text_input_field.tsx create mode 100644 app/javascript/mastodon/components/form_fields/wrapper.tsx diff --git a/app/javascript/mastodon/components/form_fields/index.ts b/app/javascript/mastodon/components/form_fields/index.ts new file mode 100644 index 00000000000..2aa87645144 --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/index.ts @@ -0,0 +1,3 @@ +export { TextInputField } from './text_input_field'; +export { TextAreaField } from './text_area_field'; +export { SelectField } from './select_field'; diff --git a/app/javascript/mastodon/components/form_fields/select_field.stories.tsx b/app/javascript/mastodon/components/form_fields/select_field.stories.tsx new file mode 100644 index 00000000000..30897adda1f --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/select_field.stories.tsx @@ -0,0 +1,55 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { SelectField } from './select_field'; + +const meta = { + title: 'Components/Form Fields/SelectField', + component: SelectField, + args: { + label: 'Fruit preference', + hint: 'Select your favourite fruit or not. Up to you.', + }, + render(args) { + // Component styles require a wrapper class at the moment + return ( +
+ + + + + + + + + + + +
+ ); + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Simple: Story = {}; + +export const Required: Story = { + args: { + required: true, + }, +}; + +export const Optional: Story = { + args: { + required: false, + }, +}; + +export const WithError: Story = { + args: { + required: false, + hasError: true, + }, +}; diff --git a/app/javascript/mastodon/components/form_fields/select_field.tsx b/app/javascript/mastodon/components/form_fields/select_field.tsx new file mode 100644 index 00000000000..aa058fc782e --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/select_field.tsx @@ -0,0 +1,38 @@ +import type { ComponentPropsWithoutRef } from 'react'; +import { forwardRef } from 'react'; + +import { FormFieldWrapper } from './wrapper'; +import type { CommonFieldWrapperProps } from './wrapper'; + +interface Props + extends ComponentPropsWithoutRef<'select'>, CommonFieldWrapperProps {} + +/** + * A simple form field for single-item selections. + * Provide selectable items via nested `