From 1ce2dc3d1309adaefdcdc9bd4f300d744b4361b8 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Thu, 9 Jan 2025 09:09:13 +0100 Subject: [PATCH 01/73] Target same browsers in development and production (#33513) --- .browserslistrc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.browserslistrc b/.browserslistrc index 6367e4d35817fc..0135379d6ea26d 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -1,10 +1,6 @@ -[production] defaults > 0.2% firefox >= 78 ios >= 15.6 not dead not OperaMini all - -[development] -supports es6-module From ce1501c3a72991b750f7084a20bdf2668595db5e Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 9 Jan 2025 03:12:48 -0500 Subject: [PATCH 02/73] Add "Account::Search" shared example for use in `Account` spec (#33507) --- spec/models/account_spec.rb | 266 +---------------- .../models/concerns/account/search.rb | 268 ++++++++++++++++++ 2 files changed, 269 insertions(+), 265 deletions(-) create mode 100644 spec/support/examples/models/concerns/account/search.rb diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 809ec52cde7113..f70690ddb6b37c 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -3,6 +3,7 @@ require 'rails_helper' RSpec.describe Account do + include_examples 'Account::Search' include_examples 'Reviewable' context 'with an account record' do @@ -344,271 +345,6 @@ end end - describe '.search_for' do - before do - _missing = Fabricate( - :account, - display_name: 'Missing', - username: 'missing', - domain: 'missing.com' - ) - end - - it 'does not return suspended users' do - Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com', - suspended: true - ) - - results = described_class.search_for('username') - expect(results).to eq [] - end - - it 'does not return unapproved users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(approved: false) - - results = described_class.search_for('username') - expect(results).to eq [] - end - - it 'does not return unconfirmed users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(confirmed_at: nil) - - results = described_class.search_for('username') - expect(results).to eq [] - end - - it 'accepts ?, \, : and space as delimiter' do - match = Fabricate( - :account, - display_name: 'A & l & i & c & e', - username: 'username', - domain: 'example.com' - ) - - results = described_class.search_for('A?l\i:c e') - expect(results).to eq [match] - end - - it 'finds accounts with matching display_name' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com' - ) - - results = described_class.search_for('display') - expect(results).to eq [match] - end - - it 'finds accounts with matching username' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com' - ) - - results = described_class.search_for('username') - expect(results).to eq [match] - end - - it 'finds accounts with matching domain' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com' - ) - - results = described_class.search_for('example') - expect(results).to eq [match] - end - - it 'limits via constant by default' do - stub_const('Account::Search::DEFAULT_LIMIT', 1) - 2.times.each { Fabricate(:account, display_name: 'Display Name') } - results = described_class.search_for('display') - expect(results.size).to eq 1 - end - - it 'accepts arbitrary limits' do - 2.times.each { Fabricate(:account, display_name: 'Display Name') } - results = described_class.search_for('display', limit: 1) - expect(results.size).to eq 1 - end - - it 'ranks multiple matches higher' do - matches = [ - { username: 'username', display_name: 'username' }, - { display_name: 'Display Name', username: 'username', domain: 'example.com' }, - ].map(&method(:Fabricate).curry(2).call(:account)) - - results = described_class.search_for('username') - expect(results).to eq matches - end - end - - describe '.advanced_search_for' do - let(:account) { Fabricate(:account) } - - context 'when limiting search to followed accounts' do - it 'accepts ?, \, : and space as delimiter' do - match = Fabricate( - :account, - display_name: 'A & l & i & c & e', - username: 'username', - domain: 'example.com' - ) - account.follow!(match) - - results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) - expect(results).to eq [match] - end - - it 'does not return non-followed accounts' do - Fabricate( - :account, - display_name: 'A & l & i & c & e', - username: 'username', - domain: 'example.com' - ) - - results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) - expect(results).to eq [] - end - - it 'does not return suspended users' do - Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com', - suspended: true - ) - - results = described_class.advanced_search_for('username', account, limit: 10, following: true) - expect(results).to eq [] - end - - it 'does not return unapproved users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(approved: false) - - results = described_class.advanced_search_for('username', account, limit: 10, following: true) - expect(results).to eq [] - end - - it 'does not return unconfirmed users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(confirmed_at: nil) - - results = described_class.advanced_search_for('username', account, limit: 10, following: true) - expect(results).to eq [] - end - end - - it 'does not return suspended users' do - Fabricate( - :account, - display_name: 'Display Name', - username: 'username', - domain: 'example.com', - suspended: true - ) - - results = described_class.advanced_search_for('username', account) - expect(results).to eq [] - end - - it 'does not return unapproved users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(approved: false) - - results = described_class.advanced_search_for('username', account) - expect(results).to eq [] - end - - it 'does not return unconfirmed users' do - match = Fabricate( - :account, - display_name: 'Display Name', - username: 'username' - ) - - match.user.update(confirmed_at: nil) - - results = described_class.advanced_search_for('username', account) - expect(results).to eq [] - end - - it 'accepts ?, \, : and space as delimiter' do - match = Fabricate( - :account, - display_name: 'A & l & i & c & e', - username: 'username', - domain: 'example.com' - ) - - results = described_class.advanced_search_for('A?l\i:c e', account) - expect(results).to eq [match] - end - - it 'limits result count by default value' do - stub_const('Account::Search::DEFAULT_LIMIT', 1) - 2.times { Fabricate(:account, display_name: 'Display Name') } - results = described_class.advanced_search_for('display', account) - expect(results.size).to eq 1 - end - - it 'accepts arbitrary limits' do - 2.times { Fabricate(:account, display_name: 'Display Name') } - results = described_class.advanced_search_for('display', account, limit: 1) - expect(results.size).to eq 1 - end - - it 'ranks followed accounts higher' do - match = Fabricate(:account, username: 'Matching') - followed_match = Fabricate(:account, username: 'Matcher') - Fabricate(:follow, account: account, target_account: followed_match) - - results = described_class.advanced_search_for('match', account) - expect(results).to eq [followed_match, match] - expect(results.first.rank).to be > results.last.rank - end - end - describe '#statuses_count' do subject { Fabricate(:account) } diff --git a/spec/support/examples/models/concerns/account/search.rb b/spec/support/examples/models/concerns/account/search.rb new file mode 100644 index 00000000000000..9d9b49973288f7 --- /dev/null +++ b/spec/support/examples/models/concerns/account/search.rb @@ -0,0 +1,268 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Account::Search' do + describe '.search_for' do + before do + _missing = Fabricate( + :account, + display_name: 'Missing', + username: 'missing', + domain: 'missing.com' + ) + end + + it 'does not return suspended users' do + Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com', + suspended: true + ) + + results = described_class.search_for('username') + expect(results).to eq [] + end + + it 'does not return unapproved users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(approved: false) + + results = described_class.search_for('username') + expect(results).to eq [] + end + + it 'does not return unconfirmed users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(confirmed_at: nil) + + results = described_class.search_for('username') + expect(results).to eq [] + end + + it 'accepts ?, \, : and space as delimiter' do + match = Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + + results = described_class.search_for('A?l\i:c e') + expect(results).to eq [match] + end + + it 'finds accounts with matching display_name' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com' + ) + + results = described_class.search_for('display') + expect(results).to eq [match] + end + + it 'finds accounts with matching username' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com' + ) + + results = described_class.search_for('username') + expect(results).to eq [match] + end + + it 'finds accounts with matching domain' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com' + ) + + results = described_class.search_for('example') + expect(results).to eq [match] + end + + it 'limits via constant by default' do + stub_const('Account::Search::DEFAULT_LIMIT', 1) + 2.times.each { Fabricate(:account, display_name: 'Display Name') } + results = described_class.search_for('display') + expect(results.size).to eq 1 + end + + it 'accepts arbitrary limits' do + 2.times.each { Fabricate(:account, display_name: 'Display Name') } + results = described_class.search_for('display', limit: 1) + expect(results.size).to eq 1 + end + + it 'ranks multiple matches higher' do + matches = [ + { username: 'username', display_name: 'username' }, + { display_name: 'Display Name', username: 'username', domain: 'example.com' }, + ].map(&method(:Fabricate).curry(2).call(:account)) + + results = described_class.search_for('username') + expect(results).to eq matches + end + end + + describe '.advanced_search_for' do + let(:account) { Fabricate(:account) } + + context 'when limiting search to followed accounts' do + it 'accepts ?, \, : and space as delimiter' do + match = Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + account.follow!(match) + + results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) + expect(results).to eq [match] + end + + it 'does not return non-followed accounts' do + Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + + results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) + expect(results).to eq [] + end + + it 'does not return suspended users' do + Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com', + suspended: true + ) + + results = described_class.advanced_search_for('username', account, limit: 10, following: true) + expect(results).to eq [] + end + + it 'does not return unapproved users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(approved: false) + + results = described_class.advanced_search_for('username', account, limit: 10, following: true) + expect(results).to eq [] + end + + it 'does not return unconfirmed users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(confirmed_at: nil) + + results = described_class.advanced_search_for('username', account, limit: 10, following: true) + expect(results).to eq [] + end + end + + it 'does not return suspended users' do + Fabricate( + :account, + display_name: 'Display Name', + username: 'username', + domain: 'example.com', + suspended: true + ) + + results = described_class.advanced_search_for('username', account) + expect(results).to eq [] + end + + it 'does not return unapproved users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(approved: false) + + results = described_class.advanced_search_for('username', account) + expect(results).to eq [] + end + + it 'does not return unconfirmed users' do + match = Fabricate( + :account, + display_name: 'Display Name', + username: 'username' + ) + + match.user.update(confirmed_at: nil) + + results = described_class.advanced_search_for('username', account) + expect(results).to eq [] + end + + it 'accepts ?, \, : and space as delimiter' do + match = Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + + results = described_class.advanced_search_for('A?l\i:c e', account) + expect(results).to eq [match] + end + + it 'limits result count by default value' do + stub_const('Account::Search::DEFAULT_LIMIT', 1) + 2.times { Fabricate(:account, display_name: 'Display Name') } + results = described_class.advanced_search_for('display', account) + expect(results.size).to eq 1 + end + + it 'accepts arbitrary limits' do + 2.times { Fabricate(:account, display_name: 'Display Name') } + results = described_class.advanced_search_for('display', account, limit: 1) + expect(results.size).to eq 1 + end + + it 'ranks followed accounts higher' do + match = Fabricate(:account, username: 'Matching') + followed_match = Fabricate(:account, username: 'Matcher') + Fabricate(:follow, account: account, target_account: followed_match) + + results = described_class.advanced_search_for('match', account) + expect(results).to eq [followed_match, match] + expect(results.first.rank).to be > results.last.rank + end + end +end From f4b463ecb1203beaafbca11b9a760729b9a66223 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 9 Jan 2025 03:17:00 -0500 Subject: [PATCH 03/73] Use `response.parsed_body` for error view application controller spec (#33515) --- .../application_controller_spec.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 4ee951628e7786..2e7a59db055962 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' RSpec.describe ApplicationController do + render_views + controller do def success head 200 @@ -23,9 +25,22 @@ def invalid_authenticity_token shared_examples 'respond_with_error' do |code| it "returns http #{code} for http and renders template" do - expect(subject).to render_template("errors/#{code}", layout: 'error') + subject + + expect(response) + .to have_http_status(code) + expect(response.parsed_body) + .to have_css('body[class=error]') + expect(response.parsed_body.css('h1').to_s) + .to include(error_content(code)) + end - expect(response).to have_http_status(code) + def error_content(code) + if code == 422 + I18n.t('errors.422.content') + else + I18n.t("errors.#{code}") + end end end From 4148b6843097295cb1d110c072946ea8f78fc238 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 9 Jan 2025 03:17:06 -0500 Subject: [PATCH 04/73] Remove `render_template` from remote interaction helper request spec (#33518) --- spec/requests/remote_interaction_helper_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/requests/remote_interaction_helper_spec.rb b/spec/requests/remote_interaction_helper_spec.rb index 942f70b9a411a2..b89060b5b292ff 100644 --- a/spec/requests/remote_interaction_helper_spec.rb +++ b/spec/requests/remote_interaction_helper_spec.rb @@ -9,7 +9,6 @@ expect(response) .to have_http_status(200) - .and render_template(:index, layout: 'helper_frame') .and have_attributes( headers: include( 'X-Frame-Options' => 'SAMEORIGIN', @@ -17,6 +16,8 @@ 'Content-Security-Policy' => expected_csp_headers ) ) + expect(response.body) + .to match(/remote_interaction_helper/) end end From cbae00ad235db71dc8963749a616b6b9561afdab Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 9 Jan 2025 03:17:11 -0500 Subject: [PATCH 05/73] Remove `render_template` from accounts request spec (#33519) --- spec/requests/accounts_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/requests/accounts_spec.rb b/spec/requests/accounts_spec.rb index afd9ac80e20863..72913ebf22da07 100644 --- a/spec/requests/accounts_spec.rb +++ b/spec/requests/accounts_spec.rb @@ -53,8 +53,9 @@ it 'returns a standard HTML response', :aggregate_failures do expect(response) .to have_http_status(200) - .and render_template(:show) .and have_http_link_header(ActivityPub::TagManager.instance.uri_for(account)).for(rel: 'alternate') + expect(response.parsed_body.at('title').content) + .to include(account.username) end end From 4e2c15b45dbe324d1f202f0260889364fd5f1702 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 08:21:47 +0000 Subject: [PATCH 06/73] New Crowdin Translations (automated) (#33522) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/hu.json | 1 + app/javascript/mastodon/locales/ia.json | 2 ++ app/javascript/mastodon/locales/nn.json | 1 + app/javascript/mastodon/locales/pt-PT.json | 6 ++--- app/javascript/mastodon/locales/sv.json | 1 + config/locales/activerecord.hu.yml | 2 ++ config/locales/activerecord.ia.yml | 2 ++ config/locales/activerecord.lv.yml | 7 +++++ config/locales/activerecord.sv.yml | 2 ++ config/locales/gl.yml | 4 +-- config/locales/lv.yml | 30 ++++++++++++++++++---- config/locales/pt-PT.yml | 2 +- config/locales/simple_form.lv.yml | 9 +++++-- config/locales/simple_form.pt-PT.yml | 4 +-- config/locales/simple_form.sv.yml | 5 ++++ 15 files changed, 63 insertions(+), 15 deletions(-) diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index d1ae388675cca7..703abf242b4ee2 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -457,6 +457,7 @@ "keyboard_shortcuts.toggle_hidden": "Tartalmi figyelmeztetéssel ellátott szöveg megjelenítése/elrejtése", "keyboard_shortcuts.toggle_sensitivity": "Média megjelenítése/elrejtése", "keyboard_shortcuts.toot": "Új bejegyzés írása", + "keyboard_shortcuts.translate": "Bejegyzés lefordítása", "keyboard_shortcuts.unfocus": "Szerkesztés/keresés fókuszból való kivétele", "keyboard_shortcuts.up": "Mozgás felfelé a listában", "lightbox.close": "Bezárás", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index 0b17e60710c42e..e2f821022ee249 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -457,6 +457,7 @@ "keyboard_shortcuts.toggle_hidden": "Monstrar/celar texto detra advertimento de contento", "keyboard_shortcuts.toggle_sensitivity": "Monstrar/celar multimedia", "keyboard_shortcuts.toot": "Initiar un nove message", + "keyboard_shortcuts.translate": "a traducer un message", "keyboard_shortcuts.unfocus": "Disfocalisar le area de composition de texto/de recerca", "keyboard_shortcuts.up": "Displaciar in alto in le lista", "lightbox.close": "Clauder", @@ -836,6 +837,7 @@ "status.reblogs.empty": "Necuno ha ancora impulsate iste message. Quando alcuno lo face, le impulsos apparera hic.", "status.redraft": "Deler e reconciper", "status.remove_bookmark": "Remover marcapagina", + "status.remove_favourite": "Remover del favoritos", "status.replied_in_thread": "Respondite in le discussion", "status.replied_to": "Respondite a {name}", "status.reply": "Responder", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 6091f1679fc0be..1a7f92be18f798 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -837,6 +837,7 @@ "status.reblogs.empty": "Ingen har framheva dette tutet enno. Om nokon gjer, så dukkar det opp her.", "status.redraft": "Slett & skriv på nytt", "status.remove_bookmark": "Fjern bokmerke", + "status.remove_favourite": "Fjern frå favorittar", "status.replied_in_thread": "Svara i tråden", "status.replied_to": "Svarte {name}", "status.reply": "Svar", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index fa4f715b6d33f4..b4b3c40dc53749 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -85,7 +85,7 @@ "alert.rate_limited.title": "Limite de tentativas", "alert.unexpected.message": "Ocorreu um erro inesperado.", "alert.unexpected.title": "Bolas!", - "alt_text_badge.title": "Texto alternativo", + "alt_text_badge.title": "Texto descritivo", "announcement.announcement": "Mensagem de manutenção", "annual_report.summary.archetype.booster": "O caçador de frescura", "annual_report.summary.archetype.lurker": "O espreitador", @@ -642,10 +642,10 @@ "notifications.policy.filter_hint": "Enviar para a caixa de notificações filtradas", "notifications.policy.filter_limited_accounts_hint": "Limitado pelos moderadores do servidor", "notifications.policy.filter_limited_accounts_title": "Contas moderadas", - "notifications.policy.filter_new_accounts.hint": "Criada {days, plural, one {no último dia} other {nos últimos # dias}}", + "notifications.policy.filter_new_accounts.hint": "Criadas {days, plural, one {no último dia} other {nos últimos # dias}}", "notifications.policy.filter_new_accounts_title": "Novas contas", "notifications.policy.filter_not_followers_hint": "Incluindo pessoas que te seguem há menos de {days, plural, one {um dia} other {# dias}}", - "notifications.policy.filter_not_followers_title": "Pessoas não te seguem", + "notifications.policy.filter_not_followers_title": "Pessoas que não te seguem", "notifications.policy.filter_not_following_hint": "Até que os aproves manualmente", "notifications.policy.filter_not_following_title": "Pessoas que não segues", "notifications.policy.filter_private_mentions_hint": "Filtrado, a não ser que seja em resposta à tua própria menção ou se seguires o remetente", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 486c3ac19dafec..f451caf6f98a51 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -697,6 +697,7 @@ "recommended": "Rekommenderas", "refresh": "Läs om", "regeneration_indicator.please_stand_by": "Vänligen vänta.", + "regeneration_indicator.preparing_your_home_feed": "Förbereder ditt hemflöde…", "relative_time.days": "{number}d", "relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sedan", "relative_time.full.hours": "{number, plural, one {# timme} other {# timmar}} sedan", diff --git a/config/locales/activerecord.hu.yml b/config/locales/activerecord.hu.yml index 27ca9018d475cc..b26d1afbe375db 100644 --- a/config/locales/activerecord.hu.yml +++ b/config/locales/activerecord.hu.yml @@ -24,6 +24,8 @@ hu: models: account: attributes: + fields: + fields_with_values_missing_labels: hiányzó címkékkel rendelkező értékeket tartalmaz username: invalid: csak betűket, számokat vagy alávonást tartalmazhat reserved: foglalt diff --git a/config/locales/activerecord.ia.yml b/config/locales/activerecord.ia.yml index fcb3f682787281..35b28a19aaa97f 100644 --- a/config/locales/activerecord.ia.yml +++ b/config/locales/activerecord.ia.yml @@ -24,6 +24,8 @@ ia: models: account: attributes: + fields: + fields_with_values_missing_labels: contine valores con etiquettas perdite username: invalid: debe continer solmente litteras, numeros e lineettas basse reserved: es reservate diff --git a/config/locales/activerecord.lv.yml b/config/locales/activerecord.lv.yml index b7e2db65e88e58..3fb928c89c5c61 100644 --- a/config/locales/activerecord.lv.yml +++ b/config/locales/activerecord.lv.yml @@ -24,6 +24,8 @@ lv: models: account: attributes: + fields: + fields_with_values_missing_labels: satur vērtības ar trūkstošām iezīmēm username: invalid: drīkst saturēt tikai burtus, ciparus un pasvītras reserved: ir rezervēts @@ -39,6 +41,11 @@ lv: attributes: data: malformed: ir nepareizi veidots + list_account: + attributes: + account_id: + taken: jau ir sarakstā + must_be_following: jābūt kontam, kuram seko status: attributes: reblog: diff --git a/config/locales/activerecord.sv.yml b/config/locales/activerecord.sv.yml index f05161992c833a..bf1ef22f3d1b9f 100644 --- a/config/locales/activerecord.sv.yml +++ b/config/locales/activerecord.sv.yml @@ -24,6 +24,8 @@ sv: models: account: attributes: + fields: + fields_with_values_missing_labels: innehåller värden med saknade etiketter username: invalid: endast bokstäver, siffror och understrykning reserved: är reserverat diff --git a/config/locales/gl.yml b/config/locales/gl.yml index ac6581c8c9813a..63a2b9b340ab86 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1859,9 +1859,9 @@ gl: '63113904': 2 anos '7889238': 3 meses min_age_label: Límite temporal - min_favs: Manter as publicacións favorecidas polo menos + min_favs: Manter publicacións favorecidas polo menos min_favs_hint: Non elimina ningunha das túas publicacións que recibiron alomenos esta cantidade de favorecementos. Deixa en branco para eliminar publicacións independentemente do número de favorecementos - min_reblogs: Manter publicacións promovidas máis de + min_reblogs: Manter publicacións promovidas polo menos min_reblogs_hint: Non elimina ningunha das túas publicacións se foron promovidas máis deste número de veces. Deixa en branco para eliminar publicacións independentemente do seu número de promocións stream_entries: sensitive_content: Contido sensible diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 0f757c6370c6ce..09b629c4dadc48 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -217,6 +217,7 @@ lv: enable_user: Ieslēgt Lietotāju memorialize_account: Saglabāt Kontu Piemiņai promote_user: Izceltt Lietotāju + publish_terms_of_service: Publicēt pakalpojuma izmantošanas nosacījumus reject_appeal: Noraidīt Apelāciju reject_user: Noraidīt lietotāju remove_avatar_user: Noņemt profila attēlu @@ -273,6 +274,7 @@ lv: enable_user_html: "%{name} iespējoja pieteikšanos lietotājam %{target}" memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiņas lapu" promote_user_html: "%{name} paaugstināja lietotāju %{target}" + publish_terms_of_service_html: "%{name} padarīja pieejamus pakalpojuma izmantošanas nosacījumu atjauninājumus" reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma iebildumu no %{target}" reject_user_html: "%{name} noraidīja reģistrēšanos no %{target}" remove_avatar_user_html: "%{name} noņēma %{target} profila attēlu" @@ -641,7 +643,7 @@ lv: create_and_resolve: Atrisināt ar piezīmi create_and_unresolve: Atvērt atkārtoti ar piezīmi delete: Dzēst - placeholder: Apraksti veiktās darbības vai citus saistītus atjauninājumus... + placeholder: Jāapraksta veiktās darbības vai jebkuri citi saistītie atjauninājumi... title: Piezīmes notes_description_html: Skati un atstāj piezīmes citiem moderatoriem un sev nākotnei processed_msg: 'Pārskats #%{id} veiksmīgi apstrādāts' @@ -746,13 +748,13 @@ lv: rules: add_new: Pievienot noteikumu delete: Dzēst - description_html: Lai gan lielākā daļa apgalvo, ka ir izlasījuši pakalpojumu sniegšanas noteikumus un piekrīt tiem, parasti cilvēki to izlasa tikai pēc problēmas rašanās. Padariet vienkāršāku sava servera noteikumu uztveršanu, veidojot tos vienkāršā sarakstā pa punktiem. Centieties, lai atsevišķi noteikumi būtu īsi un vienkārši, taču arī nesadaliet tos daudzos atsevišķos vienumos. + description_html: Kaut arī lielākā daļa apgalvo, ka ir lasījuši un piekrīt pakalpojuma izmantošanas nosacījumiem, parasti cilvēki tos neizlasa, līdz rodas sarežģījumi. Padari vienkāršāku sava servera noteikumu pārskatīšanu, sniedzot tos vienkāršā uzsvēruma punktu sarakstā! Jāmēģina atsevišķus noteikumus veidot īsus un vienkāršus, bet jāmēģina arī tos nesadalīt daudzos atsevišķos vienumos. edit: Labot nosacījumu empty: Servera noteikumi vēl nav definēti. title: Servera noteikumi settings: about: - manage_rules: Pārvaldīt servera nosacījumus + manage_rules: Pārvaldīt servera noteikumus preamble: Sniedz padziļinātu informāciju par to, kā serveris tiek darbināts, moderēts un finansēts. rules_hint: Noteikumiem, kas taviem lietotājiem ir jāievēro, ir īpaša sadaļa. title: Par @@ -821,6 +823,7 @@ lv: back_to_account: Atpakaļ uz konta lapu back_to_report: Atpakaļ uz paziņojumu lapu batch: + add_to_report: 'Pievienot atskaitei #%{id}' remove_from_report: Noņemt no ziņojuma report: Ziņojums contents: Saturs @@ -832,13 +835,17 @@ lv: media: title: Multivide metadata: Metadati + no_history: Šis ieraksts nav bijis labots no_status_selected: Neviena ziņa netika mainīta, jo neviena netika atlasīta open: Atvērt ziņu original_status: Oriģinālā ziņa reblogs: Reblogi + replied_to_html: Atbildēja %{acct_link} status_changed: Ziņa mainīta status_title: Publicēja @%{name} + title: Konta ieraksti - @%{name} trending: Aktuāli + view_publicly: Skatīt publiski visibility: Redzamība with_media: Ar multividi strikes: @@ -876,8 +883,8 @@ lv: message_html: 'Nesaderīga Elasticsearch versija: %{value}' version_comparison: Darbojas Elasticsearch %{running_version}, tomēr ir nepieciešama %{required_version} rules_check: - action: Pārvaldīt servera nosacījumus - message_html: Tu neesi definējis nevienu servera nosacījumu. + action: Pārvaldīt servera noteikumus + message_html: Nav pievienots neviens servera noteikums. sidekiq_process_check: message_html: Rindā(s) %{value} nedarbojas neviens Sidekiq process. Lūdzu, pārskati savu Sidekiq konfigurāciju software_version_check: @@ -907,13 +914,21 @@ lv: name: Nosaukums newest: Jaunākie oldest: Vecākie + open: Apskatīt publiski reset: Atiestatīt review: Pārskatīt stāvokli search: Meklēt title: Tēmturi updated_msg: Tēmtura iestatījumi ir veiksmīgi atjaunināti terms_of_service: + back: Atpakaļ uz pakalpojuma izmantošanas nosacījumiem changelog: Kas ir mainījies + create: Izmantot savus + current: Pašreizējie + draft: Melnraksts + generate: Izmantot sagatavi + generates: + action: Izveidot history: Vēsture publish: Publicēt published_on_html: Publicēts %{date} @@ -1826,6 +1841,11 @@ lv: further_actions_html: Ja tas nebiji tu, iesakām nekavējoties %{action} un iespējot divu faktoru autentifikāciju, lai tavs konts būtu drošībā. subject: Tavam kontam ir piekļūts no jaunas IP adreses title: Jauna pieteikšanās + terms_of_service_changed: + sign_off: "%{domain} komanda" + subject: Mūsu pakalpojuma izmantošanas nosacījumu atjauninājumi + subtitle: Mainās %{domain} pakalpojuma izmantošanas nosacījumi + title: Svarīgs atjauninājums warning: appeal: Iesniegt apelāciju appeal_description: Ja uzskatāt, ka tā ir kļūda, varat iesniegt apelāciju %{instance} darbiniekiem. diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 41fc9fbecc5747..bba71f165d9838 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -10,7 +10,7 @@ pt-PT: followers: one: Seguidor other: Seguidores - following: A seguir + following: Seguindo instance_actor_flash: Esta conta é um ator virtual utilizado para representar o servidor em si e não um utilizador individual. É utilizada para efeitos de federação e não deve ser suspensa. last_active: última atividade link_verified_on: A posse desta hiperligação foi verificada em %{date} diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 2f4a05dca469e0..53224966f1cc09 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -119,8 +119,8 @@ lv: sign_up_requires_approval: Jaunām reģistrācijām būs nepieciešams tavs apstiprinājums severity: Izvēlies, kas notiks ar pieprasījumiem no šīs IP adreses rule: - hint: Izvēles. Sniedz vairāk informācijas par nosacījumu - text: Apraksti nosacījumus vai prasības šī servera lietotājiem. Centies, lai tas būtu īss un vienkāršs + hint: Izvēles. Sniedz vairāk informācijas par noteikumu + text: Jāapraksta nosacījums vai prasība šī servera lietotājiem. Jāmēģina to veidot īsu un vienkāršu sessions: otp: 'Ievadi divfaktoru kodu, ko ģenerējusi tava tālruņa lietotne, vai izmanto kādu no atkopšanas kodiem:' webauthn: Ja tā ir USB atslēga, noteikti ievieto to un, ja nepieciešams, pieskaries tai. @@ -317,6 +317,11 @@ lv: name: Tēmturis trendable: Atļaut šim tēmturim parādīties zem tendencēm usable: Ļaut ierakstos vietēji izmantot šo tēmturi + terms_of_service: + changelog: Kas ir mainījies? + text: Pakalpojuma izmantošanas nosacījumi + terms_of_service_generator: + domain: Domēna vārds user: role: Loma time_zone: Laika josla diff --git a/config/locales/simple_form.pt-PT.yml b/config/locales/simple_form.pt-PT.yml index efc64d6dc6ee51..f5a7e3a27ee884 100644 --- a/config/locales/simple_form.pt-PT.yml +++ b/config/locales/simple_form.pt-PT.yml @@ -161,7 +161,7 @@ pt-PT: fields: name: Rótulo value: Conteúdo - indexable: Incluir mensagens públicas nos resultados da pesquisa + indexable: Incluir mensagens públicas nos resultados de pesquisas show_collections: Mostrar quem sigo e os meus seguidores no perfil unlocked: Aceitar automaticamente novos seguidores account_alias: @@ -205,7 +205,7 @@ pt-PT: email: Endereço de correio electrónico expires_in: Expira em fields: Metadados de perfil - header: Cabeçalho + header: Imagem de cabeçalho honeypot: "%{label} (não preencher)" inbox_url: URL da caixa de entrada do repetidor irreversible: Expandir em vez de esconder diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index 5fd258d7e0968f..421d360fac0480 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -134,7 +134,9 @@ sv: changelog: Kan struktureras med Markdown syntax. text: Kan struktureras med Markdown syntax. terms_of_service_generator: + arbitration_address: Kan vara samma som fysisk adress ovan, eller “N/A” om du använder e-post arbitration_website: Kan vara ett webbformulär, eller ”N/A” om du använder e-post + dmca_email: Kan vara samma e-postadress som används för “E-postadress för juridiska meddelanden” ovan jurisdiction: Lista det land där vem som än betalar räkningarna bor. Om det är ett företag eller annan enhet, lista landet där det är inkorporerat, och staden, regionen, territoriet eller staten på lämpligt sätt. user: chosen_languages: Vid aktivering visas bara inlägg på dina valda språk i offentliga tidslinjer @@ -330,6 +332,9 @@ sv: text: Användarvillkor terms_of_service_generator: admin_email: E-postadress för juridiska meddelanden + dmca_address: Fysisk adress för meddelanden om DMCA/upphovsrätt + dmca_email: Fysisk adress för meddelanden om DMCA/upphovsrätt + domain: Domän user: role: Roll time_zone: Tidszon From 8e2c642d4436eb06bf3c49379efe93cce20e58ef Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Thu, 9 Jan 2025 09:35:35 +0100 Subject: [PATCH 07/73] Do now swallow response body on persistent connection (#32729) --- app/lib/request.rb | 10 ++------ spec/lib/request_spec.rb | 49 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index 3d2a0c0e3154b8..f984f0e63e4d0a 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -111,16 +111,10 @@ def perform end begin - # If we are using a persistent connection, we have to - # read every response to be able to move forward at all. - # However, simply calling #to_s or #flush may not be safe, - # as the response body, if malicious, could be too big - # for our memory. So we use the #body_with_limit method - response.body_with_limit if http_client.persistent? - yield response if block_given? ensure - http_client.close unless http_client.persistent? + response.truncated_body if http_client.persistent? && !response.connection.finished_request? + http_client.close unless http_client.persistent? && response.connection.finished_request? end end diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index c600a48ee24e3a..f17cf637b99a31 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -4,7 +4,9 @@ require 'securerandom' RSpec.describe Request do - subject { described_class.new(:get, 'http://example.com') } + subject { described_class.new(:get, 'http://example.com', **options) } + + let(:options) { {} } describe '#headers' do it 'returns user agent' do @@ -39,8 +41,8 @@ end describe '#perform' do - context 'with valid host' do - before { stub_request(:get, 'http://example.com') } + context 'with valid host and non-persistent connection' do + before { stub_request(:get, 'http://example.com').to_return(body: 'lorem ipsum') } it 'executes a HTTP request' do expect { |block| subject.perform(&block) }.to yield_control @@ -71,9 +73,9 @@ expect(subject.send(:http_client)).to have_received(:close) end - it 'returns response which implements body_with_limit' do + it 'yields response' do subject.perform do |response| - expect(response).to respond_to :body_with_limit + expect(response.body_with_limit).to eq 'lorem ipsum' end end end @@ -95,6 +97,43 @@ expect { subject.perform }.to raise_error Mastodon::ValidationError end end + + context 'with persistent connection' do + before { stub_request(:get, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes)) } + + let(:http_client) { described_class.http_client.persistent('http://example.com') } + let(:options) { { http_client: http_client } } + + it 'leaves connection open after completely consumed response' do + allow(http_client).to receive(:close) + + subject.perform { |response| response.truncated_body(3.megabytes) } + + expect(http_client).to_not have_received(:close) + end + + it 'leaves connection open after nearly consumed response' do + allow(http_client).to receive(:close) + + subject.perform { |response| response.truncated_body(1.8.megabytes) } + + expect(http_client).to_not have_received(:close) + end + + it 'closes connection after unconsumed response' do + allow(http_client).to receive(:close) + + subject.perform + + expect(http_client).to have_received(:close) + end + + it 'yields response' do + subject.perform do |response| + expect(response.body_with_limit(2.megabytes).size).to eq 2.megabytes + end + end + end end describe "response's body_with_limit method" do From b0fbb7175997c81cffee815fa34022142fcc8ef0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 10:55:34 +0100 Subject: [PATCH 08/73] Update dependency jsdom to v26 (#33521) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- streaming/package.json | 2 +- yarn.lock | 121 ++++++++++++++++++++++++----------------- 2 files changed, 71 insertions(+), 52 deletions(-) diff --git a/streaming/package.json b/streaming/package.json index 2419ffd273a0c8..b98608edd5b1a5 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -21,7 +21,7 @@ "dotenv": "^16.0.3", "express": "^4.18.2", "ioredis": "^5.3.2", - "jsdom": "^25.0.0", + "jsdom": "^26.0.0", "pg": "^8.5.0", "pg-connection-string": "^2.6.0", "pino": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index a51d49ca56b6ad..3136b05aa5b0de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,6 +42,19 @@ __metadata: languageName: node linkType: hard +"@asamuzakjp/css-color@npm:^2.8.2": + version: 2.8.2 + resolution: "@asamuzakjp/css-color@npm:2.8.2" + dependencies: + "@csstools/css-calc": "npm:^2.1.1" + "@csstools/css-color-parser": "npm:^3.0.7" + "@csstools/css-parser-algorithms": "npm:^3.0.4" + "@csstools/css-tokenizer": "npm:^3.0.3" + lru-cache: "npm:^11.0.2" + checksum: 10c0/352b91ca7741876e459cd3cb350a969e842da1e532577157d38365a6da89b7d6e6944249489366ee61b8a225ede1b521e7ab305b70ad4c688b01404061eecca8 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0": version: 7.26.0 resolution: "@babel/code-frame@npm:7.26.0" @@ -3061,7 +3074,7 @@ __metadata: eslint-define-config: "npm:^2.0.0" express: "npm:^4.18.2" ioredis: "npm:^5.3.2" - jsdom: "npm:^25.0.0" + jsdom: "npm:^26.0.0" pg: "npm:^8.5.0" pg-connection-string: "npm:^2.6.0" pino: "npm:^9.0.0" @@ -4792,12 +4805,10 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0": - version: 7.1.0 - resolution: "agent-base@npm:7.1.0" - dependencies: - debug: "npm:^4.3.4" - checksum: 10c0/fc974ab57ffdd8421a2bc339644d312a9cca320c20c3393c9d8b1fd91731b9bbabdb985df5fc860f5b79d81c3e350daa3fcb31c5c07c0bb385aafc817df004ce +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 languageName: node linkType: hard @@ -7045,12 +7056,13 @@ __metadata: languageName: node linkType: hard -"cssstyle@npm:^4.1.0": - version: 4.1.0 - resolution: "cssstyle@npm:4.1.0" +"cssstyle@npm:^4.2.1": + version: 4.2.1 + resolution: "cssstyle@npm:4.2.1" dependencies: - rrweb-cssom: "npm:^0.7.1" - checksum: 10c0/05c6597e5d3e0ec6b15221f2c0ce9a0443a46cc50a6089a3ba9ee1ac27f83ff86a445a8f95435137dadd859f091fc61b6d342abaf396d3c910471b5b33cfcbfa + "@asamuzakjp/css-color": "npm:^2.8.2" + rrweb-cssom: "npm:^0.8.0" + checksum: 10c0/02ba8c47c0caaab57acadacb3eb6c0f5f009000f55d61f6563670e07d389b26edefeed497e6c1847fcd2e6bbe0b6974c2d4291f97fa0c6ec6add13a7fa926d84 languageName: node linkType: hard @@ -7761,7 +7773,7 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0, entities@npm:^4.4.0": +"entities@npm:^4.2.0, entities@npm:^4.5.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 @@ -8918,14 +8930,14 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" +"form-data@npm:^4.0.0, form-data@npm:^4.0.1": + version: 4.0.1 + resolution: "form-data@npm:4.0.1" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" mime-types: "npm:^2.1.12" - checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8 languageName: node linkType: hard @@ -9705,13 +9717,13 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5": - version: 7.0.5 - resolution: "https-proxy-agent@npm:7.0.5" +"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" dependencies: - agent-base: "npm:^7.0.2" + agent-base: "npm:^7.1.2" debug: "npm:4" - checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac languageName: node linkType: hard @@ -11316,21 +11328,21 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:^25.0.0": - version: 25.0.1 - resolution: "jsdom@npm:25.0.1" +"jsdom@npm:^26.0.0": + version: 26.0.0 + resolution: "jsdom@npm:26.0.0" dependencies: - cssstyle: "npm:^4.1.0" + cssstyle: "npm:^4.2.1" data-urls: "npm:^5.0.0" decimal.js: "npm:^10.4.3" - form-data: "npm:^4.0.0" + form-data: "npm:^4.0.1" html-encoding-sniffer: "npm:^4.0.0" http-proxy-agent: "npm:^7.0.2" - https-proxy-agent: "npm:^7.0.5" + https-proxy-agent: "npm:^7.0.6" is-potential-custom-element-name: "npm:^1.0.1" - nwsapi: "npm:^2.2.12" - parse5: "npm:^7.1.2" - rrweb-cssom: "npm:^0.7.1" + nwsapi: "npm:^2.2.16" + parse5: "npm:^7.2.1" + rrweb-cssom: "npm:^0.8.0" saxes: "npm:^6.0.0" symbol-tree: "npm:^3.2.4" tough-cookie: "npm:^5.0.0" @@ -11338,15 +11350,15 @@ __metadata: webidl-conversions: "npm:^7.0.0" whatwg-encoding: "npm:^3.1.1" whatwg-mimetype: "npm:^4.0.0" - whatwg-url: "npm:^14.0.0" + whatwg-url: "npm:^14.1.0" ws: "npm:^8.18.0" xml-name-validator: "npm:^5.0.0" peerDependencies: - canvas: ^2.11.2 + canvas: ^3.0.0 peerDependenciesMeta: canvas: optional: true - checksum: 10c0/6bda32a6dfe4e37a30568bf51136bdb3ba9c0b72aadd6356280404275a34c9e097c8c25b5eb3c742e602623741e172da977ff456684befd77c9042ed9bf8c2b4 + checksum: 10c0/e48725ba4027edcfc9bca5799eaec72c6561ecffe3675a8ff87fe9c3541ca4ff9f82b4eff5b3d9c527302da0d859b2f60e9364347a5d42b77f5c76c436c569dc languageName: node linkType: hard @@ -11813,6 +11825,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.2": + version: 11.0.2 + resolution: "lru-cache@npm:11.0.2" + checksum: 10c0/c993b8e06ead0b24b969c1dbb5b301716aed66e320e9014a80012f5febe280b438f28ff50046b2c55ff404e889351ccb332ff91f8dd175a21f5eae80e3fb155f + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -12640,10 +12659,10 @@ __metadata: languageName: node linkType: hard -"nwsapi@npm:^2.2.12, nwsapi@npm:^2.2.2": - version: 2.2.12 - resolution: "nwsapi@npm:2.2.12" - checksum: 10c0/95e9623d63df111405503df8c5d800e26f71675d319e2c9c70cddfa31e5ace1d3f8b6d98d354544fc156a1506d920ec291e303fab761e4f99296868e199a466e +"nwsapi@npm:^2.2.16, nwsapi@npm:^2.2.2": + version: 2.2.16 + resolution: "nwsapi@npm:2.2.16" + checksum: 10c0/0aa0637f4d51043d0183d994e08336bae996b03b42984381bf09ebdf3ff4909c018eda6b2a8aba0a08f3ea8303db8a0dad0608b38dc0bff15fd87017286ae21a languageName: node linkType: hard @@ -13040,12 +13059,12 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^7.0.0, parse5@npm:^7.1.1, parse5@npm:^7.1.2": - version: 7.1.2 - resolution: "parse5@npm:7.1.2" +"parse5@npm:^7.0.0, parse5@npm:^7.1.1, parse5@npm:^7.2.1": + version: 7.2.1 + resolution: "parse5@npm:7.2.1" dependencies: - entities: "npm:^4.4.0" - checksum: 10c0/297d7af8224f4b5cb7f6617ecdae98eeaed7f8cbd78956c42785e230505d5a4f07cef352af10d3006fa5c1544b76b57784d3a22d861ae071bbc460c649482bf4 + entities: "npm:^4.5.0" + checksum: 10c0/829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80 languageName: node linkType: hard @@ -15590,10 +15609,10 @@ __metadata: languageName: node linkType: hard -"rrweb-cssom@npm:^0.7.1": - version: 0.7.1 - resolution: "rrweb-cssom@npm:0.7.1" - checksum: 10c0/127b8ca6c8aac45e2755abbae6138d4a813b1bedc2caabf79466ae83ab3cfc84b5bfab513b7033f0aa4561c7753edf787d0dd01163ceacdee2e8eb1b6bf7237e +"rrweb-cssom@npm:^0.8.0": + version: 0.8.0 + resolution: "rrweb-cssom@npm:0.8.0" + checksum: 10c0/56f2bfd56733adb92c0b56e274c43f864b8dd48784d6fe946ef5ff8d438234015e59ad837fc2ad54714b6421384141c1add4eb569e72054e350d1f8a50b8ac7b languageName: node linkType: hard @@ -18334,13 +18353,13 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^14.0.0": - version: 14.0.0 - resolution: "whatwg-url@npm:14.0.0" +"whatwg-url@npm:^14.0.0, whatwg-url@npm:^14.1.0": + version: 14.1.0 + resolution: "whatwg-url@npm:14.1.0" dependencies: tr46: "npm:^5.0.0" webidl-conversions: "npm:^7.0.0" - checksum: 10c0/ac32e9ba9d08744605519bbe9e1371174d36229689ecc099157b6ba102d4251a95e81d81f3d80271eb8da182eccfa65653f07f0ab43ea66a6934e643fd091ba9 + checksum: 10c0/f00104f1c67ce086ba8ffedab529cbbd9aefd8c0a6555320026de7aeff31f91c38680f95818b140a7c9cc657cde3781e567835dda552ddb1e2b8faaba0ac3cb6 languageName: node linkType: hard From 51a92427cea51064debe59abe70c48a39fac41e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:04:14 +0100 Subject: [PATCH 09/73] Update dependency react-intl to v7 (#32954) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 128 ++++++++------------------------------------------- 2 files changed, 21 insertions(+), 109 deletions(-) diff --git a/package.json b/package.json index a4817816a2bf59..6cb314db344b85 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "react-hotkeys": "^1.1.4", "react-immutable-proptypes": "^2.2.0", "react-immutable-pure-component": "^2.2.2", - "react-intl": "^6.4.2", + "react-intl": "^7.0.0", "react-motion": "^0.5.2", "react-notification": "^6.8.5", "react-overlays": "^5.2.1", diff --git a/yarn.lock b/yarn.lock index 3136b05aa5b0de..e708282d879852 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2280,17 +2280,6 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.2.4": - version: 2.2.4 - resolution: "@formatjs/ecma402-abstract@npm:2.2.4" - dependencies: - "@formatjs/fast-memoize": "npm:2.2.3" - "@formatjs/intl-localematcher": "npm:0.5.8" - tslib: "npm:2" - checksum: 10c0/3f262533fa704ea7a1a7a8107deee2609774a242c621f8cb5dd4bf4c97abf2fc12f5aeda3f4ce85be18147c484a0ca87303dca6abef53290717e685c55eabd2d - languageName: node - linkType: hard - "@formatjs/ecma402-abstract@npm:2.3.2": version: 2.3.2 resolution: "@formatjs/ecma402-abstract@npm:2.3.2" @@ -2312,15 +2301,6 @@ __metadata: languageName: node linkType: hard -"@formatjs/fast-memoize@npm:2.2.3": - version: 2.2.3 - resolution: "@formatjs/fast-memoize@npm:2.2.3" - dependencies: - tslib: "npm:2" - checksum: 10c0/f1004c3b280de7e362bd37c5f48ff34c2ba1d6271d4a7b695fed561d1201a3379397824d8bffbf15fecee344d1e70398393bbb04297f242692310a305f12e75b - languageName: node - linkType: hard - "@formatjs/fast-memoize@npm:2.2.6": version: 2.2.6 resolution: "@formatjs/fast-memoize@npm:2.2.6" @@ -2341,17 +2321,6 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-messageformat-parser@npm:2.9.4": - version: 2.9.4 - resolution: "@formatjs/icu-messageformat-parser@npm:2.9.4" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.4" - "@formatjs/icu-skeleton-parser": "npm:1.8.8" - tslib: "npm:2" - checksum: 10c0/f1ed14ece7ef0abc9fb62e323b78c994fc772d346801ad5aaa9555e1a7d5c0fda791345f4f2e53a3223f0b82c1a4eaf9a83544c1c20cb39349d1a39bedcf1648 - languageName: node - linkType: hard - "@formatjs/icu-messageformat-parser@npm:2.9.8": version: 2.9.8 resolution: "@formatjs/icu-messageformat-parser@npm:2.9.8" @@ -2383,38 +2352,6 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-skeleton-parser@npm:1.8.8": - version: 1.8.8 - resolution: "@formatjs/icu-skeleton-parser@npm:1.8.8" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.4" - tslib: "npm:2" - checksum: 10c0/5ad78a5682e83b973e6fed4fca68660b944c41d1e941f0c84d69ff3d10ae835330062dc0a2cf0d237d2675ad3463405061a3963c14c2b9d8d1c1911f892b1a8d - languageName: node - linkType: hard - -"@formatjs/intl-displaynames@npm:6.8.5": - version: 6.8.5 - resolution: "@formatjs/intl-displaynames@npm:6.8.5" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.4" - "@formatjs/intl-localematcher": "npm:0.5.8" - tslib: "npm:2" - checksum: 10c0/1092d6bac9ba7ee22470b85c9af16802244aa8a54f07e6cd560d15b96e8a08fc359f20dee88a064fe4c9ca8860f439abb109cbb7977b9ccceb846e28aacdf29c - languageName: node - linkType: hard - -"@formatjs/intl-listformat@npm:7.7.5": - version: 7.7.5 - resolution: "@formatjs/intl-listformat@npm:7.7.5" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.4" - "@formatjs/intl-localematcher": "npm:0.5.8" - tslib: "npm:2" - checksum: 10c0/f514397f6b05ac29171fffbbd15636fbec086080058c79c159f24edd2038747c22579d46ebf339cbb672f8505ea408e5d960d6751064c16e02d18445cf4e7e61 - languageName: node - linkType: hard - "@formatjs/intl-localematcher@npm:0.5.10": version: 0.5.10 resolution: "@formatjs/intl-localematcher@npm:0.5.10" @@ -2433,15 +2370,6 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.5.8": - version: 0.5.8 - resolution: "@formatjs/intl-localematcher@npm:0.5.8" - dependencies: - tslib: "npm:2" - checksum: 10c0/7a660263986326b662d4cb537e8386331c34fda61fb830b105e6c62d49be58ace40728dae614883b27a41cec7b1df8b44f72f79e16e6028bfca65d398dc04f3b - languageName: node - linkType: hard - "@formatjs/intl-pluralrules@npm:^5.2.2": version: 5.4.2 resolution: "@formatjs/intl-pluralrules@npm:5.4.2" @@ -2454,23 +2382,21 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl@npm:2.10.15": - version: 2.10.15 - resolution: "@formatjs/intl@npm:2.10.15" +"@formatjs/intl@npm:3.1.0": + version: 3.1.0 + resolution: "@formatjs/intl@npm:3.1.0" dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.4" - "@formatjs/fast-memoize": "npm:2.2.3" - "@formatjs/icu-messageformat-parser": "npm:2.9.4" - "@formatjs/intl-displaynames": "npm:6.8.5" - "@formatjs/intl-listformat": "npm:7.7.5" - intl-messageformat: "npm:10.7.7" + "@formatjs/ecma402-abstract": "npm:2.3.2" + "@formatjs/fast-memoize": "npm:2.2.6" + "@formatjs/icu-messageformat-parser": "npm:2.9.8" + intl-messageformat: "npm:10.7.11" tslib: "npm:2" peerDependencies: - typescript: ^4.7 || 5 + typescript: 5 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/5d51fd0785d5547f375991d7df2d6303479b0083eeb35c42c30c9633aab77101895498f1eace419fd34fdb5c84aea19037c5280c3a9d85f9c3ffe6eef76b6f39 + checksum: 10c0/a073768fffc51696eb7bd25fe1f0afdda1a0e38db3e2dd9b2fc3138ea799f00ef522f3d3083626ad3acbf913593254cfd728a6c6b08ef4f167dd132626a7e9fc languageName: node linkType: hard @@ -3004,7 +2930,7 @@ __metadata: react-hotkeys: "npm:^1.1.4" react-immutable-proptypes: "npm:^2.2.0" react-immutable-pure-component: "npm:^2.2.2" - react-intl: "npm:^6.4.2" + react-intl: "npm:^7.0.0" react-motion: "npm:^0.5.2" react-notification: "npm:^6.8.5" react-overlays: "npm:^5.2.1" @@ -9982,19 +9908,7 @@ __metadata: languageName: node linkType: hard -"intl-messageformat@npm:10.7.7": - version: 10.7.7 - resolution: "intl-messageformat@npm:10.7.7" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.4" - "@formatjs/fast-memoize": "npm:2.2.3" - "@formatjs/icu-messageformat-parser": "npm:2.9.4" - tslib: "npm:2" - checksum: 10c0/691895fb6a73a2feb2569658706e0d452861441de184dd1c9201e458a39fb80fc80080dd40d3d370400a52663f87de7a6d5a263c94245492f7265dd760441a95 - languageName: node - linkType: hard - -"intl-messageformat@npm:^10.3.5": +"intl-messageformat@npm:10.7.11, intl-messageformat@npm:^10.3.5": version: 10.7.11 resolution: "intl-messageformat@npm:10.7.11" dependencies: @@ -14744,27 +14658,25 @@ __metadata: languageName: node linkType: hard -"react-intl@npm:^6.4.2": - version: 6.8.9 - resolution: "react-intl@npm:6.8.9" +"react-intl@npm:^7.0.0": + version: 7.1.0 + resolution: "react-intl@npm:7.1.0" dependencies: - "@formatjs/ecma402-abstract": "npm:2.2.4" - "@formatjs/icu-messageformat-parser": "npm:2.9.4" - "@formatjs/intl": "npm:2.10.15" - "@formatjs/intl-displaynames": "npm:6.8.5" - "@formatjs/intl-listformat": "npm:7.7.5" + "@formatjs/ecma402-abstract": "npm:2.3.2" + "@formatjs/icu-messageformat-parser": "npm:2.9.8" + "@formatjs/intl": "npm:3.1.0" "@types/hoist-non-react-statics": "npm:3" "@types/react": "npm:16 || 17 || 18" hoist-non-react-statics: "npm:3" - intl-messageformat: "npm:10.7.7" + intl-messageformat: "npm:10.7.11" tslib: "npm:2" peerDependencies: react: ^16.6.0 || 17 || 18 - typescript: ^4.7 || 5 + typescript: 5 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/d42a6252beac5448b4a248d84923b0f75dfbbee6208cd5c49ac2f525714ab94efe2a4933d464c64cb161ddccaa37b83dffb2dd0529428219b8a60ce548da3e57 + checksum: 10c0/9d69e316a5f5c6d31fa77f136b595079db2f75f63398cf8253d407878246dd5bcf0cc2eb4d7d4aa0646530ee58b16ce9a8c3876a5c2f0dc38fdda7e4f8c07615 languageName: node linkType: hard From 91c75a63616e8a8616cc09484e033bab2780a925 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 9 Jan 2025 14:38:23 +0100 Subject: [PATCH 10/73] Re-introduce `application_id` in `ScheduledStatusSerializer` (#33505) --- app/serializers/rest/scheduled_status_serializer.rb | 4 ---- spec/serializers/rest/scheduled_status_serializer_spec.rb | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/serializers/rest/scheduled_status_serializer.rb b/app/serializers/rest/scheduled_status_serializer.rb index 8aa0d89386cb75..7c54f39c0d137d 100644 --- a/app/serializers/rest/scheduled_status_serializer.rb +++ b/app/serializers/rest/scheduled_status_serializer.rb @@ -8,8 +8,4 @@ class REST::ScheduledStatusSerializer < ActiveModel::Serializer def id object.id.to_s end - - def params - object.params.without('application_id') - end end diff --git a/spec/serializers/rest/scheduled_status_serializer_spec.rb b/spec/serializers/rest/scheduled_status_serializer_spec.rb index 08ad8a6f8aebe4..2cf00986542c4e 100644 --- a/spec/serializers/rest/scheduled_status_serializer_spec.rb +++ b/spec/serializers/rest/scheduled_status_serializer_spec.rb @@ -17,7 +17,7 @@ expect(subject.deep_symbolize_keys) .to include( scheduled_at: be_a(String).and(match_api_datetime_format), - params: not_include(:application_id) + params: include(:application_id) ) end end From c6c8e7e6ab03754f6456eaf7bfc26a9a318140dc Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 9 Jan 2025 14:47:12 +0100 Subject: [PATCH 11/73] Fix last paginated notification group only including data on a single notification (#33271) --- .../api/v2/notifications_controller.rb | 23 ++++++++- app/models/notification_group.rb | 26 +++++++--- spec/requests/api/v2/notifications_spec.rb | 49 +++++++++++++++++++ 3 files changed, 89 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/v2/notifications_controller.rb b/app/controllers/api/v2/notifications_controller.rb index c070c0e5e71892..cc38b95114e86f 100644 --- a/app/controllers/api/v2/notifications_controller.rb +++ b/app/controllers/api/v2/notifications_controller.rb @@ -80,10 +80,31 @@ def load_grouped_notifications return [] if @notifications.empty? MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_grouped_notifications') do - NotificationGroup.from_notifications(@notifications, pagination_range: (@notifications.last.id)..(@notifications.first.id), grouped_types: params[:grouped_types]) + pagination_range = (@notifications.last.id)..@notifications.first.id + + # If the page is incomplete, we know we are on the last page + if incomplete_page? + if paginating_up? + pagination_range = @notifications.last.id...(params[:max_id]&.to_i) + else + range_start = params[:since_id]&.to_i + range_start += 1 unless range_start.nil? + pagination_range = range_start..(@notifications.first.id) + end + end + + NotificationGroup.from_notifications(@notifications, pagination_range: pagination_range, grouped_types: params[:grouped_types]) end end + def incomplete_page? + @notifications.size < limit_param(DEFAULT_NOTIFICATIONS_LIMIT) + end + + def paginating_up? + params[:min_id].present? + end + def browserable_account_notifications current_account.notifications.without_suspended.browserable( types: Array(browserable_params[:types]), diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index 9331b9406f793b..bf790bf7cd0ef2 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -64,21 +64,31 @@ def load_groups_data(account_id, group_keys, pagination_range: nil) binds = [ account_id, SAMPLE_ACCOUNTS_SIZE, - pagination_range.begin, - pagination_range.end, ActiveRecord::Relation::QueryAttribute.new('group_keys', group_keys, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::String.new)), + pagination_range.begin || 0, ] + binds << pagination_range.end unless pagination_range.end.nil? + + upper_bound_cond = begin + if pagination_range.end.nil? + '' + elsif pagination_range.exclude_end? + 'AND id < $5' + else + 'AND id <= $5' + end + end ActiveRecord::Base.connection.select_all(<<~SQL.squish, 'grouped_notifications', binds).cast_values.to_h { |k, *values| [k, values] } SELECT groups.group_key, - (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1), - array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT $2), - (SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4) AS notifications_count, - (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $3 ORDER BY id ASC LIMIT 1) AS min_id, - (SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1) + (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1), + array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT $2), + (SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond}) AS notifications_count, + (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $4 ORDER BY id ASC LIMIT 1) AS min_id, + (SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1) FROM - unnest($5::text[]) AS groups(group_key); + unnest($3::text[]) AS groups(group_key); SQL else binds = [ diff --git a/spec/requests/api/v2/notifications_spec.rb b/spec/requests/api/v2/notifications_spec.rb index ffa0a71c779e1b..aa4a8615576dc1 100644 --- a/spec/requests/api/v2/notifications_spec.rb +++ b/spec/requests/api/v2/notifications_spec.rb @@ -143,6 +143,55 @@ end end + context 'when there are numerous notifications for the same final group' do + before do + user.account.notifications.destroy_all + 5.times.each { FavouriteService.new.call(Fabricate(:account), user.account.statuses.first) } + end + + context 'with no options' do + it 'returns a notification group covering all notifications' do + subject + + notification_ids = user.account.notifications.reload.pluck(:id) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:notification_groups]).to contain_exactly( + a_hash_including( + type: 'favourite', + sample_account_ids: have_attributes(size: 5), + page_min_id: notification_ids.first.to_s, + page_max_id: notification_ids.last.to_s + ) + ) + end + end + + context 'with min_id param' do + let(:params) { { min_id: user.account.notifications.reload.first.id - 1 } } + + it 'returns a notification group covering all notifications' do + subject + + notification_ids = user.account.notifications.reload.pluck(:id) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:notification_groups]).to contain_exactly( + a_hash_including( + type: 'favourite', + sample_account_ids: have_attributes(size: 5), + page_min_id: notification_ids.first.to_s, + page_max_id: notification_ids.last.to_s + ) + ) + end + end + end + context 'with no options' do it 'returns expected notification types', :aggregate_failures do subject From a8b2b474d79b9621dfa6309b83a5678f25926cde Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 9 Jan 2025 15:22:33 +0100 Subject: [PATCH 12/73] Add timestamp to all announcements in Web UI (#18329) --- .../components/announcements.jsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.jsx b/app/javascript/mastodon/features/getting_started/components/announcements.jsx index 713ad9f0694650..ad66d2e5faff4c 100644 --- a/app/javascript/mastodon/features/getting_started/components/announcements.jsx +++ b/app/javascript/mastodon/features/getting_started/components/announcements.jsx @@ -335,15 +335,29 @@ class Announcement extends ImmutablePureComponent { const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at')); const now = new Date(); const hasTimeRange = startsAt && endsAt; - const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear(); - const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear(); const skipTime = announcement.get('all_day'); + let timestamp = null; + if (hasTimeRange) { + const skipYear = startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear(); + const skipEndDate = startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear(); + timestamp = ( + <> + - + + ); + } else { + const publishedAt = new Date(announcement.get('published_at')); + timestamp = ( + + ); + } + return (
- {hasTimeRange && · - } + · {timestamp} From 9b8d1fb6d171e13f043c05e9311a5de6fb9029b6 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 9 Jan 2025 09:32:48 -0500 Subject: [PATCH 13/73] Add `Account#actor_type_application?` query method (#33525) --- app/models/account.rb | 10 +++++++--- spec/models/account_spec.rb | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index fefc40869ff5c8..7321cff3db709d 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -107,14 +107,14 @@ 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 }, if: -> { (remote? || actor_type_application?) && will_save_change_to_username? } # Remote user validations validates :uri, presence: true, unless: :local?, on: :create # Local user validations - validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } - validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } + validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && !actor_type_application? } + validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && !actor_type_application? } validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? } validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? } validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? } @@ -208,6 +208,10 @@ def bot=(val) self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person' end + def actor_type_application? + actor_type == 'Application' + end + def group? actor_type == 'Group' end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index f70690ddb6b37c..3e9b36652fe38c 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -80,6 +80,20 @@ end end + describe '#actor_type_application?' do + context 'when the actor is not of type application' do + subject { Fabricate.build :account, actor_type: 'Person' } + + it { is_expected.to_not be_actor_type_application } + end + + context 'when the actor is of type application' do + subject { Fabricate.build :account, actor_type: 'Application' } + + it { is_expected.to be_actor_type_application } + end + end + describe 'Local domain user methods' do subject { Fabricate(:account, domain: nil, username: 'alice') } From 3a4aed989021f0feeda491972bb73a3263c3255a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 9 Jan 2025 09:39:49 -0500 Subject: [PATCH 14/73] Rename `app/helpers/jsonld_helper.rb` to `app/helpers/json_ld_helper.rb` (#33489) --- .rubocop_todo.yml | 6 +++--- app/helpers/{jsonld_helper.rb => json_ld_helper.rb} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename app/helpers/{jsonld_helper.rb => json_ld_helper.rb} (100%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 552054898e900b..12ef0ad6201025 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.69.1. +# using RuboCop version 1.69.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -8,7 +8,7 @@ Lint/NonLocalExitFromIterator: Exclude: - - 'app/helpers/jsonld_helper.rb' + - 'app/helpers/json_ld_helper.rb' # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: @@ -82,7 +82,7 @@ Style/MutableConstant: # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - - 'app/helpers/jsonld_helper.rb' + - 'app/helpers/json_ld_helper.rb' - 'app/lib/admin/system_check/message.rb' - 'app/lib/request.rb' - 'app/lib/webfinger.rb' diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/json_ld_helper.rb similarity index 100% rename from app/helpers/jsonld_helper.rb rename to app/helpers/json_ld_helper.rb From 54e20301462b381f27c50ed305abeedde1ace878 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 9 Jan 2025 10:08:39 -0500 Subject: [PATCH 15/73] Add `AccountWarning#appeal_eligible?` method (#33526) --- app/models/account_warning.rb | 5 +++++ app/models/appeal.rb | 4 +--- app/policies/account_warning_policy.rb | 2 +- spec/models/account_warning_spec.rb | 14 ++++++++++++++ spec/models/appeal_spec.rb | 2 +- spec/policies/account_warning_policy_spec.rb | 4 ++-- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb index 7aa474887b4e2a..9058f73fb8f057 100644 --- a/app/models/account_warning.rb +++ b/app/models/account_warning.rb @@ -27,6 +27,7 @@ class AccountWarning < ApplicationRecord suspend: 4_000, }, suffix: :action + APPEAL_WINDOW = 20.days RECENT_PERIOD = 3.months.freeze normalizes :text, with: ->(text) { text.to_s }, apply_to_nil: true @@ -49,6 +50,10 @@ def overruled? overruled_at.present? end + def appeal_eligible? + created_at >= APPEAL_WINDOW.ago + end + def to_log_human_identifier target_account.acct end diff --git a/app/models/appeal.rb b/app/models/appeal.rb index fafa75e69d8830..6a75fec661be06 100644 --- a/app/models/appeal.rb +++ b/app/models/appeal.rb @@ -16,8 +16,6 @@ # updated_at :datetime not null # class Appeal < ApplicationRecord - MAX_STRIKE_AGE = 20.days - TEXT_LENGTH_LIMIT = 2_000 belongs_to :account @@ -68,6 +66,6 @@ def to_log_route_param private def validate_time_frame - errors.add(:base, I18n.t('strikes.errors.too_late')) if strike.created_at < MAX_STRIKE_AGE.ago + errors.add(:base, I18n.t('strikes.errors.too_late')) unless strike.appeal_eligible? end end diff --git a/app/policies/account_warning_policy.rb b/app/policies/account_warning_policy.rb index 4f8df7420e492a..976cae6c9c822e 100644 --- a/app/policies/account_warning_policy.rb +++ b/app/policies/account_warning_policy.rb @@ -6,7 +6,7 @@ def show? end def appeal? - target? && record.created_at >= Appeal::MAX_STRIKE_AGE.ago + target? && record.appeal_eligible? end private diff --git a/spec/models/account_warning_spec.rb b/spec/models/account_warning_spec.rb index 37866ce3da95f6..9fe2b331eb02e6 100644 --- a/spec/models/account_warning_spec.rb +++ b/spec/models/account_warning_spec.rb @@ -8,4 +8,18 @@ it { is_expected.to normalize(:text).from(nil).to('') } end end + + describe '#appeal_eligible?' do + context 'when created too long ago' do + subject { Fabricate.build :account_warning, created_at: (described_class::APPEAL_WINDOW * 2).ago } + + it { is_expected.to_not be_appeal_eligible } + end + + context 'when created recently' do + subject { Fabricate.build :account_warning, created_at: (described_class::APPEAL_WINDOW - 2.days).ago } + + it { is_expected.to be_appeal_eligible } + end + end end diff --git a/spec/models/appeal_spec.rb b/spec/models/appeal_spec.rb index 06775f5dd68d04..d624e149491542 100644 --- a/spec/models/appeal_spec.rb +++ b/spec/models/appeal_spec.rb @@ -9,7 +9,7 @@ it { is_expected.to validate_length_of(:text).is_at_most(described_class::TEXT_LENGTH_LIMIT) } context 'with a strike created too long ago' do - let(:strike) { Fabricate.build :account_warning, created_at: (described_class::MAX_STRIKE_AGE * 2).ago } + let(:strike) { Fabricate.build :account_warning, created_at: (AccountWarning::APPEAL_WINDOW * 2).ago } it { is_expected.to_not allow_values(strike).for(:strike).against(:base).on(:create) } end diff --git a/spec/policies/account_warning_policy_spec.rb b/spec/policies/account_warning_policy_spec.rb index 75142e20715fce..26037948863c15 100644 --- a/spec/policies/account_warning_policy_spec.rb +++ b/spec/policies/account_warning_policy_spec.rb @@ -31,11 +31,11 @@ context 'when account is target' do context 'when record is appealable' do - it { is_expected.to permit(account, AccountWarning.new(target_account_id: account.id, created_at: Appeal::MAX_STRIKE_AGE.ago + 1.hour)) } + it { is_expected.to permit(account, AccountWarning.new(target_account_id: account.id, created_at: AccountWarning::APPEAL_WINDOW.ago + 1.hour)) } end context 'when record is not appealable' do - it { is_expected.to_not permit(account, AccountWarning.new(target_account_id: account.id, created_at: Appeal::MAX_STRIKE_AGE.ago - 1.hour)) } + it { is_expected.to_not permit(account, AccountWarning.new(target_account_id: account.id, created_at: AccountWarning::APPEAL_WINDOW.ago - 1.hour)) } end end end From f98972e4ebbdaf586c3e267bcae35a8922849af3 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 04:00:31 -0500 Subject: [PATCH 16/73] Use `with_options` for Account `if: :local?` validation group (#33529) --- app/models/account.rb | 10 +++++----- spec/models/account_spec.rb | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 7321cff3db709d..6258857b1bc212 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -119,11 +119,11 @@ class Account < ApplicationRecord validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? } validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? } validates_with EmptyProfileFieldNamesValidator, if: -> { local? && will_save_change_to_fields? } - with_options on: :create do - validates :uri, absence: true, if: :local? - validates :inbox_url, absence: true, if: :local? - validates :shared_inbox_url, absence: true, if: :local? - validates :followers_url, absence: true, if: :local? + with_options on: :create, if: :local? do + validates :followers_url, absence: true + validates :inbox_url, absence: true + validates :shared_inbox_url, absence: true + validates :uri, absence: true end normalizes :username, with: ->(username) { username.squish } diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 3e9b36652fe38c..5b995b4af63433 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -595,6 +595,11 @@ it { is_expected.to allow_value(fields_empty_name_value).for(:fields) } it { is_expected.to_not allow_values(fields_over_limit, fields_empty_name).for(:fields) } + + it { is_expected.to validate_absence_of(:followers_url).on(:create) } + it { is_expected.to validate_absence_of(:inbox_url).on(:create) } + it { is_expected.to validate_absence_of(:shared_inbox_url).on(:create) } + it { is_expected.to validate_absence_of(:uri).on(:create) } end context 'when account is remote' do From 846c89b66e6300e438df17f3a2d11645cd460851 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 09:00:41 +0000 Subject: [PATCH 17/73] New Crowdin Translations (automated) (#33541) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/hu.json | 2 +- app/javascript/mastodon/locales/pl.json | 3 ++ app/javascript/mastodon/locales/pt-BR.json | 6 ++-- app/javascript/mastodon/locales/pt-PT.json | 4 +-- config/locales/lv.yml | 41 +++++++++++++++++----- config/locales/simple_form.lv.yml | 2 +- config/locales/simple_form.sv.yml | 2 +- 7 files changed, 44 insertions(+), 16 deletions(-) diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 703abf242b4ee2..847f7871fa326d 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -453,7 +453,7 @@ "keyboard_shortcuts.requests": "Követési kérések listájának megnyitása", "keyboard_shortcuts.search": "Fókuszálás a keresősávra", "keyboard_shortcuts.spoilers": "Tartalmi figyelmeztetés mező megjelenítése/elrejtése", - "keyboard_shortcuts.start": "\"Első lépések\" oszlop megnyitása", + "keyboard_shortcuts.start": "„Első lépések” oszlop megnyitása", "keyboard_shortcuts.toggle_hidden": "Tartalmi figyelmeztetéssel ellátott szöveg megjelenítése/elrejtése", "keyboard_shortcuts.toggle_sensitivity": "Média megjelenítése/elrejtése", "keyboard_shortcuts.toot": "Új bejegyzés írása", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index fe56267ea1872c..f9cb9743ac3b7a 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -406,6 +406,9 @@ "ignore_notifications_modal.not_followers_title": "Ignoruj powiadomienia od użytkowników którzy cię nie obserwują?", "ignore_notifications_modal.not_following_title": "Ignoruj powiadomienia od użytkowników których nie obserwujesz?", "ignore_notifications_modal.private_mentions_title": "Ignoruj powiadomienia o nieproszonych wzmiankach prywatnych?", + "interaction_modal.action.favourite": "Aby kontynuować, musisz dodać do ulubionych na swoim koncie.", + "interaction_modal.action.follow": "Aby kontynuować, musisz obserwować ze swojego konta.", + "interaction_modal.no_account_yet": "Nie masz jeszcze konta?", "interaction_modal.on_another_server": "Na innym serwerze", "interaction_modal.on_this_server": "Na tym serwerze", "interaction_modal.title.favourite": "Polub wpis użytkownika {name}", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 1505d333ba930d..142ae33c585264 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -243,12 +243,12 @@ "dismissable_banner.explore_statuses": "Estas publicações através do fediverse estão ganhando atenção hoje. Publicações mais recentes com mais boosts e favoritos são classificados mais altamente.", "dismissable_banner.explore_tags": "Estas hashtags estão ganhando atenção hoje no fediverse. Hashtags usadas por muitas pessoas diferentes são classificadas mais altamente.", "dismissable_banner.public_timeline": "Estas são as publicações mais recentes das pessoas no fediverse que as pessoas do {domain} seguem.", - "domain_block_modal.block": "Servidor de blocos.", - "domain_block_modal.block_account_instead": "Bloco @(nome)", + "domain_block_modal.block": "Bloquear servidor", + "domain_block_modal.block_account_instead": "Bloquear @{name}", "domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicações antigas.", "domain_block_modal.they_cant_follow": "Ninguém deste servidor pode lhe seguir.", "domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.", - "domain_block_modal.title": "Dominio do bloco", + "domain_block_modal.title": "Bloquear domínio?", "domain_block_modal.you_will_lose_num_followers": "Você perderá {followersCount, plural, one {{followersCountDisplay} seguidor} other {{followersCountDisplay} seguidores}} e {followingCount, plural, one {{followingCountDisplay} pessoa que você segue} other {{followingCountDisplay} pessoas que você segue}}.", "domain_block_modal.you_will_lose_relationships": "Você irá perder todos os seguidores e pessoas que você segue neste servidor.", "domain_block_modal.you_wont_see_posts": "Você não verá postagens ou notificações de usuários neste servidor.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index b4b3c40dc53749..f7329ad97eac9a 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -244,7 +244,7 @@ "dismissable_banner.explore_tags": "Estas etiquetas estão a ganhar força no fediverso atualmente. As etiquetas que são utilizadas por mais pessoas diferentes são classificadas numa posição mais elevada.", "dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas no fediverso que as pessoas em {domain} seguem.", "domain_block_modal.block": "Bloquear servidor", - "domain_block_modal.block_account_instead": "Bloquear antes @{name}", + "domain_block_modal.block_account_instead": "Em vez disso, bloquear @{name}", "domain_block_modal.they_can_interact_with_old_posts": "As pessoas deste servidor podem interagir com as tuas publicações antigas.", "domain_block_modal.they_cant_follow": "Ninguém deste servidor pode seguir-te.", "domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.", @@ -260,7 +260,7 @@ "domain_pill.their_username": "O identificador único dele no seu servidor. É possível encontrar utilizadores com o mesmo nome de utilizador em servidores diferentes.", "domain_pill.username": "Nome de utilizador", "domain_pill.whats_in_a_handle": "Em que consiste um identificador?", - "domain_pill.who_they_are": "Uma vez que os identificadores dizem quem é alguém e onde está, pode interagir com as pessoas através da rede social de .", + "domain_pill.who_they_are": "Uma vez que os identificadores dizem quem é alguém e onde está, podes interagir com as pessoas através da rede social de .", "domain_pill.who_you_are": "Uma vez que o teu identificador indica quem és e onde estás, as pessoas podem interagir contigo através da rede social de .", "domain_pill.your_handle": "O teu identificador:", "domain_pill.your_server": "A tua casa digital, onde se encontram todas as tuas publicações. Não gostas deste? Muda de servidor a qualquer momento e leva também os teus seguidores.", diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 09b629c4dadc48..2d256b475e6625 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -217,7 +217,7 @@ lv: enable_user: Ieslēgt Lietotāju memorialize_account: Saglabāt Kontu Piemiņai promote_user: Izceltt Lietotāju - publish_terms_of_service: Publicēt pakalpojuma izmantošanas nosacījumus + publish_terms_of_service: Publicēt pakalpojuma izmantošanas noteikumus reject_appeal: Noraidīt Apelāciju reject_user: Noraidīt lietotāju remove_avatar_user: Noņemt profila attēlu @@ -274,7 +274,7 @@ lv: enable_user_html: "%{name} iespējoja pieteikšanos lietotājam %{target}" memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiņas lapu" promote_user_html: "%{name} paaugstināja lietotāju %{target}" - publish_terms_of_service_html: "%{name} padarīja pieejamus pakalpojuma izmantošanas nosacījumu atjauninājumus" + publish_terms_of_service_html: "%{name} padarīja pieejamus pakalpojuma izmantošanas noteikumu atjauninājumus" reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma iebildumu no %{target}" reject_user_html: "%{name} noraidīja reģistrēšanos no %{target}" remove_avatar_user_html: "%{name} noņēma %{target} profila attēlu" @@ -748,9 +748,9 @@ lv: rules: add_new: Pievienot noteikumu delete: Dzēst - description_html: Kaut arī lielākā daļa apgalvo, ka ir lasījuši un piekrīt pakalpojuma izmantošanas nosacījumiem, parasti cilvēki tos neizlasa, līdz rodas sarežģījumi. Padari vienkāršāku sava servera noteikumu pārskatīšanu, sniedzot tos vienkāršā uzsvēruma punktu sarakstā! Jāmēģina atsevišķus noteikumus veidot īsus un vienkāršus, bet jāmēģina arī tos nesadalīt daudzos atsevišķos vienumos. + description_html: Kaut arī lielākā daļa apgalvo, ka ir lasījuši un piekrīt pakalpojuma izmantošanas noteikumiem, parasti cilvēki tos neizlasa, līdz rodas sarežģījumi. Padari vienkāršāku sava servera noteikumu pārskatīšanu, sniedzot tos vienkāršā uzsvēruma punktu sarakstā! Jāmēģina atsevišķus noteikumus veidot īsus un vienkāršus, bet jāmēģina arī tos nesadalīt daudzos atsevišķos vienumos. edit: Labot nosacījumu - empty: Servera noteikumi vēl nav definēti. + empty: Vēl nav pievienots neviens servera noteikums. title: Servera noteikumi settings: about: @@ -921,7 +921,7 @@ lv: title: Tēmturi updated_msg: Tēmtura iestatījumi ir veiksmīgi atjaunināti terms_of_service: - back: Atpakaļ uz pakalpojuma izmantošanas nosacījumiem + back: Atpakaļ uz pakalpojuma izmantošanas noteikumiem changelog: Kas ir mainījies create: Izmantot savus current: Pašreizējie @@ -929,9 +929,27 @@ lv: generate: Izmantot sagatavi generates: action: Izveidot + chance_to_review_html: "Izveidotie pakalpojuma izmantošanas noteikumi netiks automātiski publicēti. Būs iespēja izskatīt iznākumu. Lūgums norādīt nepieciešamo informāciju, lai turpinātu." + explanation_html: Pakalpojuma izmantošanas noteikumu sagatave tiek piedāvāta tikai izzināšanas nolūkam, un to nevajadzētu izmantot kā juridisku padomu jebkurā jautājumā. Lūgums sazināties ar savu juridisko padomdevēju par saviem apstākļiem un noteiktiem juridiskiem jautājumiem. + title: Pakalpojuma izmantošānas noteikumu uzstādīšana history: Vēsture + live: Darbībā + no_history: Nav ierakstu par pakalpojuma izmantošanas noteikumu izmaiņām. + no_terms_of_service_html: Pašlaik nav uzstādīti pakalpojuma izmantošanas noteikumi. Tie ir paredzēti, lai sniegtu skaidrību un aizsargātu no iespējamas atbildības strīdos ar lietotājiem. + notified_on_html: Lietotājiem paziņots %{date} + notify_users: Paziņot lietotājiem + preview: + explanation_html: 'E-pasta ziņojums tiks nosūtīts %{display_count} lietotājiem, kuri ir reģistrējušies pirms %{date}. Šis teksts tiks iekļauts e-pasta ziņojumā:' + send_preview: Nosūtīt priekšskatījumu uz %{email} + send_to_all: + one: Nosūtīt %{display_count} e-pasta ziņojumu + other: Nosūtīt %{display_count} e-pasta ziņojumus + zero: Nosūtīt %{display_count} e-pasta ziņojumu + title: Priekškatīt pakalpojuma izmantošanas noteikumu paziņojumu publish: Publicēt - published_on_html: Publicēts %{date} + published_on_html: Publicēti %{date} + save_draft: Saglabāt melnrakstu + title: Pakalpojuma izmantošanas noteikumi title: Pārvaldība trends: allow: Atļaut @@ -1172,6 +1190,7 @@ lv: view_strikes: Skati iepriekšējos brīdinājumus par savu kontu too_fast: Veidlapa ir iesniegta pārāk ātri, mēģini vēlreiz. use_security_key: Lietot drošības atslēgu + user_agreement_html: Es esmu izlasījis un piekrītu pakalpojuma izmantošanas noteikumiem un privātuma nosacījumiem author_attribution: example_title: Parauga teksts more_from_html: Vairāk no %{name} @@ -1792,6 +1811,8 @@ lv: too_late: Brīdinājuma apstrīdēšanas laiks ir nokavēts tags: does_not_match_previous_name: nesakrīt ar iepriekšējo nosaukumu + terms_of_service: + title: Pakalpojuma izmantošanas noteikumi themes: contrast: Mastodon (Augsts kontrasts) default: Mastodon (Tumšs) @@ -1842,9 +1863,13 @@ lv: subject: Tavam kontam ir piekļūts no jaunas IP adreses title: Jauna pieteikšanās terms_of_service_changed: + agreement: Ar %{domain} izmantošanas tuprināšanu tiek piekrists šiem noteikumiem. Ja ir iebildumi pret atjauninātajiem noteikumiem, savu piekrišanu var atcelt jebkurā laikā ar sava konta izdzēšanu. + changelog: 'Šeit īsumā ir aprakstīts, ko šis atjauninājums nozīmē:' + description: 'Šis e-pasta ziņojums tika saņemts, jo mēs veicam dažas izmaiņas savos pakalpojuma izmantošanas noteikumos %{domain}. Mēs aicinām pārskatīt pilnus atjauninātos noteikumus šeit:' + description_html: Šis e-pasta ziņojums tika saņemts, jo mēs veicam dažas izmaiņas savos pakalpojuma izmantošanas noteikumos %{domain}. Mēs aicinām pārskatīt pilnus atjauninātos noteikumus šeit. sign_off: "%{domain} komanda" - subject: Mūsu pakalpojuma izmantošanas nosacījumu atjauninājumi - subtitle: Mainās %{domain} pakalpojuma izmantošanas nosacījumi + subject: Mūsu pakalpojuma izmantošanas noteikumu atjauninājumi + subtitle: Mainās %{domain} pakalpojuma izmantošanas noteikumi title: Svarīgs atjauninājums warning: appeal: Iesniegt apelāciju diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 53224966f1cc09..7c7b11c1b3d12b 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -53,7 +53,7 @@ lv: locale: Lietotāja saskarnes, e-pasta ziņojumu un push paziņojumu valoda password: Izmanto vismaz 8 rakstzīmes phrase: Tiks saskaņots neatkarīgi no ziņas teksta reģistra vai satura brīdinājuma - scopes: Kuriem API lietojumprogrammai būs atļauta piekļuve. Ja izvēlies augstākā līmeņa tvērumu, tev nav jāatlasa atsevišķi vienumi. + scopes: Kuriem API lietotnei būs ļauts piekļūt. Ja atlasa augstākā līmeņa tvērumu, nav nepieciešamas atlasīt atsevišķus. setting_aggregate_reblogs: Nerādīt jaunus izcēlumus ziņām, kas nesen tika palielinātas (ietekmē tikai nesen saņemtos palielinājumus) setting_always_send_emails: Parasti e-pasta paziņojumi netiek sūtīti, kad aktīvi izmantojat Mastodon setting_default_sensitive: Sensitīva multivide pēc noklusējuma ir paslēpti, un tos var atklāt, noklikšķinot diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index 421d360fac0480..2d2c1c006038a1 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -159,7 +159,7 @@ sv: name: Etikett value: Innehåll indexable: Inkludera offentliga inlägg i sökresultaten - show_collections: Göm följare och följeslagare på profilen + show_collections: Visa följare och följeslagare på profilen unlocked: Godkänn nya följare automatiskt account_alias: acct: Namnet på det gamla kontot From 4fb3dc0363956a1a61613e90e9a4e7e945a7f714 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 04:02:48 -0500 Subject: [PATCH 18/73] Extract `CSS_COLORS` constant for `UserRole` regex validation (#33532) --- app/models/user_role.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/user_role.rb b/app/models/user_role.rb index 24cd5983f32984..d567bf5eca53f6 100644 --- a/app/models/user_role.rb +++ b/app/models/user_role.rb @@ -42,6 +42,7 @@ class UserRole < ApplicationRecord NOBODY_POSITION = -1 POSITION_LIMIT = (2**31) - 1 + CSS_COLORS = /\A#?(?:[A-F0-9]{3}){1,2}\z/i # CSS-style hex colors module Flags NONE = 0 @@ -90,7 +91,7 @@ module Flags attr_writer :current_account validates :name, presence: true, unless: :everyone? - validates :color, format: { with: /\A#?(?:[A-F0-9]{3}){1,2}\z/i }, unless: -> { color.blank? } + validates :color, format: { with: CSS_COLORS }, if: :color? validates :position, numericality: { in: (-POSITION_LIMIT..POSITION_LIMIT) } validate :validate_permissions_elevation From 2499cd01db57275d2adc43aba97302fca5cc754f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 04:04:23 -0500 Subject: [PATCH 19/73] Add `duplicate_record` helper to maintenance CLI spec (#33536) --- spec/lib/mastodon/cli/maintenance_spec.rb | 59 +++++++++-------------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb index 6a15677f43ab91..3e8eb9c3604dc4 100644 --- a/spec/lib/mastodon/cli/maintenance_spec.rb +++ b/spec/lib/mastodon/cli/maintenance_spec.rb @@ -89,10 +89,8 @@ def duplicate_local_accounts def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :accounts, name: :index_accounts_on_username_and_domain_lower - _remote_account = Fabricate(:account, username: duplicate_account_username, domain: duplicate_account_domain) - _remote_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: duplicate_account_domain).save(validate: false) - _local_account = Fabricate(:account, username: duplicate_account_username, domain: nil) - _local_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: nil).save(validate: false) + duplicate_record(:account, username: duplicate_account_username, domain: duplicate_account_domain) + duplicate_record(:account, username: duplicate_account_username, domain: nil) end def choose_local_account_to_keep @@ -127,8 +125,7 @@ def duplicate_users def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :users, :email - Fabricate(:user, email: duplicate_email) - Fabricate.build(:user, email: duplicate_email).save(validate: false) + duplicate_record(:user, email: duplicate_email) end end @@ -156,8 +153,7 @@ def duplicate_users def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :users, :confirmation_token - Fabricate(:user, confirmation_token: duplicate_confirmation_token) - Fabricate.build(:user, confirmation_token: duplicate_confirmation_token).save(validate: false) + duplicate_record(:user, confirmation_token: duplicate_confirmation_token) end end @@ -185,8 +181,7 @@ def duplicate_users def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :users, :reset_password_token - Fabricate(:user, reset_password_token: duplicate_reset_password_token) - Fabricate.build(:user, reset_password_token: duplicate_reset_password_token).save(validate: false) + duplicate_record(:user, reset_password_token: duplicate_reset_password_token) end end @@ -214,8 +209,7 @@ def duplicate_account_domain_blocks def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :account_domain_blocks, [:account_id, :domain] - Fabricate(:account_domain_block, account: account, domain: duplicate_domain) - Fabricate.build(:account_domain_block, account: account, domain: duplicate_domain).save(validate: false) + duplicate_record(:account_domain_block, account: account, domain: duplicate_domain) end end @@ -244,8 +238,7 @@ def duplicate_announcement_reactions def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :announcement_reactions, [:account_id, :announcement_id, :name] - Fabricate(:announcement_reaction, account: account, announcement: announcement, name: name) - Fabricate.build(:announcement_reaction, account: account, announcement: announcement, name: name).save(validate: false) + duplicate_record(:announcement_reaction, account: account, announcement: announcement, name: name) end end @@ -272,8 +265,7 @@ def duplicate_conversations def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :conversations, :uri - Fabricate(:conversation, uri: uri) - Fabricate.build(:conversation, uri: uri).save(validate: false) + duplicate_record(:conversation, uri: uri) end end @@ -301,8 +293,7 @@ def duplicate_custom_emojis def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :custom_emojis, [:shortcode, :domain] - Fabricate(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain) - Fabricate.build(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain).save(validate: false) + duplicate_record(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain) end end @@ -329,8 +320,7 @@ def duplicate_custom_emoji_categories def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :custom_emoji_categories, :name - Fabricate(:custom_emoji_category, name: duplicate_name) - Fabricate.build(:custom_emoji_category, name: duplicate_name).save(validate: false) + duplicate_record(:custom_emoji_category, name: duplicate_name) end end @@ -357,8 +347,7 @@ def duplicate_domain_allows def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :domain_allows, :domain - Fabricate(:domain_allow, domain: domain) - Fabricate.build(:domain_allow, domain: domain).save(validate: false) + duplicate_record(:domain_allow, domain: domain) end end @@ -385,8 +374,7 @@ def duplicate_domain_blocks def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :domain_blocks, :domain - Fabricate(:domain_block, domain: domain) - Fabricate.build(:domain_block, domain: domain).save(validate: false) + duplicate_record(:domain_block, domain: domain) end end @@ -413,8 +401,7 @@ def duplicate_email_domain_blocks def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :email_domain_blocks, :domain - Fabricate(:email_domain_block, domain: domain) - Fabricate.build(:email_domain_block, domain: domain).save(validate: false) + duplicate_record(:email_domain_block, domain: domain) end end @@ -441,8 +428,7 @@ def duplicate_media_attachments def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :media_attachments, :shortcode - Fabricate(:media_attachment, shortcode: shortcode) - Fabricate.build(:media_attachment, shortcode: shortcode).save(validate: false) + duplicate_record(:media_attachment, shortcode: shortcode) end end @@ -469,8 +455,7 @@ def duplicate_preview_cards def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :preview_cards, :url - Fabricate(:preview_card, url: url) - Fabricate.build(:preview_card, url: url).save(validate: false) + duplicate_record(:preview_card, url: url) end end @@ -530,8 +515,7 @@ def duplicate_tags def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :tags, name: 'index_tags_on_name_lower_btree' - Fabricate(:tag, name: name) - Fabricate.build(:tag, name: name).save(validate: false) + duplicate_record(:tag, name: name) end end @@ -558,8 +542,7 @@ def duplicate_webauthn_credentials def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :webauthn_credentials, :external_id - Fabricate(:webauthn_credential, external_id: external_id) - Fabricate.build(:webauthn_credential, external_id: external_id).save(validate: false) + duplicate_record(:webauthn_credential, external_id: external_id) end end @@ -586,11 +569,15 @@ def duplicate_webhooks def prepare_duplicate_data ActiveRecord::Base.connection.remove_index :webhooks, :url - Fabricate(:webhook, url: url) - Fabricate.build(:webhook, url: url).save(validate: false) + duplicate_record(:webhook, url: url) end end + def duplicate_record(fabricator, options = {}) + Fabricate(fabricator, options) + Fabricate.build(fabricator, options).save(validate: false) + end + def agree_to_backup_warning allow(cli.shell) .to receive(:yes?) From 8d4ca95163588a32412507c3142db5c7f3fb934e Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 04:10:21 -0500 Subject: [PATCH 20/73] Convert `admin/follow_recommendations` spec controller->system (#33533) --- .../follow_recommendations_controller_spec.rb | 21 ------------------- .../admin/follow_recommendations_spec.rb | 18 ++++++++++++++++ 2 files changed, 18 insertions(+), 21 deletions(-) delete mode 100644 spec/controllers/admin/follow_recommendations_controller_spec.rb create mode 100644 spec/system/admin/follow_recommendations_spec.rb diff --git a/spec/controllers/admin/follow_recommendations_controller_spec.rb b/spec/controllers/admin/follow_recommendations_controller_spec.rb deleted file mode 100644 index 82446cd4670b93..00000000000000 --- a/spec/controllers/admin/follow_recommendations_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::FollowRecommendationsController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #show' do - it 'returns http success' do - get :show - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/system/admin/follow_recommendations_spec.rb b/spec/system/admin/follow_recommendations_spec.rb new file mode 100644 index 00000000000000..141a0f8152a746 --- /dev/null +++ b/spec/system/admin/follow_recommendations_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Follow Recommendations' do + let(:user) { Fabricate(:admin_user) } + + before { sign_in(user) } + + describe 'Viewing follow recommendations details' do + it 'shows a list of accounts' do + visit admin_follow_recommendations_path + + expect(page) + .to have_content(I18n.t('admin.follow_recommendations.title')) + end + end +end From d155763014b0a78792f0b4b38b5a9783172056a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 09:10:58 +0000 Subject: [PATCH 21/73] Update dependency react-textarea-autosize to v8.5.7 (#33542) 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 e708282d879852..1231647d7c600e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14942,15 +14942,15 @@ __metadata: linkType: hard "react-textarea-autosize@npm:^8.4.1": - version: 8.5.6 - resolution: "react-textarea-autosize@npm:8.5.6" + version: 8.5.7 + resolution: "react-textarea-autosize@npm:8.5.7" dependencies: "@babel/runtime": "npm:^7.20.13" use-composed-ref: "npm:^1.3.0" use-latest: "npm:^1.2.1" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/652d290d316c55a253507ecf65ca27f2162801dace10c715f2241203e81d82e9de6d282095b758b26c6bc9e1af9ca552cab5c3a361b230e5fcf25bec31e1bd25 + checksum: 10c0/ff004797ea28faca442460c42b30042d4c34a140f324eeeddee74508688dbc0f98966d21282c945630655006ad28a87edbcb59e6da7f9e762f4f3042c72f9f24 languageName: node linkType: hard From 1d680f19411120c9cfdf43f5284c5676eb621185 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 09:11:17 +0000 Subject: [PATCH 22/73] Update dependency rubocop to v1.70.0 (#33543) 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 36373bd337fdc6..cb5e2bbbae569c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -709,7 +709,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) rspec-support (3.13.2) - rubocop (1.69.2) + rubocop (1.70.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) From a8b0152bc548b6ac45f55ad0f0670b774a0117a6 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 04:11:57 -0500 Subject: [PATCH 23/73] Convert `admin/terms_of_service/histories` spec controller->system (#33534) --- .../histories_controller_spec.rb | 21 ------------------- .../admin/terms_of_service/histories_spec.rb | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 spec/controllers/admin/terms_of_service/histories_controller_spec.rb create mode 100644 spec/system/admin/terms_of_service/histories_spec.rb diff --git a/spec/controllers/admin/terms_of_service/histories_controller_spec.rb b/spec/controllers/admin/terms_of_service/histories_controller_spec.rb deleted file mode 100644 index 8c2c3a3de3c4c8..00000000000000 --- a/spec/controllers/admin/terms_of_service/histories_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::TermsOfService::HistoriesController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #show' do - it 'returns http success' do - get :show - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/system/admin/terms_of_service/histories_spec.rb b/spec/system/admin/terms_of_service/histories_spec.rb new file mode 100644 index 00000000000000..aa59550d097ec1 --- /dev/null +++ b/spec/system/admin/terms_of_service/histories_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Terms of Service Histories' do + let(:current_user) { Fabricate(:admin_user) } + + before { sign_in(current_user) } + + describe 'Viewing TOS histories' do + before { Fabricate :terms_of_service, changelog: 'The changelog notes from v1 are here' } + + it 'shows previous terms versions' do + visit admin_terms_of_service_history_path + + expect(page) + .to have_content(I18n.t('admin.terms_of_service.history')) + .and have_content(/changelog notes from v1/) + end + end +end From e0f6292492c5fce0c9e48b2c003c733f3d2346d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 09:12:45 +0000 Subject: [PATCH 24/73] Update dependency uuid to v11.0.5 (#33538) 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 1231647d7c600e..0e40cc7221aab6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17843,11 +17843,11 @@ __metadata: linkType: hard "uuid@npm:^11.0.0": - version: 11.0.4 - resolution: "uuid@npm:11.0.4" + version: 11.0.5 + resolution: "uuid@npm:11.0.5" bin: uuid: dist/esm/bin/uuid - checksum: 10c0/3c13591c4dedaa3741f925e284df5974e3d6e0b1cb0f6f75f98c36f9c01d2a414350364fd067613ef600a21c6973dab0506530d4f499ff878f32a06f84569ead + checksum: 10c0/6f59f0c605e02c14515401084ca124b9cb462b4dcac866916a49862bcf831874508a308588c23a7718269226ad11a92da29b39d761ad2b86e736623e3a33b6e7 languageName: node linkType: hard From 4f6edc75963f4ea4e65a3512f0626b37e83b7dd7 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 04:33:07 -0500 Subject: [PATCH 25/73] Use `in_order_of` in `trends/*` classes (#33531) --- app/models/trends/links.rb | 4 ++-- app/models/trends/query.rb | 7 +++++++ app/models/trends/statuses.rb | 4 ++-- app/models/trends/tags.rb | 5 +++-- spec/models/trends/links_spec.rb | 30 +++++++++++++++++++++++++++++ spec/models/trends/statuses_spec.rb | 25 ++++++++++++++++++++++++ spec/models/trends/tags_spec.rb | 25 ++++++++++++++++++++++++ 7 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 spec/models/trends/links_spec.rb diff --git a/app/models/trends/links.rb b/app/models/trends/links.rb index 0f3ead43f88e0d..57ad486631c69d 100644 --- a/app/models/trends/links.rb +++ b/app/models/trends/links.rb @@ -16,7 +16,7 @@ class Trends::Links < Trends::Base class Query < Trends::Query def to_arel scope = PreviewCard.joins(:trend).reorder(score: :desc) - scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present? + scope = scope.merge(language_order_clause) if preferred_languages.present? scope = scope.merge(PreviewCardTrend.allowed) if @allowed scope = scope.offset(@offset) if @offset.present? scope = scope.limit(@limit) if @limit.present? @@ -26,7 +26,7 @@ def to_arel private def language_order_clause - Arel::Nodes::Case.new.when(PreviewCardTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) + language_order_for(PreviewCardTrend) end end diff --git a/app/models/trends/query.rb b/app/models/trends/query.rb index 590e81f4fdaeff..abed64042ec16a 100644 --- a/app/models/trends/query.rb +++ b/app/models/trends/query.rb @@ -94,6 +94,13 @@ def perform_queries to_arel.to_a end + def language_order_for(trend_class) + trend_class + .reorder(nil) + .in_order_of(:language, [preferred_languages], filter: false) + .order(score: :desc) + end + def preferred_languages if @account&.chosen_languages.present? @account.chosen_languages diff --git a/app/models/trends/statuses.rb b/app/models/trends/statuses.rb index 1d2f02809b87d6..9c47dd486b9d12 100644 --- a/app/models/trends/statuses.rb +++ b/app/models/trends/statuses.rb @@ -15,7 +15,7 @@ class Trends::Statuses < Trends::Base class Query < Trends::Query def to_arel scope = Status.joins(:trend).reorder(score: :desc) - scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present? + scope = scope.merge(language_order_clause) if preferred_languages.present? scope = scope.merge(StatusTrend.allowed) if @allowed scope = scope.not_excluded_by_account(@account).not_domain_blocked_by_account(@account) if @account.present? scope = scope.offset(@offset) if @offset.present? @@ -26,7 +26,7 @@ def to_arel private def language_order_clause - Arel::Nodes::Case.new.when(StatusTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) + language_order_for(StatusTrend) end end diff --git a/app/models/trends/tags.rb b/app/models/trends/tags.rb index 18f2a9a94929aa..84e8dde11a5fc1 100644 --- a/app/models/trends/tags.rb +++ b/app/models/trends/tags.rb @@ -15,7 +15,8 @@ class Trends::Tags < Trends::Base class Query < Trends::Query def to_arel - scope = Tag.joins(:trend).reorder(language_order_clause.desc, score: :desc) + scope = Tag.joins(:trend).reorder(score: :desc) + scope = scope.merge(language_order_clause) if preferred_languages.present? scope = scope.merge(TagTrend.allowed) if @allowed scope = scope.offset(@offset) if @offset.present? scope = scope.limit(@limit) if @limit.present? @@ -25,7 +26,7 @@ def to_arel private def language_order_clause - Arel::Nodes::Case.new.when(TagTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) + language_order_for(TagTrend) end end diff --git a/spec/models/trends/links_spec.rb b/spec/models/trends/links_spec.rb new file mode 100644 index 00000000000000..b0d41d4613abff --- /dev/null +++ b/spec/models/trends/links_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Trends::Links do + describe 'Trends::Links::Query' do + subject { described_class.new.query } + + describe '#records' do + context 'with scored cards' do + let!(:higher_score) { Fabricate :preview_card_trend, score: 10, language: 'en' } + let!(:lower_score) { Fabricate :preview_card_trend, score: 1, language: 'es' } + + it 'returns higher score first' do + expect(subject.records) + .to eq([higher_score.preview_card, lower_score.preview_card]) + end + + context 'with preferred locale' do + before { subject.in_locale!('es') } + + it 'returns in language order' do + expect(subject.records) + .to eq([lower_score.preview_card, higher_score.preview_card]) + end + end + end + end + end +end diff --git a/spec/models/trends/statuses_spec.rb b/spec/models/trends/statuses_spec.rb index 7c30b5b9976869..abb1535d04a14a 100644 --- a/spec/models/trends/statuses_spec.rb +++ b/spec/models/trends/statuses_spec.rb @@ -45,6 +45,31 @@ end end + describe 'Trends::Statuses::Query methods' do + subject { described_class.new.query } + + describe '#records' do + context 'with scored cards' do + let!(:higher_score) { Fabricate :status_trend, score: 10, language: 'en' } + let!(:lower_score) { Fabricate :status_trend, score: 1, language: 'es' } + + it 'returns higher score first' do + expect(subject.records) + .to eq([higher_score.status, lower_score.status]) + end + + context 'with preferred locale' do + before { subject.in_locale!('es') } + + it 'returns in language order' do + expect(subject.records) + .to eq([lower_score.status, higher_score.status]) + end + end + end + end + end + describe '#add' do let(:status) { Fabricate(:status) } diff --git a/spec/models/trends/tags_spec.rb b/spec/models/trends/tags_spec.rb index 936b441d92aaf0..8f36b4a50d343d 100644 --- a/spec/models/trends/tags_spec.rb +++ b/spec/models/trends/tags_spec.rb @@ -29,6 +29,31 @@ end end + describe 'Trends::Tags::Query' do + subject { described_class.new.query } + + describe '#records' do + context 'with scored cards' do + let!(:higher_score) { Fabricate :tag_trend, score: 10, language: 'en' } + let!(:lower_score) { Fabricate :tag_trend, score: 1, language: 'es' } + + it 'returns higher score first' do + expect(subject.records) + .to eq([higher_score.tag, lower_score.tag]) + end + + context 'with preferred locale' do + before { subject.in_locale!('es') } + + it 'returns in language order' do + expect(subject.records) + .to eq([lower_score.tag, higher_score.tag]) + end + end + end + end + end + describe '#refresh' do let!(:today) { at_time } let!(:yesterday) { today - 1.day } From 2cfc2a777a01b26a9442fbf9a912790097990a0b Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 06:59:19 -0500 Subject: [PATCH 26/73] Add `build_object` method for defaults in `AP::Activity::Create` spec (#33537) --- spec/lib/activitypub/activity/create_spec.rb | 375 +++++++------------ 1 file changed, 129 insertions(+), 246 deletions(-) diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index d70456e4587343..5273b9be15a43d 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -162,12 +162,9 @@ def activity_for_object(json) context 'when object publication date is below ISO8601 range' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - published: '-0977-11-03T08:31:22Z', - } + build_object( + published: '-0977-11-03T08:31:22Z' + ) end it 'creates status with a valid creation date', :aggregate_failures do @@ -184,12 +181,9 @@ def activity_for_object(json) context 'when object publication date is above ISO8601 range' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - published: '10000-11-03T08:31:22Z', - } + build_object( + published: '10000-11-03T08:31:22Z' + ) end it 'creates status with a valid creation date', :aggregate_failures do @@ -206,13 +200,10 @@ def activity_for_object(json) context 'when object has been edited' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( published: '2022-01-22T15:00:00Z', - updated: '2022-01-22T16:00:00Z', - } + updated: '2022-01-22T16:00:00Z' + ) end it 'creates status with appropriate creation and edition dates', :aggregate_failures do @@ -232,13 +223,10 @@ def activity_for_object(json) context 'when object has update date equal to creation date' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( published: '2022-01-22T15:00:00Z', - updated: '2022-01-22T15:00:00Z', - } + updated: '2022-01-22T15:00:00Z' + ) end it 'creates status and does not mark it as edited' do @@ -254,11 +242,9 @@ def activity_for_object(json) context 'with an unknown object type' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Banana', - content: 'Lorem ipsum', - } + build_object( + type: 'Banana' + ) end it 'does not create a status' do @@ -267,13 +253,7 @@ def activity_for_object(json) end context 'with a standalone' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - } - end + let(:object_json) { build_object } it 'creates status' do expect { subject.perform }.to change(sender.statuses, :count).by(1) @@ -296,12 +276,9 @@ def activity_for_object(json) context 'when public with explicit public address' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'https://www.w3.org/ns/activitystreams#Public', - } + build_object( + to: 'https://www.w3.org/ns/activitystreams#Public' + ) end it 'creates status' do @@ -316,12 +293,9 @@ def activity_for_object(json) context 'when public with as:Public' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'as:Public', - } + build_object( + to: 'as:Public' + ) end it 'creates status' do @@ -336,12 +310,9 @@ def activity_for_object(json) context 'when public with Public' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'Public', - } + build_object( + to: 'Public' + ) end it 'creates status' do @@ -356,12 +327,9 @@ def activity_for_object(json) context 'when unlisted with explicit public address' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - cc: 'https://www.w3.org/ns/activitystreams#Public', - } + build_object( + cc: 'https://www.w3.org/ns/activitystreams#Public' + ) end it 'creates status' do @@ -376,12 +344,9 @@ def activity_for_object(json) context 'when unlisted with as:Public' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - cc: 'as:Public', - } + build_object( + cc: 'as:Public' + ) end it 'creates status' do @@ -396,12 +361,9 @@ def activity_for_object(json) context 'when unlisted with Public' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - cc: 'Public', - } + build_object( + cc: 'Public' + ) end it 'creates status' do @@ -416,12 +378,9 @@ def activity_for_object(json) context 'when private' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'http://example.com/followers', - } + build_object( + to: 'http://example.com/followers' + ) end it 'creates status' do @@ -436,16 +395,13 @@ def activity_for_object(json) context 'when private with inlined Collection in audience' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( to: { type: 'OrderedCollection', id: 'http://example.com/followers', first: 'http://example.com/followers?page=true', - }, - } + } + ) end it 'creates status' do @@ -462,12 +418,9 @@ def activity_for_object(json) let(:recipient) { Fabricate(:account) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: ActivityPub::TagManager.instance.uri_for(recipient), - } + build_object( + to: ActivityPub::TagManager.instance.uri_for(recipient) + ) end it 'creates status with a silent mention' do @@ -485,16 +438,13 @@ def activity_for_object(json) let(:recipient) { Fabricate(:account) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( to: ActivityPub::TagManager.instance.uri_for(recipient), tag: { type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(recipient), - }, - } + } + ) end it 'creates status' do @@ -511,12 +461,9 @@ def activity_for_object(json) let(:original_status) { Fabricate(:status) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status), - } + build_object( + inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status) + ) end it 'creates status' do @@ -536,17 +483,14 @@ def activity_for_object(json) let(:recipient) { Fabricate(:account) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(recipient), }, - ], - } + ] + ) end it 'creates status' do @@ -561,16 +505,13 @@ def activity_for_object(json) context 'with mentions missing href' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Mention', }, - ], - } + ] + ) end it 'creates status' do @@ -583,10 +524,7 @@ def activity_for_object(json) context 'with media attachments' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', @@ -598,8 +536,8 @@ def activity_for_object(json) mediaType: 'image/png', url: 'http://example.com/emoji.png', }, - ], - } + ] + ) end it 'creates status with correctly-ordered media attachments' do @@ -615,10 +553,7 @@ def activity_for_object(json) context 'with media attachments with long description' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', @@ -626,8 +561,8 @@ def activity_for_object(json) url: 'http://example.com/attachment.png', name: '*' * MediaAttachment::MAX_DESCRIPTION_LENGTH, }, - ], - } + ] + ) end it 'creates status' do @@ -642,10 +577,7 @@ def activity_for_object(json) context 'with media attachments with long description as summary' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', @@ -653,8 +585,8 @@ def activity_for_object(json) url: 'http://example.com/attachment.png', summary: '*' * MediaAttachment::MAX_DESCRIPTION_LENGTH, }, - ], - } + ] + ) end it 'creates status' do @@ -669,10 +601,7 @@ def activity_for_object(json) context 'with media attachments with focal points' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', @@ -680,8 +609,8 @@ def activity_for_object(json) url: 'http://example.com/attachment.png', focalPoint: [0.5, -0.7], }, - ], - } + ] + ) end it 'creates status' do @@ -696,17 +625,14 @@ def activity_for_object(json) context 'with media attachments missing url' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( attachment: [ { type: 'Document', mediaType: 'image/png', }, - ], - } + ] + ) end it 'creates status' do @@ -719,18 +645,15 @@ def activity_for_object(json) context 'with hashtags' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Hashtag', href: 'http://example.com/blah', name: '#test', }, - ], - } + ] + ) end it 'creates status' do @@ -745,10 +668,7 @@ def activity_for_object(json) context 'with featured hashtags' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( to: 'https://www.w3.org/ns/activitystreams#Public', tag: [ { @@ -756,8 +676,8 @@ def activity_for_object(json) href: 'http://example.com/blah', name: '#test', }, - ], - } + ] + ) end before do @@ -779,17 +699,14 @@ def activity_for_object(json) context 'with hashtags missing name' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Hashtag', href: 'http://example.com/blah', }, - ], - } + ] + ) end it 'creates status' do @@ -802,18 +719,15 @@ def activity_for_object(json) context 'with hashtags invalid name' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( tag: [ { type: 'Hashtag', href: 'http://example.com/blah', name: 'foo, #eh !', }, - ], - } + ] + ) end it 'creates status' do @@ -826,9 +740,7 @@ def activity_for_object(json) context 'with emojis' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( content: 'Lorem ipsum :tinking:', tag: [ { @@ -838,8 +750,8 @@ def activity_for_object(json) }, name: 'tinking', }, - ], - } + ] + ) end it 'creates status' do @@ -854,9 +766,7 @@ def activity_for_object(json) context 'with emojis served with invalid content-type' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( content: 'Lorem ipsum :tinkong:', tag: [ { @@ -866,8 +776,8 @@ def activity_for_object(json) }, name: 'tinkong', }, - ], - } + ] + ) end it 'creates status' do @@ -882,9 +792,7 @@ def activity_for_object(json) context 'with emojis missing name' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( content: 'Lorem ipsum :tinking:', tag: [ { @@ -893,8 +801,8 @@ def activity_for_object(json) url: 'http://example.com/emoji.png', }, }, - ], - } + ] + ) end it 'creates status' do @@ -907,17 +815,15 @@ def activity_for_object(json) context 'with emojis missing icon' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( content: 'Lorem ipsum :tinking:', tag: [ { type: 'Emoji', name: 'tinking', }, - ], - } + ] + ) end it 'creates status' do @@ -930,8 +836,7 @@ def activity_for_object(json) context 'with poll' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + build_object( type: 'Question', content: 'Which color was the submarine?', oneOf: [ @@ -949,8 +854,8 @@ def activity_for_object(json) totalItems: 3, }, }, - ], - } + ] + ) end it 'creates status with a poll' do @@ -973,12 +878,10 @@ def activity_for_object(json) let!(:local_status) { Fabricate(:status, poll: poll) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( name: 'Yellow', - inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), - } + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + ).except(:content) end it 'adds a vote to the poll with correct uri' do @@ -1000,12 +903,10 @@ def activity_for_object(json) let!(:local_status) { Fabricate(:status, poll: poll) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', + build_object( name: 'Yellow', - inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), - } + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + ).except(:content) end it 'does not add a vote to the poll' do @@ -1017,10 +918,7 @@ def activity_for_object(json) context 'with counts' do let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', + build_object( likes: { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/likes'].join, type: 'Collection', @@ -1030,8 +928,8 @@ def activity_for_object(json) id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/shares'].join, type: 'Collection', totalItems: 100, - }, - } + } + ) end it 'uses the counts from the created object' do @@ -1060,12 +958,9 @@ def activity_for_object(json) end let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'https://www.w3.org/ns/activitystreams#Public', - } + build_object( + to: 'https://www.w3.org/ns/activitystreams#Public' + ) end before do @@ -1095,13 +990,7 @@ def activity_for_object(json) subject.perform end - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - } - end + let(:object_json) { build_object } it 'creates status' do status = sender.statuses.first @@ -1116,12 +1005,9 @@ def activity_for_object(json) let!(:local_status) { Fabricate(:status) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), - } + build_object( + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + ) end before do @@ -1140,13 +1026,11 @@ def activity_for_object(json) subject { described_class.new(json, sender, delivery: true) } let!(:local_account) { Fabricate(:account) } + let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: ActivityPub::TagManager.instance.uri_for(local_account), - } + build_object( + to: ActivityPub::TagManager.instance.uri_for(local_account) + ) end before do @@ -1166,12 +1050,9 @@ def activity_for_object(json) let!(:local_account) { Fabricate(:account) } let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - cc: ActivityPub::TagManager.instance.uri_for(local_account), - } + build_object( + cc: ActivityPub::TagManager.instance.uri_for(local_account) + ) end before do @@ -1193,17 +1074,19 @@ def activity_for_object(json) subject.perform end - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - } - end + let(:object_json) { build_object } it 'does not create anything' do expect(sender.statuses.count).to eq 0 end end + + def build_object(options = {}) + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + }.merge(options) + end end end From 34cd7d6585992c03298c175ab5d22ad059b58cdb Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 10:52:43 -0500 Subject: [PATCH 27/73] Use `config_for` for `Mastodon::Version` metadata/prerelease values (#33548) --- config/mastodon.yml | 3 +++ lib/mastodon/version.rb | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/config/mastodon.yml b/config/mastodon.yml index 2c09c59e0a8bc6..e20ba0ab053c1b 100644 --- a/config/mastodon.yml +++ b/config/mastodon.yml @@ -2,3 +2,6 @@ shared: self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil) %> software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check') %> + version: + metadata: <%= ENV.fetch('MASTODON_VERSION_METADATA', nil) %> + prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil) %> diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index f132c3a54835d3..ddde4a993d640a 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -21,11 +21,11 @@ def default_prerelease end def prerelease - ENV['MASTODON_VERSION_PRERELEASE'].presence || default_prerelease + version_configuration[:prerelease].presence || default_prerelease end def build_metadata - ENV.fetch('MASTODON_VERSION_METADATA', nil) + version_configuration[:metadata] end def to_a @@ -77,5 +77,9 @@ def source_commit def user_agent @user_agent ||= "Mastodon/#{Version} (#{HTTP::Request::USER_AGENT}; +http#{Rails.configuration.x.use_https ? 's' : ''}://#{Rails.configuration.x.web_domain}/)" end + + def version_configuration + Rails.configuration.x.mastodon.version + end end end From 22c1b6f3eec14062c6e0950fdb2d436c34430543 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 10 Jan 2025 15:34:18 -0500 Subject: [PATCH 28/73] Fix `Invite#code` changing value on every save (#33550) --- app/models/invite.rb | 2 +- spec/models/invite_spec.rb | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/app/models/invite.rb b/app/models/invite.rb index d1981f16ad7642..9437ebee60b773 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -31,7 +31,7 @@ class Invite < ApplicationRecord validates :comment, length: { maximum: COMMENT_SIZE_LIMIT } - before_validation :set_code + before_validation :set_code, on: :create def valid_for_use? (max_uses.nil? || uses < max_uses) && !expired? && user&.functional? diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb index e85885a8d8b7f4..6363f77a6468c4 100644 --- a/spec/models/invite_spec.rb +++ b/spec/models/invite_spec.rb @@ -5,6 +5,29 @@ RSpec.describe Invite do include_examples 'Expireable' + describe 'Associations' do + it { is_expected.to belong_to(:user).inverse_of(:invites) } + it { is_expected.to have_many(:users).inverse_of(:invite) } + end + + describe 'Validations' do + it { is_expected.to validate_length_of(:comment).is_at_most(described_class::COMMENT_SIZE_LIMIT) } + end + + describe 'Scopes' do + describe '.available' do + let!(:no_expires) { Fabricate :invite, expires_at: nil } + let!(:past_expires) { Fabricate :invite, expires_at: 2.days.ago } + let!(:future_expires) { Fabricate :invite, expires_at: 2.days.from_now } + + it 'returns future and non-epiring records' do + expect(described_class.available) + .to include(no_expires, future_expires) + .and not_include(past_expires) + end + end + end + describe '#valid_for_use?' do it 'returns true when there are no limitations' do invite = Fabricate(:invite, max_uses: nil, expires_at: nil) @@ -37,4 +60,26 @@ expect(invite.valid_for_use?).to be false end end + + describe 'Callbacks' do + describe 'Setting the invite code' do + context 'when creating a new record' do + subject { Fabricate.build :invite } + + it 'sets a code value' do + expect { subject.save } + .to change(subject, :code).from(be_blank).to(be_present) + end + end + + context 'when updating a record' do + subject { Fabricate :invite } + + it 'does not change the code value' do + expect { subject.update(max_uses: 123_456) } + .to not_change(subject, :code) + end + end + end + end end From f3f6b65db4ca4d1f14b610dd7f6036f8516a6fa8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:47:43 +0100 Subject: [PATCH 29/73] Update dependency @babel/plugin-transform-nullish-coalescing-operator to v7.26.5 (#33553) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0e40cc7221aab6..db96b75974c42f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -238,10 +238,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.25.0, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.25.9 - resolution: "@babel/helper-plugin-utils@npm:7.25.9" - checksum: 10c0/483066a1ba36ff16c0116cd24f93de05de746a603a777cd695ac7a1b034928a65a4ecb35f255761ca56626435d7abdb73219eba196f9aa83b6c3c3169325599d +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.25.0, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.26.5, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.26.5 + resolution: "@babel/helper-plugin-utils@npm:7.26.5" + checksum: 10c0/cdaba71d4b891aa6a8dfbe5bac2f94effb13e5fa4c2c487667fdbaa04eae059b78b28d85a885071f45f7205aeb56d16759e1bed9c118b94b16e4720ef1ab0f65 languageName: node linkType: hard @@ -935,13 +935,13 @@ __metadata: linkType: hard "@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.3, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.25.9" + version: 7.26.5 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.26.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.26.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/eb623db5be078a1c974afe7c7797b0309ba2ea9e9237c0b6831ade0f56d8248bb4ab3432ab34495ff8c877ec2fe412ff779d1e9b3c2b8139da18e1753d950bc3 + checksum: 10c0/2e4b84745f9e8c40caf3e611641de4d6c7da6f96c2925b7fe568e3b031ed1864e325b9dffc9cda4e442fc40be43ffabb088782e980d411e0562bd5222df547ec languageName: node linkType: hard From 44d9dc4bb0cf64fb1561adc6c4fab60316a6a43b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:51:46 +0100 Subject: [PATCH 30/73] Update dependency pino-http to v10.4.0 (#33560) 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 db96b75974c42f..0b3b8b3d95a905 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13306,14 +13306,14 @@ __metadata: linkType: hard "pino-http@npm:^10.0.0": - version: 10.3.0 - resolution: "pino-http@npm:10.3.0" + version: 10.4.0 + resolution: "pino-http@npm:10.4.0" dependencies: get-caller-file: "npm:^2.0.5" pino: "npm:^9.0.0" pino-std-serializers: "npm:^7.0.0" process-warning: "npm:^4.0.0" - checksum: 10c0/da95d93e1176c02201f9b9bb0af53ad737105c5772acbb077dcad0f52ebce2438e0e9fc8216cd96396d1305d0ecf1f1d23142c7a50110a701ea093b2ee999ea7 + checksum: 10c0/64144e2c94e939070f56ad82dfb012b6a98d21582e0660cf821e7cee64d4e06f7724aa40bc5bf9cd1254d58ab7cbd972dec287b7989eba647d384f6edd8d95fd languageName: node linkType: hard From 99637f2deb517bc21506af18491e49a8af5e5fcb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 08:53:56 +0000 Subject: [PATCH 31/73] Update dependency ox to v2.14.20 (#33567) 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 cb5e2bbbae569c..4603530de05543 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -555,7 +555,7 @@ GEM opentelemetry-api (~> 1.0) orm_adapter (0.5.0) ostruct (0.6.1) - ox (2.14.19) + ox (2.14.20) bigdecimal (>= 3.0) parallel (1.26.3) parser (3.3.6.0) From 53885b0fdb8b25bb0d531df2cd90bdb223e21dc6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:13:47 +0100 Subject: [PATCH 32/73] New Crowdin Translations (automated) (#33559) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/cs.json | 72 +++++++- app/javascript/mastodon/locales/ga.json | 12 ++ app/javascript/mastodon/locales/ia.json | 2 +- app/javascript/mastodon/locales/it.json | 5 +- app/javascript/mastodon/locales/ja.json | 8 + app/javascript/mastodon/locales/ne.json | 65 ++++++- app/javascript/mastodon/locales/pl.json | 2 + app/javascript/mastodon/locales/pt-BR.json | 8 +- app/javascript/mastodon/locales/pt-PT.json | 16 +- app/javascript/mastodon/locales/sk.json | 2 + app/javascript/mastodon/locales/vi.json | 4 +- config/locales/activerecord.cs.yml | 13 ++ config/locales/activerecord.ga.yml | 2 + config/locales/activerecord.it.yml | 2 + config/locales/activerecord.ne.yml | 34 ++++ config/locales/activerecord.pt-PT.yml | 2 +- config/locales/cs.yml | 205 ++++++++++++++++++++- config/locales/eo.yml | 6 + config/locales/pl.yml | 3 + config/locales/pt-PT.yml | 8 +- config/locales/simple_form.cs.yml | 14 ++ config/locales/simple_form.eo.yml | 7 + config/locales/simple_form.pt-PT.yml | 2 +- 23 files changed, 459 insertions(+), 35 deletions(-) diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index ba58b998f1f572..81540c823ccbed 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -104,10 +104,11 @@ "annual_report.summary.most_used_hashtag.none": "Žádné", "annual_report.summary.new_posts.new_posts": "nové příspěvky", "annual_report.summary.percentile.text": "To vás umisťuje do vrcholu{domain} uživatelů.", + "annual_report.summary.percentile.we_wont_tell_bernie": "To, že jste zdejší smetánka zůstane mezi námi ;).", "annual_report.summary.thanks": "Děkujeme, že jste součástí Mastodonu!", "attachments_list.unprocessed": "(nezpracováno)", "audio.hide": "Skrýt zvuk", - "block_modal.remote_users_caveat": "Požádáme server {domain}, aby respektoval vaše rozhodnutí. Úplné dodržování nastavení však není zaručeno, protože některé servery mohou řešit blokování různě. Veřejné příspěvky mohou stále být viditelné pro nepřihlášené uživatele.", + "block_modal.remote_users_caveat": "Požádáme server {domain}, aby respektoval vaše rozhodnutí. Úplné dodržování nastavení však není zaručeno, protože některé servery mohou řešit blokování různě. Veřejné příspěvky mohou být stále viditelné pro nepřihlášené uživatele.", "block_modal.show_less": "Zobrazit méně", "block_modal.show_more": "Zobrazit více", "block_modal.they_cant_mention": "Nemůže vás zmiňovat ani sledovat.", @@ -180,6 +181,7 @@ "compose_form.poll.duration": "Doba trvání ankety", "compose_form.poll.multiple": "Výběr z více možností", "compose_form.poll.option_placeholder": "Volba {number}", + "compose_form.poll.single": "Jediná volba", "compose_form.poll.switch_to_multiple": "Povolit u ankety výběr více voleb", "compose_form.poll.switch_to_single": "Povolit u ankety výběr pouze jedné volby", "compose_form.poll.type": "Styl", @@ -204,6 +206,7 @@ "confirmations.edit.message": "Editovat teď znamená přepsání zprávy, kterou právě tvoříte. Opravdu chcete pokračovat?", "confirmations.edit.title": "Přepsat příspěvek?", "confirmations.follow_to_list.confirm": "Sledovat a přidat do seznamu", + "confirmations.follow_to_list.message": "Musíte {name} sledovat, abyste je přidali do seznamu.", "confirmations.follow_to_list.title": "Sledovat uživatele?", "confirmations.logout.confirm": "Odhlásit se", "confirmations.logout.message": "Opravdu se chcete odhlásit?", @@ -236,19 +239,25 @@ "disabled_account_banner.text": "Váš účet {disabledAccount} je momentálně deaktivován.", "dismissable_banner.community_timeline": "Toto jsou nejnovější veřejné příspěvky od lidí, jejichž účty hostuje {domain}.", "dismissable_banner.dismiss": "Zavřít", + "dismissable_banner.explore_links": "Tyto zprávy jsou dnes nejvíce sdíleny ve fediversu. Novější novinky publikované více různými lidmi jsou v pořadí vyšší.", + "dismissable_banner.explore_statuses": "Tyto příspěvky napříč fediversem dnes získávají na popularitě. Novější příspěvky s více boosty a oblíbenými jsou výše v pořadí.", + "dismissable_banner.explore_tags": "Tyto hashtagy dnes na fediversu získávají na popularitě. Hashtagy, které používá více různých lidí, jsou řazeny výše.", + "dismissable_banner.public_timeline": "Toto jsou nejnovější veřejné příspěvky od lidí na fediversu, které lidé na {domain} sledují.", "domain_block_modal.block": "Blokovat server", "domain_block_modal.block_account_instead": "Raději blokovat @{name}", "domain_block_modal.they_can_interact_with_old_posts": "Lidé z tohoto serveru mohou interagovat s vašimi starými příspěvky.", "domain_block_modal.they_cant_follow": "Nikdo z tohoto serveru vás nemůže sledovat.", "domain_block_modal.they_wont_know": "Nebude vědět, že je zablokován*a.", "domain_block_modal.title": "Blokovat doménu?", + "domain_block_modal.you_will_lose_num_followers": "Ztratíte {followersCount, plural, one {{followersCountDisplay} sledujícího} few {{followersCountDisplay} sledující} many {{followersCountDisplay} sledujících} other {{followersCountDisplay} sledujících}} a {followingCount, plural, one {{followingCountDisplay} sledovaného} few {{followingCountDisplay} sledované} many {{followingCountDisplay} sledovaných} other {{followingCountDisplay} sledovaných}}.", + "domain_block_modal.you_will_lose_relationships": "Ztratíte všechny sledující a lidi, které sledujete z tohoto serveru.", "domain_block_modal.you_wont_see_posts": "Neuvidíte příspěvky ani upozornění od uživatelů z tohoto serveru.", "domain_pill.activitypub_lets_connect": "Umožňuje vám spojit se a komunikovat s lidmi nejen na Mastodonu, ale i s dalšími sociálními aplikacemi.", "domain_pill.activitypub_like_language": "ActivityPub je jako jazyk, kterým Mastodon mluví s jinými sociálními sítěmi.", "domain_pill.server": "Server", "domain_pill.their_handle": "Handle:", - "domain_pill.their_server": "Digitální domov, kde žijí všechny příspěvky.", - "domain_pill.their_username": "Jedinečný identikátor na serveru. Je možné najít uživatele se stejným uživatelským jménem na různých serverech.", + "domain_pill.their_server": "Jejich digitální domov, kde žijí jejich všechny příspěvky.", + "domain_pill.their_username": "Jejich jedinečný identikátor na jejich serveru. Je možné najít uživatele se stejným uživatelským jménem na jiných serverech.", "domain_pill.username": "Uživatelské jméno", "domain_pill.whats_in_a_handle": "Co obsahuje handle?", "domain_pill.who_they_are": "Protože handle říkají kdo je kdo a také kde, je možné interagovat s lidmi napříč sociálními weby .", @@ -322,6 +331,7 @@ "filter_modal.select_filter.title": "Filtrovat tento příspěvek", "filter_modal.title.status": "Filtrovat příspěvek", "filter_warning.matches_filter": "Odpovídá filtru “{title}”", + "filtered_notifications_banner.pending_requests": "Od {count, plural, =0 {nikoho, koho možná znáte} one {člověka, kterého možná znáte} few {#, které možná znáte} many {#, které možná znáte} other {#, které možná znáte}}", "filtered_notifications_banner.title": "Filtrovaná oznámení", "firehose.all": "Vše", "firehose.local": "Tento server", @@ -329,14 +339,14 @@ "follow_request.authorize": "Autorizovat", "follow_request.reject": "Zamítnout", "follow_requests.unlocked_explanation": "Přestože váš účet není uzamčen, personál {domain} usoudil, že byste mohli chtít tyto požadavky na sledování zkontrolovat ručně.", - "follow_suggestions.curated_suggestion": "Výběr personálů", + "follow_suggestions.curated_suggestion": "Výběr personálu", "follow_suggestions.dismiss": "Znovu nezobrazovat", "follow_suggestions.featured_longer": "Ručně vybráno týmem {domain}", "follow_suggestions.friends_of_friends_longer": "Populární mezi lidmi, které sledujete", "follow_suggestions.hints.featured": "Tento profil byl ručně vybrán týmem {domain}.", "follow_suggestions.hints.friends_of_friends": "Tento profil je populární mezi lidmi, které sledujete.", - "follow_suggestions.hints.most_followed": "Tento profil je jedním z nejvíce sledovaných na {domain}.", - "follow_suggestions.hints.most_interactions": "Tento profil nedávno dostalo velkou pozornost na {domain}.", + "follow_suggestions.hints.most_followed": "Tento profil je jedním z nejsledovanějších na {domain}.", + "follow_suggestions.hints.most_interactions": "Tomuto profilu se nedávno dostalo velké pozornosti na {domain}.", "follow_suggestions.hints.similar_to_recently_followed": "Tento profil je podobný profilům, které jste nedávno sledovali.", "follow_suggestions.personalized_suggestion": "Přizpůsobený návrh", "follow_suggestions.popular_suggestion": "Populární návrh", @@ -355,6 +365,7 @@ "footer.terms_of_service": "Obchodní podmínky", "generic.saved": "Uloženo", "getting_started.heading": "Začínáme", + "hashtag.admin_moderation": "Otevřít moderátorské rozhraní pro #{name}", "hashtag.column_header.tag_mode.all": "a {additional}", "hashtag.column_header.tag_mode.any": "nebo {additional}", "hashtag.column_header.tag_mode.none": "bez {additional}", @@ -370,9 +381,13 @@ "hashtag.follow": "Sledovat hashtag", "hashtag.unfollow": "Přestat sledovat hashtag", "hashtags.and_other": "…a {count, plural, one {# další} few {# další} other {# dalších}}", + "hints.profiles.followers_may_be_missing": "Sledující mohou pro tento profil chybět.", + "hints.profiles.follows_may_be_missing": "Sledování mohou pro tento profil chybět.", + "hints.profiles.posts_may_be_missing": "Některé příspěvky z tohoto profilu mohou chybět.", "hints.profiles.see_more_followers": "Zobrazit více sledujících na {domain}", "hints.profiles.see_more_follows": "Zobrazit další sledování na {domain}", "hints.profiles.see_more_posts": "Zobrazit další příspěvky na {domain}", + "hints.threads.replies_may_be_missing": "Odpovědi z jiných serverů mohou chybět.", "hints.threads.see_more": "Zobrazit další odpovědi na {domain}", "home.column_settings.show_reblogs": "Zobrazit boosty", "home.column_settings.show_replies": "Zobrazit odpovědi", @@ -381,7 +396,22 @@ "home.pending_critical_update.link": "Zobrazit aktualizace", "home.pending_critical_update.title": "K dispozici je kritická bezpečnostní aktualizace!", "home.show_announcements": "Zobrazit oznámení", + "ignore_notifications_modal.disclaimer": "Mastodon nemůže informovat uživatele, že jste ignorovali jejich oznámení. Ignorování oznámení nezabrání odesílání zpráv samotných.", + "ignore_notifications_modal.filter_instead": "Místo toho filtrovat", + "ignore_notifications_modal.filter_to_act_users": "Stále budete moci přijmout, odmítnout nebo nahlásit uživatele", + "ignore_notifications_modal.filter_to_avoid_confusion": "Filtrování pomáhá vyhnout se možným nejasnostem", + "ignore_notifications_modal.filter_to_review_separately": "Filtrovaná oznámení můžete zkontrolovat samostatně", "ignore_notifications_modal.ignore": "Ignorovat oznámení", + "ignore_notifications_modal.limited_accounts_title": "Ignorovat oznámení z moderovaných účtů?", + "ignore_notifications_modal.new_accounts_title": "Ignorovat oznámení z nových účtů?", + "ignore_notifications_modal.not_followers_title": "Ignorovat oznámení od lidí, kteří vás nesledují?", + "ignore_notifications_modal.not_following_title": "Ignorovat oznámení od lidí, které nesledujete?", + "ignore_notifications_modal.private_mentions_title": "Ignorovat oznámení z nevyžádaných soukromých zmínek?", + "interaction_modal.action.favourite": "Chcete-li pokračovat, musíte oblíbit z vašeho účtu.", + "interaction_modal.action.follow": "Chcete-li pokračovat, musíte sledovat z vašeho účtu.", + "interaction_modal.action.reblog": "Chcete-li pokračovat, musíte dát boost z vašeho účtu.", + "interaction_modal.action.reply": "Chcete-li pokračovat, musíte odpovědět z vašeho účtu.", + "interaction_modal.action.vote": "Chcete-li pokračovat, musíte hlasovat z vašeho účtu.", "interaction_modal.go": "Přejít", "interaction_modal.no_account_yet": "Ještě nemáte účet?", "interaction_modal.on_another_server": "Na jiném serveru", @@ -433,20 +463,27 @@ "lightbox.close": "Zavřít", "lightbox.next": "Další", "lightbox.previous": "Předchozí", + "lightbox.zoom_in": "Přiblížit na skutečnou velikost", + "lightbox.zoom_out": "Přizpůsobit velikost", "limited_account_hint.action": "Přesto profil zobrazit", "limited_account_hint.title": "Tento profil byl skryt moderátory {domain}.", "link_preview.author": "Podle {name}", "link_preview.more_from_author": "Více od {name}", "link_preview.shares": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}", "lists.add_member": "Přidat", + "lists.add_to_list": "Přidat do seznamu", "lists.add_to_lists": "Přidat {name} do seznamů", "lists.create": "Vytvořit", + "lists.create_a_list_to_organize": "Vytvořte nový seznam pro organizaci vašeho domovského kanálu", "lists.create_list": "Vytvořit seznam", "lists.delete": "Smazat seznam", "lists.done": "Hotovo", "lists.edit": "Upravit seznam", + "lists.exclusive": "Skrýt členy na domovském kanálu", + "lists.exclusive_hint": "Pokud je někdo na tomto seznamu, skryjte jej ve vašem domovském kanálu, abyste se vyhnuli dvojímu vidění jejich příspěvků.", "lists.find_users_to_add": "Najít uživatele, které chcete přidat", "lists.list_members": "Členové seznamu", + "lists.list_members_count": "{count, plural, one {# člen} few {# členové} many {# členů} other {# členů}}", "lists.list_name": "Název seznamu", "lists.new_list_name": "Název nového seznamu", "lists.no_lists_yet": "Zatím žádné seznamy.", @@ -458,6 +495,7 @@ "lists.replies_policy.none": "Nikomu", "lists.save": "Uložit", "lists.search": "Hledat", + "lists.show_replies_to": "Zahrnout odpovědi od členů seznamu pro", "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", @@ -500,14 +538,25 @@ "navigation_bar.security": "Zabezpečení", "not_signed_in_indicator.not_signed_in": "Pro přístup k tomuto zdroji se musíte přihlásit.", "notification.admin.report": "Uživatel {name} nahlásil {target}", + "notification.admin.report_account": "{name} nahlásil {count, plural, one {jeden příspěvek} few {# příspěvky} many {# příspěvků} other {# příspěvků}} od {target} za {category}", + "notification.admin.report_account_other": "{name} nahlásil {count, plural, one {jeden příspěvek} few {# příspěvky} many {# příspěvků} other {# příspěvků}} od {target}", "notification.admin.report_statuses": "{name} nahlásil {target} za {category}", "notification.admin.report_statuses_other": "{name} nahlásil {target}", "notification.admin.sign_up": "Uživatel {name} se zaregistroval", + "notification.admin.sign_up.name_and_others": "{name} a {count, plural, one {# další} few {# další} many {# dalších} other {# dalších}} se zaregistrovali", + "notification.annual_report.message": "Váš #Wrapstodon {year} na Vás čeká! Podívejte se, jak vypadal tento Váš rok na Mastodonu!", + "notification.annual_report.view": "Zobrazit #Wrapstodon", "notification.favourite": "Uživatel {name} si oblíbil váš příspěvek", + "notification.favourite.name_and_others_with_link": "{name} a {count, plural, one {# další si oblíbil} few {# další si oblíbili} other {# dalších si oblíbilo}} Váš příspěvek", + "notification.favourite_pm": "{name} si oblíbil vaši soukromou zmínku", + "notification.favourite_pm.name_and_others_with_link": "{name} a {count, plural, one {# další si oblíbil} few {# další si oblíbili} other {# dalších si oblíbilo}} Vaši soukromou zmínku", "notification.follow": "Uživatel {name} vás začal sledovat", + "notification.follow.name_and_others": "{name} a {count, plural, one {# další Vás začal sledovat} few {# další Vás začali sledovat} other {# dalších Vás začalo sledovat}}", "notification.follow_request": "Uživatel {name} požádal o povolení vás sledovat", + "notification.follow_request.name_and_others": "{name} a {count, plural, one {# další Vám poslal žádost o sledování} few {# další Vám poslali žádost o sledování} other {# dalších Vám poslalo žádost o sledování}}", "notification.label.mention": "Zmínka", "notification.label.private_mention": "Soukromá zmínka", + "notification.label.private_reply": "Privátní odpověď", "notification.label.reply": "Odpověď", "notification.mention": "Zmínka", "notification.mentioned_you": "{name} vás zmínil", @@ -523,6 +572,7 @@ "notification.own_poll": "Vaše anketa skončila", "notification.poll": "Anketa, ve které jste hlasovali, skončila", "notification.reblog": "Uživatel {name} boostnul váš příspěvek", + "notification.reblog.name_and_others_with_link": "{name} a {count, plural, one {# další boostnul} few {# další boostnuli} other {# dalších boostnulo}} Váš příspěvek", "notification.relationships_severance_event": "Kontakt ztracen s {name}", "notification.relationships_severance_event.account_suspension": "Administrátor z {from} pozastavil {target}, což znamená, že již od nich nemůžete přijímat aktualizace nebo s nimi interagovat.", "notification.relationships_severance_event.domain_block": "Administrátor z {from} pozastavil {target}, včetně {followersCount} z vašich sledujících a {followingCount, plural, one {# účet, který sledujete} few {# účty, které sledujete} many {# účtů, které sledujete} other {# účtů, které sledujete}}.", @@ -531,10 +581,19 @@ "notification.status": "Uživatel {name} právě přidal příspěvek", "notification.update": "Uživatel {name} upravil příspěvek", "notification_requests.accept": "Přijmout", + "notification_requests.accept_multiple": "{count, plural, one {Schválit # požadavek…} few {Schválit # požadavky…} other {Schválit # požadavků…}}", + "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Schválit požadavek} other {Schválit požadavky}}", + "notification_requests.confirm_accept_multiple.message": "Chystáte se schválit {count, plural, one {jeden požadavek} few {# požadavky} other {# požadavků}} na oznámení. Opravdu chcete pokračovat?", "notification_requests.confirm_accept_multiple.title": "Přijmout žádosti o oznámení?", + "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Zamítnout požadavek} other {Zamítnout požadavky}}", + "notification_requests.confirm_dismiss_multiple.message": "Chystáte se zamítnout {count, plural, one {jeden požadavek} few {# požadavky} many {# požadavků} other {# požadavků}} na oznámení. Poté k {count, plural, one {němu} other {něm}} již nebudete mít snadný přístup. Opravdu chcete pokračovat?", + "notification_requests.confirm_dismiss_multiple.title": "Zamítnout požadavky na oznámení?", "notification_requests.dismiss": "Zamítnout", + "notification_requests.dismiss_multiple": "Zamítnout {count, plural, one {# požadavek} few {# požadavky} many {# požadavků} other {# požadavků}}…", "notification_requests.edit_selection": "Upravit", "notification_requests.exit_selection": "Hotovo", + "notification_requests.explainer_for_limited_account": "Oznámení z tohoto účtu byla filtrována, protože tento účet byl omezen moderátorem.", + "notification_requests.explainer_for_limited_remote_account": "Oznámení z tohoto účtu byla filtrována, protože tento účet nebo jeho server byl omezen moderátorem.", "notification_requests.maximize": "Maximalizovat", "notification_requests.minimize_banner": "Minimalizovat banner filtrovaných oznámení", "notification_requests.notifications_from": "Oznámení od {name}", @@ -578,6 +637,7 @@ "notifications.policy.accept": "Přijmout", "notifications.policy.accept_hint": "Zobrazit v oznámeních", "notifications.policy.drop": "Ignorovat", + "notifications.policy.drop_hint": "Permanentně odstranit, aby již nikdy nespatřil světlo světa", "notifications.policy.filter": "Filtrovat", "notifications.policy.filter_hint": "Odeslat do filtrované schránky oznámení", "notifications.policy.filter_limited_accounts_hint": "Omezeno moderátory serveru", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 1f3bccee152e33..d533e99906bbda 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -407,6 +407,13 @@ "ignore_notifications_modal.not_followers_title": "An dtugann tú aird ar fhógraí ó dhaoine nach leanann tú?", "ignore_notifications_modal.not_following_title": "An ndéanann tú neamhaird de fhógraí ó dhaoine nach leanann tú?", "ignore_notifications_modal.private_mentions_title": "An dtugann tú aird ar fhógraí ó Luaintí Príobháideacha gan iarraidh?", + "interaction_modal.action.favourite": "Chun leanúint ar aghaidh, ní mór duit an ceann is fearr leat ó do chuntas.", + "interaction_modal.action.follow": "Chun leanúint ar aghaidh, ní mór duit leanúint ó do chuntas.", + "interaction_modal.action.reblog": "Chun leanúint ar aghaidh, ní mór duit athbhlagáil ó do chuntas.", + "interaction_modal.action.reply": "Chun leanúint ar aghaidh, ní mór duit freagra a thabhairt ó do chuntas.", + "interaction_modal.action.vote": "Chun leanúint ar aghaidh, ní mór duit vótáil ó do chuntas.", + "interaction_modal.go": "Téigh", + "interaction_modal.no_account_yet": "Níl cuntas agat fós?", "interaction_modal.on_another_server": "Ar freastalaí eile", "interaction_modal.on_this_server": "Ar an freastalaí seo", "interaction_modal.title.favourite": "An postáil {name} is fearr leat", @@ -414,6 +421,7 @@ "interaction_modal.title.reblog": "Mol postáil de chuid {name}", "interaction_modal.title.reply": "Freagair postáil {name}", "interaction_modal.title.vote": "Vótáil i vótaíocht {name}", + "interaction_modal.username_prompt": "M.sh. {example}", "intervals.full.days": "{number, plural, one {# lá} other {# lá}}", "intervals.full.hours": "{number, plural, one {# uair} other {# uair}}", "intervals.full.minutes": "{number, plural, one {# nóiméad} other {# nóiméad}}", @@ -449,6 +457,7 @@ "keyboard_shortcuts.toggle_hidden": "Taispeáin/folaigh an téacs taobh thiar de CW", "keyboard_shortcuts.toggle_sensitivity": "Taispeáin / cuir i bhfolach meáin", "keyboard_shortcuts.toot": "Cuir tús le postáil nua", + "keyboard_shortcuts.translate": "post a aistriú", "keyboard_shortcuts.unfocus": "Unfocus cum textarea/search", "keyboard_shortcuts.up": "Bog suas ar an liosta", "lightbox.close": "Dún", @@ -687,6 +696,8 @@ "privacy_policy.title": "Polasaí príobháideachais", "recommended": "Molta", "refresh": "Athnuaigh", + "regeneration_indicator.please_stand_by": "Fan i do sheasamh, le do thoil.", + "regeneration_indicator.preparing_your_home_feed": "Ag ullmhú do bheatha baile…", "relative_time.days": "{number}l", "relative_time.full.days": "{number, plural, one {# lá} other {# lá}} ó shin", "relative_time.full.hours": "{number, plural, one {# uair} other {# uair}} ó shin", @@ -826,6 +837,7 @@ "status.reblogs.empty": "Níor mhol éinne an phostáil seo fós. Nuair a mholfaidh duine éigin í, taispeánfar anseo é sin.", "status.redraft": "Scrios ⁊ athdhréachtaigh", "status.remove_bookmark": "Bain leabharmharc", + "status.remove_favourite": "Bain ó cheanáin", "status.replied_in_thread": "D'fhreagair sa snáithe", "status.replied_to": "D'fhreagair {name}", "status.reply": "Freagair", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index e2f821022ee249..9125ce641bc63f 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -803,7 +803,7 @@ "status.bookmark": "Adder al marcapaginas", "status.cancel_reblog_private": "Disfacer impulso", "status.cannot_reblog": "Iste message non pote esser impulsate", - "status.continued_thread": "Discussion continuate", + "status.continued_thread": "Continuation del discussion", "status.copy": "Copiar ligamine a message", "status.delete": "Deler", "status.detailed_status": "Vista detaliate del conversation", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 09e6ea072a0152..aa58c34687248c 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -431,11 +431,11 @@ "keyboard_shortcuts.column": "Focalizza alla colonna", "keyboard_shortcuts.compose": "Focalizza l'area di composizione testuale", "keyboard_shortcuts.description": "Descrizione", - "keyboard_shortcuts.direct": "per aprire la colonna menzioni private", + "keyboard_shortcuts.direct": "Apre la colonna \"menzioni private\"", "keyboard_shortcuts.down": "Scorri in basso nell'elenco", "keyboard_shortcuts.enter": "Apre il post", "keyboard_shortcuts.favourite": "Contrassegna il post come preferito", - "keyboard_shortcuts.favourites": "Apri l'elenco dei preferiti", + "keyboard_shortcuts.favourites": "Apre l'elenco dei preferiti", "keyboard_shortcuts.federated": "Apre la cronologia federata", "keyboard_shortcuts.heading": "Scorciatoie da tastiera", "keyboard_shortcuts.home": "Apre la cronologia domestica", @@ -457,6 +457,7 @@ "keyboard_shortcuts.toggle_hidden": "Mostra/Nasconde il testo dietro CW", "keyboard_shortcuts.toggle_sensitivity": "Mostra/Nasconde media", "keyboard_shortcuts.toot": "Crea un nuovo post", + "keyboard_shortcuts.translate": "Traduce un post", "keyboard_shortcuts.unfocus": "Rimuove il focus sull'area di composizione testuale/ricerca", "keyboard_shortcuts.up": "Scorre in su nell'elenco", "lightbox.close": "Chiudi", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 208ed911046b7b..926781b7f6a856 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -407,6 +407,13 @@ "ignore_notifications_modal.not_followers_title": "本当に「フォローされていないアカウントからの通知」を無視するようにしますか?", "ignore_notifications_modal.not_following_title": "本当に「フォローしていないアカウントからの通知」を無視するようにしますか?", "ignore_notifications_modal.private_mentions_title": "本当に「外部からの非公開の返信」を無視するようにしますか?", + "interaction_modal.action.favourite": "お気に入り登録はあなたのアカウントがあるサーバーで行う必要があります。", + "interaction_modal.action.follow": "ユーザーをフォローするには、あなたのアカウントがあるサーバーからフォローする必要があります。", + "interaction_modal.action.reblog": "投稿をブーストするには、あなたのアカウントがあるサーバーでブーストする必要があります。", + "interaction_modal.action.reply": "リプライを送るには、あなたのアカウントがあるサーバーから送る必要があります。", + "interaction_modal.action.vote": "票を入れるには、あなたのアカウントがあるサーバーから投票する必要があります。", + "interaction_modal.go": "サーバーに移動", + "interaction_modal.no_account_yet": "アカウントを持っていない場合は:", "interaction_modal.on_another_server": "別のサーバー", "interaction_modal.on_this_server": "このサーバー", "interaction_modal.title.favourite": "{name}さんの投稿をお気に入り登録", @@ -414,6 +421,7 @@ "interaction_modal.title.reblog": "{name}さんの投稿をブースト", "interaction_modal.title.reply": "{name}さんの投稿にリプライ", "interaction_modal.title.vote": "{name}さんのアンケートに投票", + "interaction_modal.username_prompt": "例: {example}", "intervals.full.days": "{number}日", "intervals.full.hours": "{number}時間", "intervals.full.minutes": "{number}分", diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json index 9e9fe9088852dc..6fe1330d6c68c1 100644 --- a/app/javascript/mastodon/locales/ne.json +++ b/app/javascript/mastodon/locales/ne.json @@ -47,11 +47,13 @@ "account.mutual": "आपसी", "account.no_bio": "कुनै विवरण प्रदान गरिएको छैन।", "account.posts": "पोस्टहरू", + "account.posts_with_replies": "पोस्ट र जवाफहरू", "account.report": "@{name}लाई रिपोर्ट गर्नुहोस्", "account.requested": "स्वीकृतिको पर्खाइमा। फलो अनुरोध रद्द गर्न क्लिक गर्नुहोस्", "account.requested_follow": "{name} ले तपाईंलाई फलो गर्न अनुरोध गर्नुभएको छ", "account.share": "@{name} को प्रोफाइल सेयर गर्नुहोस्", "account.show_reblogs": "@{name} को बूस्टहरू देखाउनुहोस्", + "account.statuses_counter": "{count, plural, one {{counter} पोस्ट} other {{counter} पोस्टहरू}}", "account.unblock": "@{name} लाई अनब्लक गर्नुहोस्", "account.unblock_domain": "{domain} डोमेनलाई अनब्लक गर्नुहोस्", "account.unblock_short": "अनब्लक गर्नुहोस्", @@ -67,14 +69,18 @@ "alert.unexpected.message": "एउटा अनपेक्षित त्रुटि भयो।", "announcement.announcement": "घोषणा", "annual_report.summary.followers.followers": "फलोअरहरु", + "annual_report.summary.highlighted_post.by_reblogs": "सबैभन्दा बढि बूस्ट गरिएको पोस्ट", "annual_report.summary.new_posts.new_posts": "नयाँ पोस्टहरू", "block_modal.remote_users_caveat": "हामी सर्भर {domain} लाई तपाईंको निर्णयको सम्मान गर्न सोध्नेछौं। तर, हामी अनुपालनको ग्यारेन्टी दिन सक्दैनौं किनभने केही सर्भरहरूले ब्लकहरू फरक रूपमा ह्यान्डल गर्न सक्छन्। सार्वजनिक पोस्टहरू लग इन नभएका प्रयोगकर्ताहरूले देख्न सक्छन्।", "block_modal.show_less": "कम देखाउनुहोस्", "block_modal.show_more": "थप देखाउनुहोस्", - "block_modal.title": "प्रयोगकर्तालाई ब्लक गर्ने हो?", + "block_modal.title": "प्रयोगकर्तालाई ब्लक गर्ने?", + "boost_modal.reblog": "पोस्ट बुस्ट गर्ने?", + "boost_modal.undo_reblog": "पोस्ट अनबुस्ट गर्ने?", "bundle_column_error.copy_stacktrace": "त्रुटि रिपोर्ट प्रतिलिपि गर्नुहोस्", "bundle_column_error.network.title": "नेटवर्क त्रुटि", "bundle_column_error.retry": "पुन: प्रयास गर्नुहोस्", + "bundle_column_error.routing.title": "४०४", "bundle_modal_error.close": "बन्द गर्नुहोस्", "bundle_modal_error.retry": "Try again", "closed_registrations.other_server_instructions": "Mastodon विकेन्द्रीकृत भएकोले, तपाइँ अर्को सर्भरमा खाता खोल्न सक्नुहुन्छ र पनि यो सर्भरसँग अन्तरक्रिया गर्न सक्नुहुन्छ।", @@ -82,23 +88,54 @@ "closed_registrations_modal.find_another_server": "अर्को सर्भर खोज्नुहोस्", "closed_registrations_modal.title": "Mastodon मा साइन अप गर्दै", "column.blocks": "ब्लक गरिएको प्रयोगकर्ताहरु", + "column.bookmarks": "बुकमार्कहरू", + "column.create_list": "सूची बनाउनुहोस्", + "column.direct": "निजी उल्लेखहरू", "column.directory": "प्रोफाइल ब्राउज गर्नुहोस्", "column.domain_blocks": "ब्लक गरिएको डोमेन", + "column.edit_list": "सूची सम्पादन गर्नुहोस्", "column.follow_requests": "फलो अनुरोधहरू", + "column.home": "गृहपृष्ठ", "column.lists": "सूचीहरू", + "column.mutes": "म्यूट गरिएका प्रयोगकर्ताहरू", "column.notifications": "सूचनाहरू", + "column.pins": "पिन गरिएका पोस्टहरू", "column_header.hide_settings": "सेटिङ्हरू लुकाउनुहोस्", + "column_header.pin": "पिन गर्नुहोस्", + "column_header.unpin": "अनपिन गर्नुहोस्", + "column_search.cancel": "रद्द गर्नुहोस्", "column_subheading.settings": "सेटिङहरू", + "community.column_settings.media_only": "मिडिया मात्र", "compose.language.change": "भाषा परिवर्तन गर्नुहोस्", "compose.language.search": "भाषाहरू खोज्नुहोस्...", + "compose.published.body": "पोस्ट प्रकाशित भयो।", + "compose.published.open": "खोल्नुहोस्", + "compose.saved.body": "पोस्ट सेभ गरियो।", "compose_form.direct_message_warning_learn_more": "थप जान्नुहोस्", + "compose_form.placeholder": "तपाईको मनमा के छ?", + "compose_form.publish": "पोस्ट गर्नुहोस्", "compose_form.publish_form": "नयाँ पोस्ट", + "compose_form.reply": "जवाफ दिनुहोस्", + "compose_form.save_changes": "अपडेट गर्नुहोस्", + "confirmations.delete.message": "के तपाइँ पक्का हुनुहुन्छ कि तपाईं यो पोष्ट मेटाउन चाहनुहुन्छ?", + "confirmations.delete.title": "पोस्ट मेटाउने?", + "confirmations.delete_list.message": "के तपाइँ पक्का हुनुहुन्छ कि तपाईं यो सूची स्थायी रूपमा मेटाउन चाहनुहुन्छ?", + "confirmations.delete_list.title": "सूची मेटाउने?", + "confirmations.edit.confirm": "सम्पादन गर्नुहोस्", + "confirmations.edit.message": "अहिले सम्पादन गर्नाले तपाईंले हाल लेखिरहनुभएको सन्देश अधिलेखन हुनेछ। के तपाईं अगाडि बढ्न चाहनुहुन्छ?", + "confirmations.edit.title": "पोस्ट अधिलेखन गर्ने?", "confirmations.follow_to_list.confirm": "फलो गर्नुहोस र सूचीमा थप्नुहोस्", "confirmations.follow_to_list.message": "सूचीमा {name}लाई थप्नको लागि तपाईंले तिनीहरूलाई फलो गरेको हुनुपर्छ।", - "confirmations.follow_to_list.title": "प्रयोगकर्तालाई फलो गर्ने हो?", + "confirmations.follow_to_list.title": "प्रयोगकर्तालाई फलो गर्ने?", + "confirmations.logout.message": "के तपाइँ पक्का हुनुहुन्छ कि तपाइँ लाई लग आउट गर्न चाहनुहुन्छ?", + "confirmations.logout.title": "लग आउट गर्ने?", + "confirmations.redraft.title": "पोस्ट मेटाएर पुन: ड्राफ्ट गर्ने?", + "confirmations.reply.message": "अहिले जवाफ दिनाले तपाईंले हाल लेखिरहनुभएको सन्देश अधिलेखन हुनेछ। के तपाईं अगाडि बढ्न चाहनुहुन्छ?", + "confirmations.reply.title": "पोस्ट अधिलेखन गर्ने?", "confirmations.unfollow.confirm": "अनफलो गर्नुहोस्", "confirmations.unfollow.message": "के तपाइँ पक्का हुनुहुन्छ कि तपाइँ {name}लाई अनफलो गर्न चाहनुहुन्छ?", - "confirmations.unfollow.title": "प्रयोगकर्तालाई अनफलो गर्ने हो?", + "confirmations.unfollow.title": "प्रयोगकर्तालाई अनफलो गर्ने?", + "disabled_account_banner.account_settings": "खाता सेटिङहरू", "empty_column.follow_requests": "तपाईंले अहिलेसम्म कुनै पनि फलो अनुरोधहरू प्राप्त गर्नुभएको छैन। तपाईंले कुनै प्राप्त गरेपछि त्यो यहाँ देखिनेछ।", "empty_column.followed_tags": "तपाईंले अहिलेसम्म कुनै पनि ह्यासट्यागहरू फलो गर्नुभएको छैन। तपाईंले ह्यासट्याग फलो गरेपछि तिनीहरू यहाँ देखिनेछन्।", "follow_suggestions.dismiss": "फेरि नदेखाउनुहोस्", @@ -111,15 +148,35 @@ "followed_tags": "फलो गरिएका ह्यासट्यागहरू", "hashtag.follow": "ह्यासट्याग फलो गर्नुहोस्", "hashtag.unfollow": "ह्यासट्याग अनफलो गर्नुहोस्", + "home.column_settings.show_reblogs": "बूस्टहरू देखाउनुहोस्", + "interaction_modal.no_account_yet": "अहिलेसम्म खाता छैन?", "interaction_modal.title.follow": "{name} लाई फलो गर्नुहोस्", + "interaction_modal.title.reblog": "{name} को पोस्ट बुस्ट गर्नुहोस्", + "keyboard_shortcuts.boost": "पोस्ट बुस्ट गर्नुहोस्", "mute_modal.they_wont_know": "उनीहरूलाई म्यूट गरिएको बारे थाहा हुँदैन।", - "mute_modal.title": "प्रयोगकर्तालाई म्युट गर्ने हो?", + "mute_modal.title": "प्रयोगकर्तालाई म्युट गर्ने?", "navigation_bar.blocks": "ब्लक गरिएको प्रयोगकर्ताहरु", "navigation_bar.follow_requests": "फलो अनुरोधहरू", "navigation_bar.followed_tags": "फलो गरिएका ह्यासट्यागहरू", + "notification.reblog": "{name} ले तपाईंको पोस्ट बूस्ट गर्नुभयो", + "notification_requests.confirm_accept_multiple.title": "सूचना अनुरोधहरू स्वीकार गर्ने?", + "notification_requests.confirm_dismiss_multiple.title": "सूचना अनुरोधहरू खारेज गर्ने?", + "notifications.clear_title": "सूचनाहरू खाली गर्ने?", + "notifications.column_settings.reblog": "बूस्टहरू:", + "notifications.filter.boosts": "बूस्टहरू", + "report.comment.title": "के हामीले थाहा पाउनुपर्ने अरू केही छ जस्तो लाग्छ?", + "report.forward_hint": "यो खाता अर्को सर्भरबाट हो। त्यहाँ पनि रिपोर्टको गुमनाम प्रतिलिपि पठाउने हो?", + "report.rules.title": "कुन नियमहरू उल्लङ्घन भइरहेका छन्?", + "report.statuses.title": "के यस रिपोर्टलाई समर्थन गर्ने कुनै पोस्टहरू छन्?", + "report.thanks.title": "यो हेर्न चाहनुहुन्न?", "report.unfollow": "@{name} लाई अनफलो गर्नुहोस्", "search_results.hashtags": "ह्यासट्यागहरू", + "status.cancel_reblog_private": "अनबुस्ट गर्नुहोस्", + "status.cannot_reblog": "यो पोस्टलाई बुस्ट गर्न सकिँदैन", "status.mute": "@{name}लाई म्यूट गर्नुहोस्", "status.mute_conversation": "कुराकानी म्यूट गर्नुहोस्", + "status.reblog": "बूस्ट गर्नुहोस्", + "status.reblogged_by": "{name} ले बूस्ट गर्नुभएको", + "status.reblogs": "{count, plural, one {बूस्ट} other {बूस्टहरू}}", "status.unmute_conversation": "कुराकानी अनम्यूट गर्नुहोस्" } diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index f9cb9743ac3b7a..b419870d7aefa0 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -416,6 +416,7 @@ "interaction_modal.title.reblog": "Podbij wpis {name}", "interaction_modal.title.reply": "Odpowiedz na post {name}", "interaction_modal.title.vote": "Weź udział w głosowaniu {name}", + "interaction_modal.username_prompt": "Np. {example}", "intervals.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}", "intervals.full.hours": "{number, plural, one {# godzina} few {# godziny} many {# godzin} other {# godzin}}", "intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}", @@ -828,6 +829,7 @@ "status.reblogs.empty": "Nikt nie podbił jeszcze tego wpisu. Gdy ktoś to zrobi, pojawi się tutaj.", "status.redraft": "Usuń i przeredaguj", "status.remove_bookmark": "Usuń zakładkę", + "status.remove_favourite": "Usuń z ulubionych", "status.replied_in_thread": "Odpowiedź w wątku", "status.replied_to": "Odpowiedź do wpisu użytkownika {name}", "status.reply": "Odpowiedz", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 142ae33c585264..99929e1f351172 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -108,7 +108,7 @@ "annual_report.summary.thanks": "Obrigada por fazer parte do Mastodon!", "attachments_list.unprocessed": "(não processado)", "audio.hide": "Ocultar áudio", - "block_modal.remote_users_caveat": "Pediremos ao servidor {domínio} que respeite sua decisão. No entanto, a conformidade não é garantida pois alguns servidores podem lidar com os blocos de maneira diferente. As postagens públicas ainda podem estar visíveis para usuários não logados.", + "block_modal.remote_users_caveat": "Pediremos ao servidor {domain} que respeite sua decisão. No entanto, a conformidade não é garantida, já que alguns servidores podem lidar com bloqueios de maneira diferente. As postagens públicas ainda podem estar visíveis para usuários não logados.", "block_modal.show_less": "Mostrar menos", "block_modal.show_more": "Mostrar mais", "block_modal.they_cant_mention": "Eles não podem mencionar ou seguir você.", @@ -259,9 +259,9 @@ "domain_pill.their_server": "Sua casa digital, onde ficam todas as suas postagens.", "domain_pill.their_username": "Seu identificador exclusivo em seu servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.", "domain_pill.username": "Nome de usuário", - "domain_pill.whats_in_a_handle": "O que há em uma alça?", - "domain_pill.who_they_are": "Como os identificadores indicam quem alguém é e onde está, você pode interagir com pessoas na web social de .", - "domain_pill.who_you_are": "Como seu identificador indica quem você é e onde está, as pessoas podem interagir com você nas redes sociais das .", + "domain_pill.whats_in_a_handle": "O que há em um identificador?", + "domain_pill.who_they_are": "Como os identificadores indicam quem alguém é e onde está, você pode interagir com pessoas na rede de .", + "domain_pill.who_you_are": "Como seu identificador indica quem você é e onde está, as pessoas podem interagir com você na rede de .", "domain_pill.your_handle": "Seu identificador:", "domain_pill.your_server": "Sua casa digital, onde ficam todas as suas postagens. Não gosta deste? Transfira servidores a qualquer momento e traga seus seguidores também.", "domain_pill.your_username": "Seu identificador exclusivo neste servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index f7329ad97eac9a..42d06ea72be384 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -1,7 +1,7 @@ { "about.blocks": "Servidores moderados", "about.contact": "Contacto:", - "about.disclaimer": "O Mastodon é um software livre, de código aberto e uma marca registada do Mastodon gGmbH.", + "about.disclaimer": "O Mastodon é um software livre, de código aberto e uma marca registada de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Motivo não disponível", "about.domain_blocks.preamble": "O Mastodon geralmente permite ver e interagir com o conteúdo de utilizadores de qualquer outra instância no fediverso. Estas são as exceções desta instância em específico.", "about.domain_blocks.silenced.explanation": "Normalmente não verás perfis e conteúdos deste servidor, a não ser que os procures explicitamente ou optes por segui-los.", @@ -723,7 +723,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicação", "report.close": "Concluído", - "report.comment.title": "Há algo mais que pensa que devemos saber?", + "report.comment.title": "Há mais alguma coisa que devamos saber?", "report.forward": "Reencaminhar para {target}", "report.forward_hint": "A conta pertence a outro servidor. Enviar uma cópia anónima da denúncia para esse servidor também?", "report.mute": "Ocultar", @@ -739,15 +739,15 @@ "report.reasons.spam": "É spam", "report.reasons.spam_description": "Hiperligações maliciosas, contactos falsos ou respostas repetitivas", "report.reasons.violation": "Viola as regras do servidor", - "report.reasons.violation_description": "Está ciente de que infringe regras específicas", - "report.rules.subtitle": "Selecione tudo o que se aplicar", + "report.reasons.violation_description": "Infringe regras específicas", + "report.rules.subtitle": "Seleciona tudo o que se aplicar", "report.rules.title": "Que regras estão a ser violadas?", - "report.statuses.subtitle": "Selecione tudo o que se aplicar", + "report.statuses.subtitle": "Seleciona tudo o que se aplicar", "report.statuses.title": "Existe alguma publicação que suporte esta denúncia?", "report.submit": "Enviar", "report.target": "A denunciar {target}", "report.thanks.take_action": "Aqui estão as suas opções para controlar o que vê no Mastodon:", - "report.thanks.take_action_actionable": "Enquanto revemos a sua denúncia, pode tomar medidas contra @{name}:", + "report.thanks.take_action_actionable": "Enquanto revemos a tua denúncia, podes tomar medidas contra @{name}:", "report.thanks.title": "Não quer ver isto?", "report.thanks.title_actionable": "Obrigado por nos informares, vamos analisar a situação.", "report.unfollow": "Deixar de seguir @{name}", @@ -758,7 +758,7 @@ "report_notification.categories.other": "Outro", "report_notification.categories.other_sentence": "outro", "report_notification.categories.spam": "Spam", - "report_notification.categories.spam_sentence": "spam", + "report_notification.categories.spam_sentence": "publicidade indesejada / spam", "report_notification.categories.violation": "Violação de regra", "report_notification.categories.violation_sentence": "violação de regra", "report_notification.open": "Abrir denúncia", @@ -813,7 +813,7 @@ "status.edited": "Última edição em {date}", "status.edited_x_times": "Editado {count, plural,one {{count} vez} other {{count} vezes}}", "status.embed": "Obter código de incorporação", - "status.favourite": "Assinalar como favorito", + "status.favourite": "Adicionar aos favoritos", "status.favourites": "{count, plural, one {favorito} other {favoritos}}", "status.filter": "Filtrar esta publicação", "status.history.created": "{name} criado em {date}", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index f59e7b96bc4fad..d0617a52c84b5f 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -530,6 +530,7 @@ "notification.status": "{name} uverejňuje niečo nové", "notification.update": "{name} upravuje príspevok", "notification_requests.accept": "Prijať", + "notification_requests.confirm_accept_multiple.title": "Priať požiadavku o oboznámenia?", "notification_requests.dismiss": "Zamietnuť", "notification_requests.edit_selection": "Uprav", "notification_requests.exit_selection": "Hotovo", @@ -624,6 +625,7 @@ "privacy_policy.title": "Pravidlá ochrany súkromia", "recommended": "Odporúčané", "refresh": "Obnoviť", + "regeneration_indicator.preparing_your_home_feed": "Pripravuje sa tvoj domáci kanál…", "relative_time.days": "{number} dní", "relative_time.full.days": "Pred {number, plural, one {# dňom} other {# dňami}}", "relative_time.full.hours": "Pred {number, plural, one {# hodinou} other {# hodinami}}", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 834c74eb777177..3bad8fedb7b3ee 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -550,7 +550,7 @@ "notification.favourite.name_and_others_with_link": "{name} và {count, plural, other {# người khác}} đã thích tút của bạn", "notification.favourite_pm": "{name} đã thích lượt nhắn riêng của bạn", "notification.favourite_pm.name_and_others_with_link": "{name} và {count, plural, other {# người khác}} đã thích lượt nhắn riêng của bạn", - "notification.follow": "{name} theo dõi bạn", + "notification.follow": "{name} đã theo dõi bạn", "notification.follow.name_and_others": "{name} và {count, plural, other {# người khác}} theo dõi bạn", "notification.follow_request": "{name} yêu cầu theo dõi bạn", "notification.follow_request.name_and_others": "{name} và {count, plural, other {# người khác}} đã yêu cầu theo dõi bạn", @@ -764,7 +764,7 @@ "report_notification.open": "Mở báo cáo", "search.no_recent_searches": "Gần đây chưa tìm gì", "search.placeholder": "Tìm kiếm", - "search.quick_action.account_search": "Người có tên {x}", + "search.quick_action.account_search": "Người tên {x}", "search.quick_action.go_to_account": "Xem trang {x}", "search.quick_action.go_to_hashtag": "Xem hashtag {x}", "search.quick_action.open_url": "Mở liên kết trong Mastodon", diff --git a/config/locales/activerecord.cs.yml b/config/locales/activerecord.cs.yml index 6f4fe86e3fdc85..e62c4264ddbf25 100644 --- a/config/locales/activerecord.cs.yml +++ b/config/locales/activerecord.cs.yml @@ -15,9 +15,17 @@ cs: user/invite_request: text: Důvod errors: + attributes: + domain: + invalid: není platný název domény + messages: + invalid_domain_on_line: "%{value} není platný název domény" + too_many_lines: překročil limit %{limit} řádků models: account: attributes: + fields: + fields_with_values_missing_labels: obsahuje hodnoty s chybějícími popisky username: invalid: musí obsahovat pouze písmena, číslice a podtržítka reserved: je vyhrazeno @@ -33,6 +41,11 @@ cs: attributes: data: malformed: je chybný + list_account: + attributes: + account_id: + taken: již je na seznamu + must_be_following: musí být sledovaný účet status: attributes: reblog: diff --git a/config/locales/activerecord.ga.yml b/config/locales/activerecord.ga.yml index 5b36d80d79addf..c02c5370762aca 100644 --- a/config/locales/activerecord.ga.yml +++ b/config/locales/activerecord.ga.yml @@ -24,6 +24,8 @@ ga: models: account: attributes: + fields: + fields_with_values_missing_labels: ina bhfuil luachanna a bhfuil lipéid in easnamh orthu username: invalid: ní mór go mbeadh litreacha, uimhreacha agus pointí béime amháin reserved: in áirithe diff --git a/config/locales/activerecord.it.yml b/config/locales/activerecord.it.yml index 3e8897577ada4a..6db643408c20c7 100644 --- a/config/locales/activerecord.it.yml +++ b/config/locales/activerecord.it.yml @@ -24,6 +24,8 @@ it: models: account: attributes: + fields: + fields_with_values_missing_labels: contiene valori con label mancanti username: invalid: deve contenere solo lettere, numeri e trattini bassi reserved: è riservato diff --git a/config/locales/activerecord.ne.yml b/config/locales/activerecord.ne.yml index db03c5186bdef4..12795ea20d159f 100644 --- a/config/locales/activerecord.ne.yml +++ b/config/locales/activerecord.ne.yml @@ -1 +1,35 @@ +--- ne: + activerecord: + attributes: + user: + agreement: सेवा सम्झौता + email: ईमेल ठेगाना + password: पासवर्ड + user/account: + username: प्रयोगकर्ता नाम + user/invite_request: + text: कारण + errors: + attributes: + domain: + invalid: मान्य डोमेन नाम होइन + messages: + invalid_domain_on_line: "%{value} मान्य डोमेन नाम होइन" + models: + account: + attributes: + username: + invalid: अक्षर, संख्या र अन्डरस्कोर मात्र हुनु पर्छ + admin/webhook: + attributes: + url: + invalid: मान्य URL होइन + doorkeeper/application: + attributes: + website: + invalid: मान्य URL होइन + list_account: + attributes: + account_id: + taken: पहिले नै सूचीमा छ diff --git a/config/locales/activerecord.pt-PT.yml b/config/locales/activerecord.pt-PT.yml index 8b17ade2eb6738..ecbfab3dc40140 100644 --- a/config/locales/activerecord.pt-PT.yml +++ b/config/locales/activerecord.pt-PT.yml @@ -27,7 +27,7 @@ pt-PT: fields: fields_with_values_missing_labels: contém valores com etiquetas em falta username: - invalid: deve conter apenas letras, números e traços inferiores + invalid: deve conter apenas letras, números e traços inferiores (_) reserved: está reservado admin/webhook: attributes: diff --git a/config/locales/cs.yml b/config/locales/cs.yml index c9deb24ee1e757..135975c3c8c7ae 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -78,10 +78,10 @@ cs: enabled: Povoleno enabled_msg: Účet %{username} byl úspěšně rozmrazen followers: Sledující - follows: Sledované + follows: Sledovaní header: Záhlaví inbox_url: URL příchozí schránky - invite_request_text: Důvody založení + invite_request_text: Důvody pro připojení invited_by: Pozván uživatelem ip: IP adresa joined: Uživatelem od @@ -187,29 +187,40 @@ cs: confirm_user: Potvrdit uživatele create_account_warning: Vytvořit varování create_announcement: Nové oznámení + create_canonical_email_block: Vytvořit blok e-mailu create_custom_emoji: Vytvořit vlastní emoji create_domain_allow: Vytvořit povolení domény create_domain_block: Vytvořit blokaci domény + create_email_domain_block: Vytvořit blok e-mailové domény create_ip_block: Vytvořit IP pravidlo + create_relay: Vytvořit relay create_unavailable_domain: Vytvořit nedostupnou doménu create_user_role: Vytvořit roli demote_user: Snížit roli uživatele destroy_announcement: Odstranit oznámení + destroy_canonical_email_block: Odblokovat email destroy_custom_emoji: Odstranit vlastní emoji destroy_domain_allow: Odstranit povolení domény destroy_domain_block: Odstranit blokaci domény + destroy_email_domain_block: Smazat blokaci e-mailové domény destroy_instance: Odmazat doménu destroy_ip_block: Smazat IP pravidlo + destroy_relay: Smazat relay destroy_status: Odstranit Příspěvek destroy_unavailable_domain: Smazat nedostupnou doménu destroy_user_role: Zničit roli disable_2fa_user: Vypnout 2FA disable_custom_emoji: Zakázat vlastní emoji + disable_relay: Deaktivovat relay + disable_sign_in_token_auth_user: Zrušit uživatelovo ověřování e-mailovým tokenem disable_user: Deaktivovat uživatele enable_custom_emoji: Povolit vlastní emoji + enable_relay: Aktivovat relay + enable_sign_in_token_auth_user: Povolit uživatelovo ověřování e-mailovým tokenem enable_user: Povolit uživatele memorialize_account: Změna na „in memoriam“ promote_user: Povýšit uživatele + publish_terms_of_service: Zveřejnit smluvní podmínky reject_appeal: Zamítnout odvolání reject_user: Odmítnout uživatele remove_avatar_user: Odstranit avatar @@ -236,36 +247,50 @@ cs: approve_appeal_html: Uživatel %{name} schválil odvolání proti rozhodnutí moderátora %{target} approve_user_html: "%{name} schválil registraci od %{target}" assigned_to_self_report_html: Uživatel %{name} si přidělil hlášení %{target} + change_email_user_html: "%{name} změnil*a e-mailovou adresu %{target}" change_role_user_html: "%{name} změnil roli %{target}" + confirm_user_html: Uživatel %{name} potvrdil e-mailovou adresu uživatele %{target} create_account_warning_html: Uživatel %{name} poslal %{target} varování create_announcement_html: Uživatel %{name} vytvořil nové oznámení %{target} + create_canonical_email_block_html: "%{name} zablokoval e-mail s hashem %{target}" create_custom_emoji_html: Uživatel %{name} nahrál nové emoji %{target} create_domain_allow_html: Uživatel %{name} povolil federaci s doménou %{target} create_domain_block_html: Uživatel %{name} zablokoval doménu %{target} + create_email_domain_block_html: Uživatel %{name} zablokoval e-mailovou doménu %{target} create_ip_block_html: Uživatel %{name} vytvořil pravidlo pro IP %{target} + create_relay_html: "%{name} vytvořil*a relay %{target}" create_unavailable_domain_html: "%{name} zastavil doručování na doménu %{target}" create_user_role_html: "%{name} vytvořil %{target} roli" demote_user_html: Uživatel %{name} degradoval uživatele %{target} destroy_announcement_html: Uživatel %{name} odstranil oznámení %{target} + destroy_canonical_email_block_html: "%{name} odblokoval*a e-mail s hashem %{target}" destroy_custom_emoji_html: "%{name} odstranil emoji %{target}" destroy_domain_allow_html: Uživatel %{name} zakázal federaci s doménou %{target} destroy_domain_block_html: Uživatel %{name} odblokoval doménu %{target} + destroy_email_domain_block_html: "%{name} odblokoval*a e-mailovou doménu %{target}" destroy_instance_html: Uživatel %{name} odmazal doménu %{target} destroy_ip_block_html: Uživatel %{name} odstranil pravidlo pro IP %{target} + destroy_relay_html: "%{name} odstranil*a relay %{target}" destroy_status_html: Uživatel %{name} odstranil příspěvek uživatele %{target} destroy_unavailable_domain_html: "%{name} obnovil doručování na doménu %{target}" destroy_user_role_html: "%{name} odstranil %{target} roli" disable_2fa_user_html: Uživatel %{name} vypnul dvoufázové ověřování pro uživatele %{target} disable_custom_emoji_html: Uživatel %{name} zakázal emoji %{target} + disable_relay_html: "%{name} deaktivoval*a relay %{target}" + disable_sign_in_token_auth_user_html: "%{name} deaktivoval*a ověřování e-mailovým tokenem pro %{target}" disable_user_html: Uživatel %{name} zakázal přihlašování pro uživatele %{target} enable_custom_emoji_html: Uživatel %{name} povolil emoji %{target} + enable_relay_html: "%{name} aktivoval*a relay %{target}" + enable_sign_in_token_auth_user_html: "%{name} aktivoval*a ověřování e-mailovým tokenem pro %{target}" enable_user_html: Uživatel %{name} povolil přihlašování pro uživatele %{target} memorialize_account_html: Uživatel %{name} změnil účet %{target} na „in memoriam“ stránku promote_user_html: Uživatel %{name} povýšil uživatele %{target} + publish_terms_of_service_html: "%{name} zveřejnil*a aktualizace podmínek služby" reject_appeal_html: Uživatel %{name} zamítl odvolání proti rozhodnutí moderátora %{target} reject_user_html: "%{name} odmítl registraci od %{target}" remove_avatar_user_html: Uživatel %{name} odstranil avatar uživatele %{target} reopen_report_html: Uživatel %{name} znovu otevřel hlášení %{target} + resend_user_html: "%{name} znovu odeslal*a potvrzovací e-mail pro %{target}" reset_password_user_html: Uživatel %{name} obnovil heslo uživatele %{target} resolve_report_html: Uživatel %{name} vyřešil hlášení %{target} sensitive_account_html: "%{name} označil média účtu %{target} jako citlivá" @@ -436,6 +461,7 @@ cs: many: "%{count} pokusů o registraci za poslední týden" one: "%{count} pokus o registraci za poslední týden" other: "%{count} pokusů o registraci za poslední týden" + created_msg: E-mailová doména úspěšně zablokována delete: Smazat dns: types: @@ -445,7 +471,9 @@ cs: create: Přidat doménu resolve: Přeložit doménu title: Blokovat novou e-mailovou doménu + no_email_domain_block_selected: Žádné blokace e-mailové domény nebyly změněny, protože žádné nebyly vybrány not_permitted: Nepovoleno + resolved_dns_records_hint_html: Doménové jméno vede na následující MX domény, které mají nakonec na starost přijímání e-mailů. Blokování MX domény zablokuje registrace z jakékoliv e-mailové adresy, která používá stejnou MX doménu, i když je viditelné doménové jméno jiné. Dejte si pozor, abyste nezablokovali velké e-mailové poskytovatele. resolved_through_html: Přeložena přes %{domain} title: Blokované e-mailové domény export_domain_allows: @@ -609,7 +637,9 @@ cs: resolve_description_html: Nebudou učiněny žádné kroky proti nahlášenému účtu, žádný prohřešek zaznamenán a hlášení bude uzavřeno. silence_description_html: Účet bude viditelný pouze těm, kdo jej již sledují nebo si jej ručně vyhledají, což výrazně omezí jeho dosah. Vždy lze vrátit zpět. Uzavře všechna hlášení proti tomuto účtu. suspend_description_html: Účet a veškerý jeho obsah se znepřístupní a bude nakonec smazán, interakce s ním nebude možná. Lze vrátit zpět do 30 dnů. Uzavře všechna hlášení proti tomuto účtu. + actions_description_html: Rozhodněte, který krok učinit pro vyřešení tohoto hlášení. Pokud podniknete kárný krok proti nahlášenému účtu, bude mu zasláno e-mailové oznámení, s výjimkou případu, kdy je zvolena kategorie Spam. actions_description_remote_html: Rozhodněte, co podniknout pro vyřešení tohoto hlášení. Toto ovlivní pouze to, jak váš server komunikuje s tímto vzdáleným účtem, a zpracuje jeho obsah. + actions_no_posts: Toto hlášení nemá žádné související příspěvky k odstranění add_to_report: Přidat do hlášení další already_suspended_badges: local: Již pozastaveno na tomto serveru @@ -673,6 +703,7 @@ cs: delete_data_html: Odstranit profil a obsah @%{acct} ode dneška po 30 dní, pokud mezitím nebude zrušeno jeho pozastavení preview_preamble_html: "@%{acct} obdrží varování s následujícím obsahem:" record_strike_html: Zaznamenat prohřešek @%{acct} pro pomoc s řešením budoucích přestupků z tohoto účtu + send_email_html: Poslat varovný e-mail pro @%{acct} warning_placeholder: Volitelné další odůvodnění moderační akce. target_origin: Původ nahlášeného účtu title: Hlášení @@ -716,6 +747,7 @@ cs: manage_appeals: Spravovat odvolání manage_appeals_description: Umožňuje uživatelům posuzovat odvolání proti moderátorským zásahům manage_blocks: Spravovat blokace + manage_blocks_description: Umožňuje uživatelům blokovat poskytovatele e-mailů a IP adresy manage_custom_emojis: Spravovat vlastní emoji manage_custom_emojis_description: Umožňuje uživatelům spravovat vlastní emoji na serveru manage_federation: Spravovat federaci @@ -733,6 +765,7 @@ cs: manage_taxonomies: Správa taxonomií manage_taxonomies_description: Umožňuje uživatelům zkontrolovat populární obsah a aktualizovat nastavení hashtag manage_user_access: Spravovat uživatelské přístupy + manage_user_access_description: Umožňuje uživatelům rušit jiným uživatelům dvoufázové ověřování, měnit jejich e-mailovou adresu a obnovovat jim hesla manage_users: Spravovat uživatele manage_users_description: Umožňuje uživatelům zobrazit podrobnosti ostatních uživatelů a provádět moderování proti nim manage_webhooks: Spravovat webhooky @@ -807,6 +840,7 @@ cs: destroyed_msg: Upload stránky byl úspěšně smazán! software_updates: critical_update: Kritické — aktualizujte, prosím, co nejdříve + description: Doporučuje se udržovat vaši instalaci Mastodonu aktuální, aby se využily nejnovější opravy a funkce. Kromě toho je někdy velmi důležité včas aktualizovat Mastodon, aby se předešlo bezpečnostním problémům. Z těchto důvodů Mastodon kontroluje aktualizace každých 30 minut a bude vás informovat podle nastavení vašeho e-mailového oznámení. documentation_link: Zjistit více release_notes: Poznámky k vydání title: Dostupné aktualizace @@ -822,6 +856,7 @@ cs: back_to_account: Zpět na stránku účtu back_to_report: Zpět na stránku hlášení batch: + add_to_report: 'Přidat do hlášení #%{id}' remove_from_report: Odebrat z hlášení report: Nahlásit contents: Obsah @@ -833,12 +868,17 @@ cs: media: title: Média metadata: Metadata + no_history: Tento příspěvek nebyl upraven no_status_selected: Nebyly změněny žádné příspěvky, neboť žádné nebyly vybrány open: Otevřít příspěvek original_status: Původní příspěvek reblogs: Boosty + replied_to_html: Odpověděl %{acct_link} status_changed: Příspěvek změněn + status_title: Příspěvek od @%{name} + title: Příspěvky na účtu - @%{name} trending: Populární + view_publicly: Zobrazit veřejně visibility: Viditelnost with_media: S médii strikes: @@ -880,6 +920,9 @@ cs: message_html: Nedefinovali jste žádná pravidla serveru. sidekiq_process_check: message_html: Pro %{value} frontu/fronty neběží žádný Sidekiq proces. Zkontrolujte prosím svou Sidekiq konfiguraci + software_version_check: + action: Zobrazit dostupné aktualizace + message_html: K dispozici je aktualizace Mastodonu. software_version_critical_check: action: Zobrazit dostupné aktualizace message_html: K dispozici je kritická aktualizace Mastodonu, prosím aktualizujte co nejrychleji. @@ -906,19 +949,57 @@ cs: name: Název newest: Nejnovější oldest: Nejstarší + open: Zobrazit veřejně reset: Resetovat review: Stav posouzení search: Hledat title: Hashtagy updated_msg: Nastavení hashtagů bylo úspěšně aktualizováno + terms_of_service: + back: Zpět na podmínky služby + changelog: Co se změnilo + create: Použít vlastní + current: Aktuální + draft: Koncept + generate: Použít šablonu + generates: + action: Vygenerovat + chance_to_review_html: "Vygenerované podmínky služby nebudou zveřejněny automaticky. Výsledky budete mít možnost zkontrolovat. Pro pokračování vyplňte potřebné podrobnosti." + explanation_html: Poskytovaná šablona služeb je určena pouze pro informační účely a neměla by být vykládána jako právní poradenství v jakékoli věci. Prosíme, konzultujte Vaši situaci a konkrétní právní otázky s Vaším pravním zástupcem. + title: Nastavení podmínek služby + history: Historie + live: Živě + no_history: Zatím nejsou zaznamenány žádné změny podmínek služby. + no_terms_of_service_html: Momentálně nemáte nakonfigurovány žádné podmínky služby. Podmínky služby jsou určeny k zajištění jasnosti a ochránit Vás před případnými právními závazky ve sporech s vašimi uživateli. + notified_on_html: Uživatelé upozorněni dne %{date} + notify_users: Upozornit uživatele + preview: + explanation_html: 'E-mail bude poslán %{display_count} uživatelům, kteří se zaregistrovali před datem %{date}. Následující text bude obsažen v onom e-mailu:' + send_preview: Poslat náhled na %{email} + send_to_all: + few: Poslat %{display_count} emaily + many: Poslat %{display_count} emailů + one: Poslat %{display_count} email + other: Poslat %{display_count} emailů + title: Náhled oznámení o podmínkách služby + publish: Zveřejnit + published_on_html: Zveřejněno %{date} + save_draft: Uložit koncept + title: Podmínky služby title: Administrace trends: allow: Povolit approved: Schválené + confirm_allow: Opravdu chcete povolit vybrané štítky? + confirm_disallow: Opravdu chcete zakázat vybrané štítky? disallow: Zakázat links: allow: Povolit odkaz allow_provider: Povolit vydavatele + confirm_allow: Jste si jist, že chcete povolit vybrané odkazy? + confirm_allow_provider: Opravdu chcete povolit vybrané poskytovatele? + confirm_disallow: Opravdu chcete zakázat vybrané odkazy? + confirm_disallow_provider: Opravdu chcete zakázat vybrané poskytovatele? description_html: Toto jsou odkazy, které jsou momentálně hojně sdíleny účty, jejichž příspěvky váš server vidí. To může pomoct vašim uživatelům zjistit, co se děje ve světě. Žádné odkazy se nezobrazují veřejně, dokud neschválíte vydavatele. Můžete také povolit nebo zamítnout jednotlivé odkazy. disallow: Zakázat odkaz disallow_provider: Zakázat vydavatele @@ -944,6 +1025,10 @@ cs: statuses: allow: Povolit příspěvek allow_account: Povolit autora + confirm_allow: Opravdu chcete povolit vybrané tooty? + confirm_allow_account: Opravdu chcete povolit vybrané účty? + confirm_disallow: Opravdu chcete zakázat vybrané tooty? + confirm_disallow_account: Opravdu chcete zakázat vybrané účty? description_html: Toto jsou příspěvky, o kterých váš server ví, že jsou momentálně hodně sdíleny a oblibovány. To může pomoci vašim novým i vracejícím se uživatelům najít další lidi ke sledování. Žádné příspěvky se nezobrazují veřejně, dokud neschválíte autora a tento autor nepovolí navrhování svého účtu ostatním. Můžete také povolit či zamítnout jednotlivé příspěvky. disallow: Zakázat příspěvek disallow_account: Zakázat autora @@ -980,6 +1065,7 @@ cs: many: Použit %{count} lidmi za poslední týden one: Použit jedním člověkem za poslední týden other: Použit %{count} lidmi za poslední týden + title: Doporučení & Trendy trending: Populární warning_presets: add_new: Přidat nové @@ -1066,7 +1152,9 @@ cs: guide_link_text: Zapojit se může každý. sensitive_content: Citlivý obsah application_mailer: + notification_preferences: Změnit předvolby e-mailu salutation: "%{name}," + settings: 'Změnit předvolby e-mailu: %{link}' unsubscribe: Přestat odebírat view: 'Zobrazit:' view_profile: Zobrazit profil @@ -1086,6 +1174,7 @@ cs: hint_html: Ještě jedna věc! Musíme potvrdit, že jste člověk (to proto, abychom drželi stranou spam!). Vyřešte CAPTCHA níže a klikněte na "Pokračovat". title: Bezpečnostní kontrola confirmations: + awaiting_review: Vaše e-mailová adresa je potvrzena! Personál %{domain} nyní kontrolují vaši registraci. Pokud váš účet schválí, obdržíte e-mail! awaiting_review_title: Vaše registrace se ověřuje clicking_this_link: kliknutím na tento odkaz login_link: přihlásit se @@ -1093,6 +1182,7 @@ cs: redirect_to_app_html: Měli byste být přesměrováni do aplikace %{app_name}. Pokud se tak nestalo, zkuste %{clicking_this_link} nebo ručně se vrátit do aplikace. registration_complete: Vaše registrace na %{domain} je hotová! welcome_title: Vítám uživatele %{name}! + wrong_email_hint: Pokud není tento e-mail správný, můžete si ho změnit v nastavení účtu. delete_account: Odstranit účet delete_account_html: Chcete-li odstranit svůj účet, pokračujte zde. Budete požádáni o potvrzení. description: @@ -1112,6 +1202,7 @@ cs: migrate_account_html: Zde můžete nastavit přesměrování tohoto účtu na jiný. or_log_in_with: Nebo se přihlaste pomocí progress: + confirm: Potvrdit e-mail details: Vaše údaje review: Naše hodnocení rules: Přijmout pravidla @@ -1133,22 +1224,37 @@ cs: security: Zabezpečení set_new_password: Nastavit nové heslo setup: + email_below_hint_html: Zkontrolujte složku se spamem, nebo požádejte o další. Svou e-mailovou adresu si můžete opravit, pokud je špatně. + email_settings_hint_html: Kliknutím na odkaz, který jsme poslali do %{email}, začnete používat Mastodon. Budeme tu čekat. link_not_received: Nedostali jste odkaz? + new_confirmation_instructions_sent: Za několik minut obdržíte nový e-mail s potvrzovacím odkazem! title: Zkontrolujte doručenou poštu sign_in: preamble_html: Přihlaste se svými %{domain} údaji. Pokud je váš účet hostován na jiném serveru, přihlásit se zde nemůžete. title: Přihlásit se k %{domain} sign_up: manual_review: Registrace na %{domain} procházejí manuálním hodnocením od našich moderátorů. Abyste nám pomohli zpracovat Vaši registraci, napište trochu o sobě a proč chcete účet na %{domain}. + preamble: S účtem na tomto Mastodon serveru budete moci sledovat jakoukoli jinou osobu na fediversu, bez ohledu na to, kde je jejich účet hostován. title: Pojďme vás nastavit na %{domain}. status: account_status: Stav účtu + confirming: Čekáme na dokončení potvrzení e-mailu. functional: Váš účet je plně funkční. + pending: Vaše žádost čeká na posouzení naším personálem. To může nějakou dobu trvat. Pokud bude váš požadavek schválen, obdržíte e-mail. redirecting_to: Váš účet je neaktivní, protože je právě přesměrován na účet %{acct}. self_destruct: Protože %{domain} končí, budete mít k účtu jen omezený přístup. view_strikes: Zobrazit minulé prohřešky vašeho účtu too_fast: Formulář byl odeslán příliš rychle, zkuste to znovu. use_security_key: Použít bezpečnostní klíč + user_agreement_html: Přečetl jsem si a souhlasím s podmínkami služby a ochranou osobních údajů + author_attribution: + example_title: Ukázkový text + hint_html: Píšete novinové články nebo blog mimo Mastodon? Kontrolujte, jak Vám bude připisováno autorství, když jsou sdíleny na Mastodonu. + instructions: 'Ujistěte se, že tento kód je v HTML vašeho článku:' + more_from_html: Více od %{name} + s_blog: Blog %{name} + then_instructions: Poté přidejte název domény publikace do níže uvedeného pole. + title: Připisování autorství challenge: confirm: Pokračovat hint_html: "Tip: Po dobu jedné hodiny vás o heslo nebudeme znovu žádat." @@ -1185,6 +1291,9 @@ cs: before: 'Před pokračováním si prosím pečlivě přečtěte tyto poznámky:' caches: Obsah, který byl uložen do cache jiných serverů, nemusí být smazán data_removal: Vaše příspěvky a další data budou trvale smazána + email_change_html: Můžete změnit svou e-mailovou adresu bez odstranění svého účtu + email_contact_html: Pokud stále nedorazí, můžete poslat e-mail %{email} pro pomoc + email_reconfirmation_html: Pokud neobdržíte potvrzovací e-mail, můžete si ho vyžádat znovu irreversible: Váš účet nebude možné obnovit ani znovu aktivovat more_details_html: Podrobnosti najdete v zásadách ochrany osobních údajů. username_available: Vaše uživatelské jméno bude opět dostupné @@ -1356,6 +1465,68 @@ cs: merge_long: Ponechat existující záznamy a přidat nové overwrite: Přepsat overwrite_long: Nahradit aktuální záznamy novými + overwrite_preambles: + blocking_html: + few: Chystáte se nahradit váš seznam bloků s %{count} účty z %{filename}. + many: Chystáte se nahradit váš seznam bloků s %{count} účty z %{filename}. + one: Chystáte se nahradit váš seznam bloků s %{count} účtem z %{filename}. + other: Chystáte se nahradit váš seznam bloků s %{count} účty z %{filename}. + bookmarks_html: + few: Chystáte se nahradit své záložky s %{count} příspěvky z %{filename}. + many: Chystáte se nahradit své záložky s %{count} příspěvky z %{filename}. + one: Chystáte se nahradit své záložky s %{count} příspěvkem z %{filename}. + other: Chystáte se nahradit své záložky s %{count} příspěvky z %{filename}. + domain_blocking_html: + few: Chystáte se nahradit Váš seznam zablokovaných domén s %{count} stránky z %{filename}. + many: Chystáte se nahradit Váš seznam zablokovaných domén s %{count} stránky z %{filename}. + one: Chystáte se nahradit Váš seznam zablokovaných domén s %{count} stránkou z %{filename}. + other: Chystáte se nahradit Váš seznam zablokovaných domén s %{count} stránky z %{filename}. + following_html: + few: Chystáte se sledovat%{count} účty z %{filename} a přestanete sledovat kohokoliv jiného. + many: Chystáte se sledovat%{count} účtů z %{filename} a přestanete sledovat kohokoliv jiného. + one: Chystáte se sledovat%{count} účet z %{filename} a přestanete sledovat kohokoliv jiného. + other: Chystáte se sledovat%{count} účtů z %{filename} a přestanete sledovat kohokoliv jiného. + lists_html: + few: Chystáte se nahradit své seznamy obsahem %{filename}. Až %{count} účty budou přidány do nových seznamů. + many: Chystáte se nahradit své seznamy obsahem %{filename}. Až %{count} účtů bude přidáno do nových seznamů. + one: Chystáte se nahradit své seznamy obsahem %{filename}. Až %{count} účet bude přidán do nových seznamů. + other: Chystáte se nahradit své seznamy obsahem %{filename}. Až %{count} účtů bude přidáno do nových seznamů. + muting_html: + few: Chystáte se nahradit svůj seznam ztišených účtů s %{count} účty z %{filename}. + many: Chystáte se nahradit svůj seznam ztišených účtů s %{count} účty z %{filename}. + one: Chystáte se nahradit svůj seznam ztišených účtů s %{count} účtem z %{filename}. + other: Chystáte se nahradit svůj seznam ztišených účtů s %{count} účty z %{filename}. + preambles: + blocking_html: + few: Chystáte se zablokovat%{count} účty z %{filename}. + many: Chystáte se zablokovat%{count} účtů z %{filename}. + one: Chystáte se zablokovat%{count} účet z %{filename}. + other: Chystáte se zablokovat%{count} účtů z %{filename}. + bookmarks_html: + few: Chystáte se přidat až %{count} příspěvky z %{filename} do vašich záložek. + many: Chystáte se přidat až %{count} příspěvků z %{filename} do vašich záložek. + one: Chystáte se přidat až %{count} příspěvek z %{filename} do vašich záložek. + other: Chystáte se přidat až %{count} příspěvků z %{filename} do vašich záložek. + domain_blocking_html: + few: Chystáte se zablokovat%{count} domény z %{filename}. + many: Chystáte se zablokovat%{count} domén z %{filename}. + one: Chystáte se zablokovat%{count} doménu z %{filename}. + other: Chystáte se zablokovat%{count} domén z %{filename}. + following_html: + few: Chystáte se sledovat%{count} účty z %{filename}. + many: Chystáte se sledovat%{count} účtů z %{filename}. + one: Chystáte se sledovat%{count} účet z %{filename}. + other: Chystáte se sledovat%{count} účtů z %{filename}. + lists_html: + few: Chystáte se přidat %{count} účty z %{filename} do vaších seznamů. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by je bylo možné přidat. + many: Chystáte se přidat %{count} účtů z %{filename} do vaších seznamů. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by je bylo možné přidat. + one: Chystáte se přidat %{count} účet z %{filename} do vaších seznamů. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by jej bylo možné přidat. + other: Chystáte se přidat %{count} účtů z %{filename} do vaších seznamů. Nové seznamy budou vytvořeny, pokud neexistuje žádný seznam, do kterého by je bylo možné přidat. + muting_html: + few: Chystáte se ztišit%{count} účty z %{filename}. + many: Chystáte se ztišit%{count} účtů z %{filename}. + one: Chystáte se ztišit%{count} účet z %{filename}. + other: Chystáte se ztišit%{count} účtů z %{filename}. preface: Můžete importovat data, která jste exportovali z jiného serveru, jako například seznam lidí, které sledujete či blokujete. recent_imports: Nedávné importy states: @@ -1417,6 +1588,7 @@ cs: authentication_methods: otp: aplikací pro dvoufaktorové ověření password: heslem + sign_in_token: bezpečnostní kód e-mailu webauthn: bezpečnostními klíči description_html: Pokud vidíte aktivitu, kterou nepoznáváte, zvažte změnu hesla a zapnutí dvoufaktorového ověřování. empty: Není k dispozici žádná historie přihlášení @@ -1427,10 +1599,21 @@ cs: unsubscribe: action: Ano, odeberte odběr complete: Odběr byl odhlášen + confirmation_html: Jste si jisti, že chcete odhlásit odběr %{type} pro Mastodon na %{domain} na váš e-mail %{email}? Vždy se můžete znovu přihlásit ve svém nastavení e-mailových oznámení. + emails: + notification_emails: + favourite: e-mailové oznámení při oblíbení + follow: e-mailové oznámení při sledování + follow_request: e-mail při žádost o sledování + mention: e-mailové oznámení při zmínění + reblog: e-mailové oznámení při boostu + resubscribe_html: Pokud jste se odhlásili omylem, můžete se znovu přihlásit ve svých nastavení e-mailových oznámení. + success_html: Již nebudete dostávat %{type} pro Mastodon na %{domain} na vaši e-mailovou adresu %{email}. title: Odhlásit odběr media_attachments: validations: images_and_video: K příspěvku, který již obsahuje obrázky, nelze připojit video + not_found: Média %{ids} nebyla nalezena nebo již byla připojena k jinému příspěvku not_ready: Nelze připojit soubory před jejich zpracováním. Zkuste to znovu za chvíli! too_many: Nelze připojit více než 4 soubory migrations: @@ -1507,6 +1690,8 @@ cs: update: subject: Uživatel %{name} upravil příspěvek notifications: + administration_emails: E-mailová oznámení administrátora + email_events: Události pro e-mailová oznámení email_events_hint: 'Vyberte události, pro které chcete dostávat oznámení:' number: human: @@ -1600,6 +1785,7 @@ cs: scheduled_statuses: over_daily_limit: Pro dnešek jste překročili limit %{limit} naplánovaných příspěvků over_total_limit: Překročili jste limit %{limit} naplánovaných příspěvků + too_soon: datum musí být v budoucnu self_destruct: lead_html: "%{domain} bohužel končí nadobro. Pokud jste tam měli účet, nebudete jej moci dále používat, ale stále si můžete vyžádat zálohu vašich dat." title: Tento server končí @@ -1659,10 +1845,12 @@ cs: delete: Smazání účtu development: Vývoj edit_profile: Upravit profil + export: Export featured_tags: Zvýrazněné hashtagy import: Import import_and_export: Import a export migrate: Přesun účtu + notifications: Emailové oznámení preferences: Předvolby profile: Profil relationships: Sledovaní a sledující @@ -1768,6 +1956,8 @@ cs: too_late: Na odvolání proti tomuto prohřešku už je pozdě tags: does_not_match_previous_name: se neshoduje s předchozím názvem + terms_of_service: + title: Podmínky služby themes: contrast: Mastodon (vysoký kontrast) default: Mastodon (tmavý) @@ -1828,6 +2018,15 @@ cs: further_actions_html: Pokud jste to nebyli vy, doporučujeme pro zabezpečení vašeho účtu okamžitě %{action} a zapnout dvoufázové ověřování. subject: Váš účet byl použit z nové IP adresy title: Nové přihlášení + terms_of_service_changed: + agreement: Pokračováním v používání %{domain} souhlasíte s těmito podmínkami. Pokud nesouhlasíte s aktualizovanými podmínkami, můžete svůj souhlas s %{domain} kdykoliv ukončit odstraněním vašeho účtu. + changelog: 'Ve zkratce, zde je to, co tato změna znamená pro vás:' + description: 'Tento e-mail jste obdrželi, protože na %{domain} provádíme určité změny našich smluvních podmínek. Doporučujeme vám zkontrolovat aktualizované podmínky v plném znění zde:' + description_html: Tento e-mail jste obdrželi, protože na %{domain} provádíme určité změny našich smluvních podmínek. Doporučujeme Vám zkontrolovat aktualizované termíny v plném znění zde. + sign_off: Tým %{domain} + subject: Aktualizace našich podmínek služby + subtitle: Podmínky služby %{domain} se mění + title: Důležitá aktualizace warning: appeal: Podat odvolání appeal_description: Pokud se domníváte, že se jedná o chybu, můžete podat odvolání personálu %{instance}. @@ -1908,6 +2107,7 @@ cs: invalid_otp_token: Neplatný kód pro dvoufázové ověřování otp_lost_help_html: Pokud jste ztratili přístup k oběma, spojte se s %{email} rate_limited: Příliš mnoho pokusů o ověření, zkuste to znovu později. + seamless_external_login: Jste přihlášeni přes externí službu, nastavení hesla a e-mailu proto nejsou dostupná. signed_in_as: 'Přihlášeni jako:' verification: extra_instructions_html: Tip: Odkaz na vaší webové stránce může být neviditelný. Důležitou součástí je rel="me", která brání proti napodování vás na webových stránkách s obsahem vytvořeným uživatelem. Můžete dokonce použít odkaz v záhlaví stránky místo a, ale HTML musí být přístupné bez spuštění JavaScript. @@ -1916,6 +2116,7 @@ cs: instructions_html: Zkopírujte a vložte níže uvedený kód do HTML vašeho webu. Poté přidejte adresu vašeho webu do jednoho z extra políček na vašem profilu na záložce "Upravit profil" a uložte změny. verification: Ověření verified_links: Vaše ověřené odkazy + website_verification: Ověření webové stránky webauthn_credentials: add: Přidat nový bezpečnostní klíč create: diff --git a/config/locales/eo.yml b/config/locales/eo.yml index c456a424dc5581..ee45921aaa6379 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -937,9 +937,13 @@ eo: generates: action: Generi title: Agordo de kondiĉoj de uzado + history: Historio + live: Antaŭmontro no_history: Ankoraŭ ne estas registritaj ŝanĝoj de la kondiĉoj de la servo. no_terms_of_service_html: Vi nuntempe ne havas iujn ajn kondiĉojn de la servo agordita. La kondiĉoj de la servo celas doni klarecon kaj protekti vin kontraŭ eblaj respondecoj en disputoj kun viaj uzantoj. + notify_users: Informu uzantojn preview: + send_preview: Sendu antaŭrigardon al %{email} send_to_all: one: Sendi %{display_count} retpoŝton other: Sendi %{display_count} retpoŝtojn @@ -1926,6 +1930,8 @@ eo: subject: Via konto estas alirita de nova IP-adreso title: Nova saluto terms_of_service_changed: + description: 'Vi ricevas ĉi tiun retmesaĝon ĉar ni faras iujn ŝanĝojn al niaj servokondiĉoj ĉe %{domain}. Ni instigas vin revizii la ĝisdatigitajn kondiĉojn tute ĉi tie:' + description_html: Vi ricevas ĉi tiun retmesaĝon ĉar ni faras iujn ŝanĝojn al niaj servokondiĉoj ĉe %{domain}. Ni instigas vin revizii la ĝisdatigitajn kondiĉojn plene ĉi tie. sign_off: La teamo de %{domain} subject: Ĝisdatigoj al niaj kondiĉoj de servo subtitle: La kondiĉoj de la servo de %{domain} ŝanĝiĝas diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 0110fe540dd07b..83a9674f843623 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -956,6 +956,8 @@ pl: updated_msg: Pomyślnie uaktualniono ustawienia hashtagów terms_of_service: draft: Szkic + generate: Użyj szablonu + save_draft: Zapisz wersję roboczą title: Administracja trends: allow: Zezwól @@ -1214,6 +1216,7 @@ pl: view_strikes: Zobacz dawne ostrzeżenia nałożone na twoje konto too_fast: Zbyt szybko przesłano formularz, spróbuj ponownie. use_security_key: Użyj klucza bezpieczeństwa + user_agreement_html: Przeczytałem i akceptuję warunki korzystania z usługi oraz politykę prywatności author_attribution: example_title: Przykładowy tekst hint_html: Piszesz wiadomości albo bloga poza Mastodonem? Kontroluj jak będą ci przypisywane podczas dizielenia się nimi na Mastodonie. diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index bba71f165d9838..7dcf0977f35f58 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -352,7 +352,7 @@ pt-PT: not_permitted: Não estás autorizado a executar esta ação overwrite: Substituir shortcode: Código de atalho - shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e traços inferiores + shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e traços inferiores (_) title: Emojis personalizados uncategorized: Não categorizados unlist: Não listar @@ -1826,8 +1826,8 @@ pt-PT: private_long: Mostrar só aos seguidores public: Público public_long: Todos podem ver - unlisted: Não inventariado - unlisted_long: Todos podem ver, mas não será inventariado nas cronologias públicas + unlisted: Não listado + unlisted_long: Todos podem ver, mas não aparecerá nas cronologias públicas statuses_cleanup: enabled: Eliminar publicações antigas automaticamente enabled_hint: Elimina automaticamente as tuas publicações assim que atingirem um certo limite de tempo, a não ser que correspondam a uma das seguintes exceções @@ -1945,7 +1945,7 @@ pt-PT: appeal: Submeter uma contestação appeal_description: Se achas que isto é um erro, podes submeter uma contestação para a equipa de %{instance}. categories: - spam: Spam + spam: Publicidade indesejada / spam violation: O conteúdo infringe as seguintes diretrizes da comunidade explanation: delete_statuses: Algumas das tuas mensagens foram consideradas como violando uma ou mais diretrizes da comunidade e foram subsequentemente removidas pelos moderadores do %{instance}. diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index 0bfeacd9cd935a..22b92a44346d96 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -3,12 +3,14 @@ cs: simple_form: hints: account: + attribution_domains_as_text: Jeden na řádek. Chrání před falešným připisování autorství. discoverable: Vaše veřejné příspěvky a profil mohou být zobrazeny nebo doporučeny v různých oblastech Mastodonu a váš profil může být navrhován ostatním uživatelům. display_name: Vaše celé jméno nebo přezdívka. fields: Vaše domovská stránka, zájmena, věk, cokoliv chcete. indexable: Vaše veřejné příspěvky se mohou objevit ve výsledcích vyhledávání na Mastodonu. Lidé, kteří s vašimi příspěvky interagovaly, je mohou stále vyhledávat. note: 'Můžete @zmínit jiné osoby nebo #hashtagy.' show_collections: Lidé budou moci procházet skrz sledující. Lidé, které sledujete, uvidí, že je sledujete bezohledně. + unlocked: Lidé vás budou moci sledovat, aniž by vás žádali o schválení. Zrušte zaškrtnutí, pokud chcete kontrolovat požadavky o sledování a vybírat si, zda přijmete nebo odmítnete nové sledující. account_alias: acct: Zadejte svůj účet, ze kterého se chcete přesunout jinam, ve formátu přezdívka@doména account_migration: @@ -58,6 +60,7 @@ cs: setting_display_media_default: Skrývat média označená jako citlivá setting_display_media_hide_all: Vždy skrývat média setting_display_media_show_all: Vždy zobrazovat média + setting_system_scrollbars_ui: Platí pouze pro desktopové prohlížeče založené na Safari nebo Chrome setting_use_blurhash: Gradienty jsou vytvořeny na základě barvev skrytých médií, ale zakrývají veškeré detaily setting_use_pending_items: Aktualizovat časovou osu až po kliknutí namísto automatického rolování kanálu username: Pouze písmena, číslice a podtržítka @@ -130,8 +133,17 @@ cs: terms_of_service: changelog: Může být strukturováno pomocí Markdown syntaxu. text: Může být strukturováno pomocí Markdown syntaxu. + terms_of_service_generator: + admin_email: Právní oznámení zahrnují protioznámení, soudní příkazy, žádosti o stáhnutí příspěvků a žádosti od právních vymahačů. + arbitration_address: Může být stejné jako výše uvedená fyzická adresa, nebo „N/A“, pokud používáte e-mail + arbitration_website: Může být webový formulář nebo „N/A“, pokud používáte e-mail + dmca_address: V případě provozovatelů v USA použijte adresu zapsanou v DMCA Designated Agent Directory. Na přímou žádost je možné použít namísto domovské adresy PO box, použijte DMCA Designated Agent Post Office Box Waiver Request k e-mailovaní Copyright Office a řekněte jim, že jste malým moderátorem obsahu, který se obavá pomsty nabo odplaty za Vaši práci a potřebujete použít PO box, abyste schovali Vaši domovskou adresu před širokou veřejností. + dmca_email: Může být stejný e-mail použitý pro „E-mail adresy pro právní upozornění“ + domain: Jedinečná identifikace online služby, kterou poskytujete. + jurisdiction: Uveďte zemi, kde žije ten, kdo platí účty. Pokud je to společnost nebo jiný subjekt, uveďte zemi, v níž je zapsán do obchodního rejstříku, a město, region, území, případně stát, pokud je to povinné. user: chosen_languages: Po zaškrtnutí budou ve veřejných časových osách zobrazeny pouze příspěvky ve zvolených jazycích + role: Role určuje, která oprávnění uživatel má. user_role: color: Barva, která má být použita pro roli v celém UI, jako RGB v hex formátu highlighted: Toto roli učiní veřejně viditelnou @@ -144,6 +156,7 @@ cs: url: Kam budou události odesílány labels: account: + attribution_domains_as_text: Webové stránky s povolením Vám připsat autorství discoverable: Zobrazovat profil a příspěvky ve vyhledávacích algoritmech fields: name: Označení @@ -222,6 +235,7 @@ cs: setting_hide_network: Skrýt mou síť setting_reduce_motion: Omezit pohyb v animacích setting_system_font_ui: Použít výchozí písmo systému + setting_system_scrollbars_ui: Použít výchozí posuvník systému setting_theme: Vzhled stránky setting_trends: Zobrazit dnešní trendy setting_unfollow_modal: Před zrušením sledování zobrazovat potvrzovací okno diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml index be2ed2135efdb8..59fad146d4cfd5 100644 --- a/config/locales/simple_form.eo.yml +++ b/config/locales/simple_form.eo.yml @@ -140,6 +140,7 @@ eo: dmca_address: Por tenantoj en Usono, uzu la adreson registritan en la DMCA Designated Agenŭ Directory. Registrolibro de poŝtskatoloj haveblas per direkta postulo, uzu la DMCA Designated Agent Post Office Box Waiver Request por retpoŝti la Ofico de Kopirajto kaj priskribu, ke vi estas hejm-trovigita administranto por enhavo kaj devas uzi Poŝtskatolon por forigi vian hejmadreson de publika vido. dmca_email: Povas esti la sama retpoŝtadreso uzita por "Retpoŝtadreso por legalaj sciigoj" supre domain: Unika identigilo de la retaj servicoj, ke vi provizas. + jurisdiction: Enlistigu la landon, kie loĝas kiu pagas la fakturojn. Se ĝi estas kompanio aŭ alia ento, listigu la landon, kie ĝi estas enkorpigita, kaj la urbon, regionon, teritorion aŭ ŝtaton laŭeble. user: chosen_languages: Kun tio markita nur mesaĝoj en elektitaj lingvoj aperos en publikaj tempolinioj role: La rolo kontrolas kiujn permesojn la uzanto havas. @@ -333,7 +334,13 @@ eo: changelog: Kio ŝanĝiĝis? text: Kondiĉoj de uzado terms_of_service_generator: + admin_email: Retpoŝtadreso por laŭleĝaj avizoj + arbitration_address: Fizika adreso por arbitraciaj avizoj + arbitration_website: Retejo por sendi arbitraciajn avizojn + dmca_address: Fizika adreso por DMCA/kopirajto-avizoj + dmca_email: Retpoŝtadreso por DMCA/kopirajto-avizoj domain: Domajno + jurisdiction: Laŭleĝa jurisdikcio user: role: Rolo time_zone: Horzono diff --git a/config/locales/simple_form.pt-PT.yml b/config/locales/simple_form.pt-PT.yml index f5a7e3a27ee884..207d77f38b9f4a 100644 --- a/config/locales/simple_form.pt-PT.yml +++ b/config/locales/simple_form.pt-PT.yml @@ -63,7 +63,7 @@ pt-PT: setting_system_scrollbars_ui: Aplica-se apenas a navegadores de desktop baseados no Safari e Chrome setting_use_blurhash: Os gradientes são baseados nas cores das imagens escondidas, mas ofuscam quaisquer pormenores setting_use_pending_items: Ocultar as atualizações da cronologia após um clique em vez de percorrer automaticamente a cronologia - username: Pode utilizar letras, números e sublinhados + username: Pode utilizar letras, números e traços inferiores (_) whole_word: Quando a palavra-chave ou expressão-chave é somente alfanumérica, ela só será aplicada se corresponder à palavra completa domain_allow: domain: Este domínio será capaz de obter dados desta instância e os dados dele recebidos serão processados e armazenados From d517fa5ab7979bda3a316b62563b244d6572fbd9 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 13 Jan 2025 10:39:05 +0100 Subject: [PATCH 33/73] Change ActivityPub path generation to all happen in `ActivityPub::TagManager` (#33527) --- .../activitypub/collections_controller.rb | 2 +- .../activitypub/outboxes_controller.rb | 8 +--- .../follower_accounts_controller.rb | 2 +- app/lib/activitypub/tag_manager.rb | 34 +++++++++++++-- .../activitypub/actor_serializer.rb | 14 +++---- app/serializers/activitypub/add_serializer.rb | 2 +- .../activitypub/remove_serializer.rb | 2 +- app/serializers/webfinger_serializer.rb | 4 +- spec/lib/activitypub/tag_manager_spec.rb | 41 +++++++++++++++++-- 9 files changed, 82 insertions(+), 27 deletions(-) diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index ab1b98e646a1f6..c80db3500ded77 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -49,7 +49,7 @@ def set_type def collection_presenter ActivityPub::CollectionPresenter.new( - id: account_collection_url(@account, params[:id]), + id: ActivityPub::TagManager.instance.collection_uri_for(@account, params[:id]), type: @type, size: @size, items: @items diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 0c995edbf87277..a9476b806f54e1 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -41,12 +41,8 @@ def outbox_presenter end end - def outbox_url(**) - if params[:account_username].present? - account_outbox_url(@account, **) - else - instance_actor_outbox_url(**) - end + def outbox_url(...) + ActivityPub::TagManager.instance.outbox_uri_for(@account, ...) end def next_page diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index 5effd9495e3583..f4c7b37088a562 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -46,7 +46,7 @@ def page_requested? end def page_url(page) - account_followers_url(@account, page: page) unless page.nil? + ActivityPub::TagManager.instance.followers_uri_for(@account, page: page) unless page.nil? end def next_page_url diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index a4899284075393..5cba9a8006fa68 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -86,8 +86,34 @@ def shares_uri_for(target) account_status_shares_url(target.account, target) end - def followers_uri_for(target) - target.local? ? account_followers_url(target) : target.followers_url.presence + def following_uri_for(target, ...) + raise ArgumentError, 'target must be a local account' unless target.local? + + account_following_index_url(target, ...) + end + + def followers_uri_for(target, ...) + return target.followers_url.presence unless target.local? + + account_followers_url(target, ...) + end + + def collection_uri_for(target, ...) + raise NotImplementedError unless target.local? + + account_collection_url(target, ...) + end + + def inbox_uri_for(target) + raise NotImplementedError unless target.local? + + target.instance_actor? ? instance_actor_inbox_url : account_inbox_url(target) + end + + def outbox_uri_for(target, ...) + raise NotImplementedError unless target.local? + + target.instance_actor? ? instance_actor_outbox_url(...) : account_outbox_url(target, ...) end # Primary audience of a status @@ -99,7 +125,7 @@ def to(status) when 'public' [COLLECTIONS[:public]] when 'unlisted', 'private' - [account_followers_url(status.account)] + [followers_uri_for(status.account)] when 'direct', 'limited' if status.account.silenced? # Only notify followers if the account is locally silenced @@ -133,7 +159,7 @@ def cc(status) case status.visibility when 'public' - cc << account_followers_url(status.account) + cc << followers_uri_for(status.account) when 'unlisted' cc << COLLECTIONS[:public] end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index f698e758e8d6bb..ed90fa428f9668 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -44,7 +44,7 @@ def shared_inbox delegate :suspended?, :instance_actor?, to: :object def id - object.instance_actor? ? instance_actor_url : account_url(object) + ActivityPub::TagManager.instance.uri_for(object) end def type @@ -60,27 +60,27 @@ def type end def following - account_following_index_url(object) + ActivityPub::TagManager.instance.following_uri_for(object) end def followers - account_followers_url(object) + ActivityPub::TagManager.instance.followers_uri_for(object) end def inbox - object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object) + ActivityPub::TagManager.instance.inbox_uri_for(object) end def outbox - object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object) + ActivityPub::TagManager.instance.outbox_uri_for(object) end def featured - account_collection_url(object, :featured) + ActivityPub::TagManager.instance.collection_uri_for(object, :featured) end def featured_tags - account_collection_url(object, :tags) + ActivityPub::TagManager.instance.collection_uri_for(object, :tags) end def endpoints diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb index 436b05086f26c0..640d7742726d3f 100644 --- a/app/serializers/activitypub/add_serializer.rb +++ b/app/serializers/activitypub/add_serializer.rb @@ -38,6 +38,6 @@ def proper_object end def target - account_collection_url(object.account, :featured) + ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured) end end diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb index fb224f8a999951..4f78804031c330 100644 --- a/app/serializers/activitypub/remove_serializer.rb +++ b/app/serializers/activitypub/remove_serializer.rb @@ -38,6 +38,6 @@ def proper_object end def target - account_collection_url(object.account, :featured) + ActivityPub::TagManager.instance.collection_uri_for(object.account, :featured) end end diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb index b67cd2771a2372..1cb3175c079d3e 100644 --- a/app/serializers/webfinger_serializer.rb +++ b/app/serializers/webfinger_serializer.rb @@ -13,7 +13,7 @@ def aliases if object.instance_actor? [instance_actor_url] else - [short_account_url(object), account_url(object)] + [short_account_url(object), ActivityPub::TagManager.instance.uri_for(object)] end end @@ -43,6 +43,6 @@ def profile_page_href end def self_href - object.instance_actor? ? instance_actor_url : account_url(object) + ActivityPub::TagManager.instance.uri_for(object) end end diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb index 05d2609b0dad0e..4dcfc698241021 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -7,17 +7,50 @@ subject { described_class.instance } + let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" } + + describe '#public_collection?' do + it 'returns true for the special public collection and common shorthands' do + expect(subject.public_collection?('https://www.w3.org/ns/activitystreams#Public')).to be true + expect(subject.public_collection?('as:Public')).to be true + expect(subject.public_collection?('Public')).to be true + end + + it 'returns false for other URIs' do + expect(subject.public_collection?('https://example.com/foo/bar')).to be false + end + end + describe '#url_for' do - it 'returns a string' do + it 'returns a string starting with web domain' do account = Fabricate(:account) - expect(subject.url_for(account)).to be_a String + expect(subject.url_for(account)).to be_a(String) + .and start_with(domain) end end describe '#uri_for' do - it 'returns a string' do + it 'returns a string starting with web domain' do account = Fabricate(:account) - expect(subject.uri_for(account)).to be_a String + expect(subject.uri_for(account)).to be_a(String) + .and start_with(domain) + end + end + + describe '#activity_uri_for' do + context 'when given an account' do + it 'raises an exception' do + account = Fabricate(:account) + expect { subject.activity_uri_for(account) }.to raise_error(ArgumentError) + end + end + + context 'when given a local activity' do + it 'returns a string starting with web domain' do + status = Fabricate(:status) + expect(subject.uri_for(status)).to be_a(String) + .and start_with(domain) + end end end From 50449ae7ac57a12d81e0a3e2a6d6197af578e675 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 13 Jan 2025 12:48:47 +0100 Subject: [PATCH 34/73] Fix media preview height in compose form when 3 or more images are attached (#33571) --- app/javascript/styles/mastodon/components.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index d21175595dd3d3..072e7cb4a29346 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6815,6 +6815,8 @@ a.status-card { } &--layout-3 { + min-height: calc(64px * 2 + 8px); + & > .media-gallery__item:nth-child(1) { border-end-end-radius: 0; border-start-end-radius: 0; @@ -6834,6 +6836,8 @@ a.status-card { } &--layout-4 { + min-height: calc(64px * 2 + 8px); + & > .media-gallery__item:nth-child(1) { border-end-end-radius: 0; border-start-end-radius: 0; From 68d818121d6b0252084e97ec4e3a7229510e620d Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 13 Jan 2025 13:46:32 +0100 Subject: [PATCH 35/73] Switch `webpush` dependency to latest version of Mastodon-maintained fork (#33572) --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- app/lib/web_push_request.rb | 2 +- app/validators/web_push_key_validator.rb | 2 +- spec/workers/web/push_notification_worker_spec.rb | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 8df00d8c0a5fe8..f112e5ea5fc17e 100644 --- a/Gemfile +++ b/Gemfile @@ -94,7 +94,7 @@ gem 'twitter-text', '~> 3.1.0' gem 'tzinfo-data', '~> 1.2023' gem 'webauthn', '~> 3.0' gem 'webpacker', '~> 5.4' -gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9' +gem 'webpush', github: 'mastodon/webpush', ref: '52725def8baf67e0d645c9d1c6c0bdff69da0c60' gem 'json-ld' gem 'json-ld-preloaded', '~> 3.2' diff --git a/Gemfile.lock b/Gemfile.lock index 4603530de05543..596ed7bf52186b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT - remote: https://github.com/ClearlyClaire/webpush.git - revision: f14a4d52e201128b1b00245d11b6de80d6cfdcd9 - ref: f14a4d52e201128b1b00245d11b6de80d6cfdcd9 + remote: https://github.com/mastodon/webpush.git + revision: 52725def8baf67e0d645c9d1c6c0bdff69da0c60 + ref: 52725def8baf67e0d645c9d1c6c0bdff69da0c60 specs: - webpush (0.3.8) + webpush (1.1.0) hkdf (~> 0.2) jwt (~> 2.0) diff --git a/app/lib/web_push_request.rb b/app/lib/web_push_request.rb index a43e22480e12a2..91227ed46029fa 100644 --- a/app/lib/web_push_request.rb +++ b/app/lib/web_push_request.rb @@ -33,7 +33,7 @@ def crypto_key_header end def encrypt(payload) - Webpush::Encryption.encrypt(payload, key_p256dh, key_auth) + Webpush::Legacy::Encryption.encrypt(payload, key_p256dh, key_auth) end private diff --git a/app/validators/web_push_key_validator.rb b/app/validators/web_push_key_validator.rb index a8ad5c9c6bbaea..25914d59ebc5b8 100644 --- a/app/validators/web_push_key_validator.rb +++ b/app/validators/web_push_key_validator.rb @@ -3,7 +3,7 @@ class WebPushKeyValidator < ActiveModel::Validator def validate(subscription) begin - Webpush::Encryption.encrypt('validation_test', subscription.key_p256dh, subscription.key_auth) + Webpush::Legacy::Encryption.encrypt('validation_test', subscription.key_p256dh, subscription.key_auth) rescue ArgumentError, OpenSSL::PKey::EC::Point::Error subscription.errors.add(:base, I18n.t('crypto.errors.invalid_key')) end diff --git a/spec/workers/web/push_notification_worker_spec.rb b/spec/workers/web/push_notification_worker_spec.rb index 4993d467b36752..88d88f2f3deb3b 100644 --- a/spec/workers/web/push_notification_worker_spec.rb +++ b/spec/workers/web/push_notification_worker_spec.rb @@ -35,7 +35,7 @@ before do Setting.site_contact_email = contact_email - allow(Webpush::Encryption).to receive(:encrypt).and_return(payload) + allow(Webpush::Legacy::Encryption).to receive(:encrypt).and_return(payload) allow(JWT).to receive(:encode).and_return('jwt.encoded.payload') stub_request(:post, endpoint).to_return(status: 201, body: '') From 3c4a83fc6248df47d76cba944dcaac765198e6b4 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 13 Jan 2025 07:58:53 -0500 Subject: [PATCH 36/73] Remove unused `LanguagePresenter#native_name` (#33551) --- app/presenters/language_presenter.rb | 6 +----- spec/requests/api/v1/instances/languages_spec.rb | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/presenters/language_presenter.rb b/app/presenters/language_presenter.rb index 69ea991d54b2f6..717d70fdbc9b5c 100644 --- a/app/presenters/language_presenter.rb +++ b/app/presenters/language_presenter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class LanguagePresenter < ActiveModelSerializers::Model - attributes :code, :name, :native_name + attributes :code, :name def initialize(code) super() @@ -13,8 +13,4 @@ def initialize(code) def name @item[0] end - - def native_name - @item[1] - end end diff --git a/spec/requests/api/v1/instances/languages_spec.rb b/spec/requests/api/v1/instances/languages_spec.rb index 3d188d23c05bde..0b325bbb6ddf99 100644 --- a/spec/requests/api/v1/instances/languages_spec.rb +++ b/spec/requests/api/v1/instances/languages_spec.rb @@ -9,10 +9,21 @@ end it 'returns http success and includes supported languages' do - expect(response).to have_http_status(200) + expect(response) + .to have_http_status(200) expect(response.content_type) .to start_with('application/json') - expect(response.parsed_body.pluck(:code)).to match_array LanguagesHelper::SUPPORTED_LOCALES.keys.map(&:to_s) + expect(response.parsed_body) + .to match_array(supported_locale_expectations) + end + + def supported_locale_expectations + LanguagesHelper::SUPPORTED_LOCALES.map do |key, values| + include( + code: key.to_s, + name: values.first + ) + end end end end From 3a762cddf6311db95c4bd1b674a7bbaccb446152 Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Mon, 13 Jan 2025 14:35:16 +0100 Subject: [PATCH 37/73] Reject announce payload if object is nil (#33570) --- app/lib/activitypub/activity/announce.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 9dcafff3abc71a..b57dca9ad75f4a 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -3,6 +3,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity def perform return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity? + return reject_payload! if @object.nil? with_redis_lock("announce:#{value_or_id(@object)}") do original_status = status_from_object From 0db75588220719d5f812b33a3e45970118c6acb9 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 13 Jan 2025 10:50:58 -0300 Subject: [PATCH 38/73] Fix HTTP 500 on `POST /api/v1/admin/ip_blocks` (#29308) --- app/models/ip_block.rb | 2 +- spec/models/ip_block_spec.rb | 2 ++ spec/requests/api/v1/admin/ip_blocks_spec.rb | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/ip_block.rb b/app/models/ip_block.rb index 416ae38382722c..4c95ac38dea2ac 100644 --- a/app/models/ip_block.rb +++ b/app/models/ip_block.rb @@ -24,7 +24,7 @@ class IpBlock < ApplicationRecord sign_up_requires_approval: 5000, sign_up_block: 5500, no_access: 9999, - }, prefix: true + }, prefix: true, validate: true validates :ip, :severity, presence: true validates :ip, uniqueness: true diff --git a/spec/models/ip_block_spec.rb b/spec/models/ip_block_spec.rb index 93ee72423b4ac5..856d55be9d8b94 100644 --- a/spec/models/ip_block_spec.rb +++ b/spec/models/ip_block_spec.rb @@ -12,6 +12,8 @@ it { is_expected.to validate_presence_of(:severity) } it { is_expected.to validate_uniqueness_of(:ip) } + + it { is_expected.to allow_values(:sign_up_requires_approval, :sign_up_block, :no_access).for(:severity) } end describe '#to_log_human_identifier' do diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb index aa3db339154fa0..59ef8d29665b00 100644 --- a/spec/requests/api/v1/admin/ip_blocks_spec.rb +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -187,6 +187,16 @@ .to start_with('application/json') end end + + context 'when the given severity is invalid' do + let(:params) { { ip: '151.0.32.55', severity: 'invalid' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end end describe 'PUT /api/v1/admin/ip_blocks/:id' do From f9451c5614061bee2f3e08bc3e76b641f327bc70 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 13 Jan 2025 09:27:30 -0500 Subject: [PATCH 39/73] Fix issue with trending order when user has chosen languages (#33557) --- app/models/trends/links.rb | 6 +++--- app/models/trends/query.rb | 11 +++++++---- app/models/trends/statuses.rb | 6 +++--- app/models/trends/tags.rb | 6 +++--- spec/models/trends/links_spec.rb | 14 ++++++++++++++ spec/models/trends/statuses_spec.rb | 14 ++++++++++++++ spec/models/trends/tags_spec.rb | 14 ++++++++++++++ 7 files changed, 58 insertions(+), 13 deletions(-) diff --git a/app/models/trends/links.rb b/app/models/trends/links.rb index 57ad486631c69d..35ccf7744c3a6c 100644 --- a/app/models/trends/links.rb +++ b/app/models/trends/links.rb @@ -16,7 +16,7 @@ class Trends::Links < Trends::Base class Query < Trends::Query def to_arel scope = PreviewCard.joins(:trend).reorder(score: :desc) - scope = scope.merge(language_order_clause) if preferred_languages.present? + scope = scope.reorder(language_order_clause, score: :desc) if preferred_languages.present? scope = scope.merge(PreviewCardTrend.allowed) if @allowed scope = scope.offset(@offset) if @offset.present? scope = scope.limit(@limit) if @limit.present? @@ -25,8 +25,8 @@ def to_arel private - def language_order_clause - language_order_for(PreviewCardTrend) + def trend_class + PreviewCardTrend end end diff --git a/app/models/trends/query.rb b/app/models/trends/query.rb index abed64042ec16a..670390ae3cf174 100644 --- a/app/models/trends/query.rb +++ b/app/models/trends/query.rb @@ -94,11 +94,14 @@ def perform_queries to_arel.to_a end - def language_order_for(trend_class) + def language_order_clause + Arel::Nodes::Case.new.when(language_is_preferred).then(1).else(0).desc + end + + def language_is_preferred trend_class - .reorder(nil) - .in_order_of(:language, [preferred_languages], filter: false) - .order(score: :desc) + .arel_table[:language] + .in(preferred_languages) end def preferred_languages diff --git a/app/models/trends/statuses.rb b/app/models/trends/statuses.rb index 9c47dd486b9d12..1a41eb9e9a37df 100644 --- a/app/models/trends/statuses.rb +++ b/app/models/trends/statuses.rb @@ -15,7 +15,7 @@ class Trends::Statuses < Trends::Base class Query < Trends::Query def to_arel scope = Status.joins(:trend).reorder(score: :desc) - scope = scope.merge(language_order_clause) if preferred_languages.present? + scope = scope.reorder(language_order_clause, score: :desc) if preferred_languages.present? scope = scope.merge(StatusTrend.allowed) if @allowed scope = scope.not_excluded_by_account(@account).not_domain_blocked_by_account(@account) if @account.present? scope = scope.offset(@offset) if @offset.present? @@ -25,8 +25,8 @@ def to_arel private - def language_order_clause - language_order_for(StatusTrend) + def trend_class + StatusTrend end end diff --git a/app/models/trends/tags.rb b/app/models/trends/tags.rb index 84e8dde11a5fc1..63897f4f9301da 100644 --- a/app/models/trends/tags.rb +++ b/app/models/trends/tags.rb @@ -16,7 +16,7 @@ class Trends::Tags < Trends::Base class Query < Trends::Query def to_arel scope = Tag.joins(:trend).reorder(score: :desc) - scope = scope.merge(language_order_clause) if preferred_languages.present? + scope = scope.reorder(language_order_clause, score: :desc) if preferred_languages.present? scope = scope.merge(TagTrend.allowed) if @allowed scope = scope.offset(@offset) if @offset.present? scope = scope.limit(@limit) if @limit.present? @@ -25,8 +25,8 @@ def to_arel private - def language_order_clause - language_order_for(TagTrend) + def trend_class + TagTrend end end diff --git a/spec/models/trends/links_spec.rb b/spec/models/trends/links_spec.rb index b0d41d4613abff..81a4270c3861ca 100644 --- a/spec/models/trends/links_spec.rb +++ b/spec/models/trends/links_spec.rb @@ -24,6 +24,20 @@ .to eq([lower_score.preview_card, higher_score.preview_card]) end end + + context 'when account has chosen languages' do + let!(:lang_match_higher_score) { Fabricate :preview_card_trend, score: 10, language: 'is' } + let!(:lang_match_lower_score) { Fabricate :preview_card_trend, score: 1, language: 'da' } + let(:user) { Fabricate :user, chosen_languages: %w(da is) } + let(:account) { Fabricate :account, user: user } + + before { subject.filtered_for!(account) } + + it 'returns results' do + expect(subject.records) + .to eq([lang_match_higher_score.preview_card, lang_match_lower_score.preview_card, higher_score.preview_card, lower_score.preview_card]) + end + end end end end diff --git a/spec/models/trends/statuses_spec.rb b/spec/models/trends/statuses_spec.rb index abb1535d04a14a..398390104272f7 100644 --- a/spec/models/trends/statuses_spec.rb +++ b/spec/models/trends/statuses_spec.rb @@ -66,6 +66,20 @@ .to eq([lower_score.status, higher_score.status]) end end + + context 'when account has chosen languages' do + let!(:lang_match_higher_score) { Fabricate :status_trend, score: 10, language: 'is' } + let!(:lang_match_lower_score) { Fabricate :status_trend, score: 1, language: 'da' } + let(:user) { Fabricate :user, chosen_languages: %w(da is) } + let(:account) { Fabricate :account, user: user } + + before { subject.filtered_for!(account) } + + it 'returns results' do + expect(subject.records) + .to eq([lang_match_higher_score.status, lang_match_lower_score.status, higher_score.status, lower_score.status]) + end + end end end end diff --git a/spec/models/trends/tags_spec.rb b/spec/models/trends/tags_spec.rb index 8f36b4a50d343d..dacae602acb0ab 100644 --- a/spec/models/trends/tags_spec.rb +++ b/spec/models/trends/tags_spec.rb @@ -50,6 +50,20 @@ .to eq([lower_score.tag, higher_score.tag]) end end + + context 'when account has chosen languages' do + let!(:lang_match_higher_score) { Fabricate :tag_trend, score: 10, language: 'is' } + let!(:lang_match_lower_score) { Fabricate :tag_trend, score: 1, language: 'da' } + let(:user) { Fabricate :user, chosen_languages: %w(da is) } + let(:account) { Fabricate :account, user: user } + + before { subject.filtered_for!(account) } + + it 'returns results' do + expect(subject.records) + .to eq([lang_match_higher_score.tag, lang_match_lower_score.tag, higher_score.tag, lower_score.tag]) + end + end end end end From 77a44e61a84dedc293af5ea702f2cd8cb32240ac Mon Sep 17 00:00:00 2001 From: Wolfgang Date: Mon, 13 Jan 2025 17:05:24 +0100 Subject: [PATCH 40/73] Add enum validation to `DomainBlock#severity` (#29158) --- app/models/domain_block.rb | 2 +- spec/requests/api/v1/admin/domain_blocks_spec.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 2853f6457a9b44..8e7d7b6afc5ee0 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -21,7 +21,7 @@ class DomainBlock < ApplicationRecord include DomainNormalizable include DomainMaterializable - enum :severity, { silence: 0, suspend: 1, noop: 2 } + enum :severity, { silence: 0, suspend: 1, noop: 2 }, validate: true validates :domain, presence: true, uniqueness: true, domain: true diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb index 029de72fd7fd5a..0b01d04f9a0fdb 100644 --- a/spec/requests/api/v1/admin/domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb @@ -217,6 +217,19 @@ .to start_with('application/json') end end + + context 'when severity is invalid' do + let(:params) { { domain: 'bar.com', severity: :bar } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:error]).to eq('Validation failed: Severity is not included in the list') + end + end end describe 'PUT /api/v1/admin/domain_blocks/:id' do From 74da9e9281d549bc59453be71bf386d74946bdb7 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Tue, 14 Jan 2025 17:24:00 +0900 Subject: [PATCH 41/73] Fix custom css cache miss (#33583) --- app/helpers/theme_helper.rb | 4 +++- spec/helpers/theme_helper_spec.rb | 25 ++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index 5dfb4a5184c200..cda380b3bc074f 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -45,7 +45,9 @@ def active_custom_stylesheet end def cached_custom_css_digest - Rails.cache.read(:setting_digest_custom_css) + Rails.cache.fetch(:setting_digest_custom_css) do + Setting.custom_css&.then { |content| Digest::SHA256.hexdigest(content) } + end end def theme_color_for(theme) diff --git a/spec/helpers/theme_helper_spec.rb b/spec/helpers/theme_helper_spec.rb index c811b7c9811482..51611b82112232 100644 --- a/spec/helpers/theme_helper_spec.rb +++ b/spec/helpers/theme_helper_spec.rb @@ -80,18 +80,37 @@ end describe '#custom_stylesheet' do + let(:custom_css) { 'body {}' } + let(:custom_digest) { Digest::SHA256.hexdigest(custom_css) } + + before do + Setting.custom_css = custom_css + end + context 'when custom css setting value digest is present' do - before { Rails.cache.write(:setting_digest_custom_css, '1a2s3d4f1a2s3d4f') } + before { Rails.cache.write(:setting_digest_custom_css, custom_digest) } it 'returns value from settings' do expect(custom_stylesheet) - .to match('/css/custom-1a2s3d4f.css') + .to match("/css/custom-#{custom_digest[...8]}.css") end end - context 'when custom css setting value digest is not present' do + context 'when custom css setting value digest is expired' do before { Rails.cache.delete(:setting_digest_custom_css) } + it 'returns value from settings' do + expect(custom_stylesheet) + .to match("/css/custom-#{custom_digest[...8]}.css") + end + end + + context 'when custom css setting is not present' do + before do + Setting.custom_css = nil + Rails.cache.delete(:setting_digest_custom_css) + end + it 'returns default value' do expect(custom_stylesheet) .to be_blank From ee4edbb94f4006670c819a4db653a8dea347259e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:44:58 +0100 Subject: [PATCH 42/73] New Crowdin Translations (automated) (#33582) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/lv.json | 2 +- app/javascript/mastodon/locales/nan.json | 26 ++++++++++++++++++++++++ config/locales/ca.yml | 3 +++ config/locales/simple_form.ca.yml | 3 +++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 9244f3509cc09d..94c48453b7c044 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -345,7 +345,7 @@ "hints.profiles.see_more_followers": "Skatīt vairāk sekotāju {domain}", "hints.profiles.see_more_follows": "Skatīt vairāk sekojumu {domain}", "hints.profiles.see_more_posts": "Skatīt vairāk ierakstu {domain}", - "hints.threads.replies_may_be_missing": "Var trūkt atbildes no citiem serveriem.", + "hints.threads.replies_may_be_missing": "Var trūkt atbilžu no citiem serveriem.", "hints.threads.see_more": "Skatīt vairāk atbilžu {domain}", "home.column_settings.show_reblogs": "Rādīt pastiprinātos ierakstus", "home.column_settings.show_replies": "Rādīt atbildes", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 009f96b9a7233a..22a7c507fca837 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -126,9 +126,35 @@ "bundle_column_error.network.title": "網路錯誤", "bundle_column_error.retry": "Koh試", "bundle_column_error.return": "Tńg去頭頁", + "bundle_column_error.routing.body": "Tshuē bô所要求ê頁面。Lí kám確定地址liâu-á ê URL正確?", "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "關", + "bundle_modal_error.message": "Tī載入tsit ê畫面ê時起錯誤。", + "bundle_modal_error.retry": "Koh試", + "column.create_list": "建立列單", + "column.direct": "私人ê提起", + "column.directory": "瀏覽個人資料", + "column.domain_blocks": "封鎖ê域名", + "column.edit_list": "編輯列單", + "column.favourites": "Siōng kah意", + "column.firehose": "Tsit-má ê動態", + "column.follow_requests": "跟tuè請求", + "column.home": "頭頁", + "column_header.pin": "釘", + "column_header.show_settings": "顯示設定", + "column_header.unpin": "Pak掉", + "column_search.cancel": "取消", + "column_subheading.settings": "設定", + "community.column_settings.local_only": "Kan-ta展示本地ê", + "community.column_settings.media_only": "Kan-ta展示媒體", + "community.column_settings.remote_only": "Kan-ta展示遠距離ê", "compose.language.change": "換語言", + "compose.language.search": "Tshiau-tshuē語言……", + "compose.published.body": "成功PO文。", + "compose.published.open": "開", + "compose.saved.body": "PO文儲存ah。", + "compose_form.direct_message_warning_learn_more": "詳細資訊", + "compose_form.encryption_warning": "Mastodon ê PO文無點tuì點加密。M̄通用Mastodon分享任何敏感ê資訊。", "confirmations.follow_to_list.confirm": "跟tuè,加入kàu列單", "notification.favourite_pm": "{name} kah意lí ê私人提起", "notification.favourite_pm.name_and_others_with_link": "{name} kap{count, plural, other {另外 # ê lâng}}kah意lí ê私人提起", diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 66738c8689bb6b..acd103a2507868 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -937,6 +937,7 @@ ca: generates: action: Generar chance_to_review_html: "Les condicions de servei generades no es publicaran automàticament. Tindreu l'oportunitat de revisar-ne els resultats. Empleneu els detalls necessaris per a procedir." + explanation_html: La plantilla de condicions de servei proveïda ho és només a títol informatiu i no s'ha d'interpretar com a consell jurídic en cap cas. Consulteu el vostre propi servei legal sobre la vostra situació i les qüestions legals específiques que tingueu. title: Configuració de les condicions de servei history: Historial live: En ús @@ -1892,6 +1893,8 @@ ca: title: Un inici de sessió nou terms_of_service_changed: sign_off: L'equip de %{domain} + subject: Actualitzacions de les condicions de servei + subtitle: Les condicions de servei de %{domain} canvien title: Actualització important warning: appeal: Envia una apel·lació diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 2fecc78c793d42..58cc0a034c1321 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -130,6 +130,9 @@ ca: show_application: Sempre podràs veure quina aplicació ha publicat els teus tuts. tag: name: Només pots canviar la caixa de les lletres, per exemple, per fer-la més llegible + terms_of_service_generator: + domain: Identificació única del servei en línia que oferiu. + jurisdiction: Indiqueu el país on resideix qui paga les factures. Si és una empresa o una altra entitat, indiqueu el país en què està registrada, així com la ciutat, regió, territori o estat, segons calqui. user: chosen_languages: Quan estigui marcat, només es mostraran els tuts de les llengües seleccionades en les línies de temps públiques role: El rol controla quins permisos té l'usuari. From 4a2813158d16914450b6bc4c00e1b3e7edfa26ba Mon Sep 17 00:00:00 2001 From: S1m <31284753+p1gp1g@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:14:00 +0100 Subject: [PATCH 43/73] Add support for standard webpush (#33528) Co-authored-by: Claire --- Gemfile | 2 +- Gemfile.lock | 4 +- .../api/v1/push/subscriptions_controller.rb | 3 +- .../api/web/push_subscriptions_controller.rb | 3 +- app/lib/web_push_request.rb | 26 +++++- app/models/web/push_subscription.rb | 5 +- .../rest/web_push_subscription_serializer.rb | 4 +- app/validators/web_push_key_validator.rb | 2 +- app/workers/web/push_notification_worker.rb | 66 +++++++++++---- ...11200_add_standard_to_push_subscription.rb | 7 ++ db/schema.rb | 5 +- .../web/push_notification_worker_spec.rb | 84 ++++++++++++++++--- 12 files changed, 166 insertions(+), 45 deletions(-) create mode 100644 db/migrate/20250108111200_add_standard_to_push_subscription.rb diff --git a/Gemfile b/Gemfile index f112e5ea5fc17e..2abdae151fef80 100644 --- a/Gemfile +++ b/Gemfile @@ -94,7 +94,7 @@ gem 'twitter-text', '~> 3.1.0' gem 'tzinfo-data', '~> 1.2023' gem 'webauthn', '~> 3.0' gem 'webpacker', '~> 5.4' -gem 'webpush', github: 'mastodon/webpush', ref: '52725def8baf67e0d645c9d1c6c0bdff69da0c60' +gem 'webpush', github: 'mastodon/webpush', ref: '9631ac63045cfabddacc69fc06e919b4c13eb913' gem 'json-ld' gem 'json-ld-preloaded', '~> 3.2' diff --git a/Gemfile.lock b/Gemfile.lock index 596ed7bf52186b..89739f9053dbc1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/mastodon/webpush.git - revision: 52725def8baf67e0d645c9d1c6c0bdff69da0c60 - ref: 52725def8baf67e0d645c9d1c6c0bdff69da0c60 + revision: 9631ac63045cfabddacc69fc06e919b4c13eb913 + ref: 9631ac63045cfabddacc69fc06e919b4c13eb913 specs: webpush (1.1.0) hkdf (~> 0.2) diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index e1ad89ee3e02e2..d74b5d958febfa 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -21,6 +21,7 @@ def create endpoint: subscription_params[:endpoint], key_p256dh: subscription_params[:keys][:p256dh], key_auth: subscription_params[:keys][:auth], + standard: subscription_params[:standard] || false, data: data_params, user_id: current_user.id, access_token_id: doorkeeper_token.id @@ -55,7 +56,7 @@ def check_push_subscription end def subscription_params - params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh]) + params.require(:subscription).permit(:endpoint, :standard, keys: [:auth, :p256dh]) end def data_params diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index f5159614278a0b..7eb51c6846254e 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -66,7 +66,7 @@ def set_push_subscription end def subscription_params - @subscription_params ||= params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh]) + @subscription_params ||= params.require(:subscription).permit(:standard, :endpoint, keys: [:auth, :p256dh]) end def web_push_subscription_params @@ -76,6 +76,7 @@ def web_push_subscription_params endpoint: subscription_params[:endpoint], key_auth: subscription_params[:keys][:auth], key_p256dh: subscription_params[:keys][:p256dh], + standard: subscription_params[:standard] || false, user_id: active_session.user_id, } end diff --git a/app/lib/web_push_request.rb b/app/lib/web_push_request.rb index 91227ed46029fa..85e8ab6bb51157 100644 --- a/app/lib/web_push_request.rb +++ b/app/lib/web_push_request.rb @@ -2,7 +2,8 @@ class WebPushRequest SIGNATURE_ALGORITHM = 'p256ecdsa' - AUTH_HEADER = 'WebPush' + LEGACY_AUTH_HEADER = 'WebPush' + STANDARD_AUTH_HEADER = 'vapid' PAYLOAD_EXPIRATION = 24.hours JWT_ALGORITHM = 'ES256' JWT_TYPE = 'JWT' @@ -10,6 +11,7 @@ class WebPushRequest attr_reader :web_push_subscription delegate( + :standard, :endpoint, :key_auth, :key_p256dh, @@ -24,20 +26,36 @@ def audience @audience ||= Addressable::URI.parse(endpoint).normalized_site end - def authorization_header - [AUTH_HEADER, encoded_json_web_token].join(' ') + def legacy_authorization_header + [LEGACY_AUTH_HEADER, encoded_json_web_token].join(' ') end def crypto_key_header [SIGNATURE_ALGORITHM, vapid_key.public_key_for_push_header].join('=') end - def encrypt(payload) + def legacy_encrypt(payload) Webpush::Legacy::Encryption.encrypt(payload, key_p256dh, key_auth) end + def standard_authorization_header + [STANDARD_AUTH_HEADER, standard_vapid_value].join(' ') + end + + def standard_encrypt(payload) + Webpush::Encryption.encrypt(payload, key_p256dh, key_auth) + end + + def legacy + !standard + end + private + def standard_vapid_value + "t=#{encoded_json_web_token},k=#{vapid_key.public_key_for_push_header}" + end + def encoded_json_web_token JWT.encode( web_token_payload, diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index 656040d2cec43b..12d843cd09d26f 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -5,10 +5,11 @@ # Table name: web_push_subscriptions # # id :bigint(8) not null, primary key +# data :json # endpoint :string not null -# key_p256dh :string not null # key_auth :string not null -# data :json +# key_p256dh :string not null +# standard :boolean default(FALSE), not null # created_at :datetime not null # updated_at :datetime not null # access_token_id :bigint(8) diff --git a/app/serializers/rest/web_push_subscription_serializer.rb b/app/serializers/rest/web_push_subscription_serializer.rb index 674a2d5a86ec01..4cb980bb9334c7 100644 --- a/app/serializers/rest/web_push_subscription_serializer.rb +++ b/app/serializers/rest/web_push_subscription_serializer.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true class REST::WebPushSubscriptionSerializer < ActiveModel::Serializer - attributes :id, :endpoint, :alerts, :server_key, :policy + attributes :id, :endpoint, :standard, :alerts, :server_key, :policy + + delegate :standard, to: :object def alerts (object.data&.dig('alerts') || {}).each_with_object({}) { |(k, v), h| h[k] = ActiveModel::Type::Boolean.new.cast(v) } diff --git a/app/validators/web_push_key_validator.rb b/app/validators/web_push_key_validator.rb index 25914d59ebc5b8..a8ad5c9c6bbaea 100644 --- a/app/validators/web_push_key_validator.rb +++ b/app/validators/web_push_key_validator.rb @@ -3,7 +3,7 @@ class WebPushKeyValidator < ActiveModel::Validator def validate(subscription) begin - Webpush::Legacy::Encryption.encrypt('validation_test', subscription.key_p256dh, subscription.key_auth) + Webpush::Encryption.encrypt('validation_test', subscription.key_p256dh, subscription.key_auth) rescue ArgumentError, OpenSSL::PKey::EC::Point::Error subscription.errors.add(:base, I18n.t('crypto.errors.invalid_key')) end diff --git a/app/workers/web/push_notification_worker.rb b/app/workers/web/push_notification_worker.rb index 3629904fa7f920..32279a9e74da0e 100644 --- a/app/workers/web/push_notification_worker.rb +++ b/app/workers/web/push_notification_worker.rb @@ -19,7 +19,19 @@ def perform(subscription_id, notification_id) # in the meantime, so we have to double-check before proceeding return unless @notification.activity.present? && @subscription.pushable?(@notification) - payload = web_push_request.encrypt(push_notification_json) + if web_push_request.legacy + perform_legacy_request + else + perform_standard_request + end + rescue ActiveRecord::RecordNotFound + true + end + + private + + def perform_legacy_request + payload = web_push_request.legacy_encrypt(push_notification_json) request_pool.with(web_push_request.audience) do |http_client| request = Request.new(:post, web_push_request.endpoint, body: payload.fetch(:ciphertext), http_client: http_client) @@ -31,28 +43,48 @@ def perform(subscription_id, notification_id) 'Content-Encoding' => 'aesgcm', 'Encryption' => "salt=#{Webpush.encode64(payload.fetch(:salt)).delete('=')}", 'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{web_push_request.crypto_key_header}", - 'Authorization' => web_push_request.authorization_header, + 'Authorization' => web_push_request.legacy_authorization_header, 'Unsubscribe-URL' => subscription_url ) - request.perform do |response| - # If the server responds with an error in the 4xx range - # that isn't about rate-limiting or timeouts, we can - # assume that the subscription is invalid or expired - # and must be removed - - if (400..499).cover?(response.code) && ![408, 429].include?(response.code) - @subscription.destroy! - elsif !(200...300).cover?(response.code) - raise Mastodon::UnexpectedResponseError, response - end - end + send(request) end - rescue ActiveRecord::RecordNotFound - true end - private + def perform_standard_request + payload = web_push_request.standard_encrypt(push_notification_json) + + request_pool.with(web_push_request.audience) do |http_client| + request = Request.new(:post, web_push_request.endpoint, body: payload, http_client: http_client) + + request.add_headers( + 'Content-Type' => 'application/octet-stream', + 'Ttl' => TTL.to_s, + 'Urgency' => URGENCY, + 'Content-Encoding' => 'aes128gcm', + 'Authorization' => web_push_request.standard_authorization_header, + 'Unsubscribe-URL' => subscription_url, + 'Content-Length' => payload.length.to_s + ) + + send(request) + end + end + + def send(request) + request.perform do |response| + # If the server responds with an error in the 4xx range + # that isn't about rate-limiting or timeouts, we can + # assume that the subscription is invalid or expired + # and must be removed + + if (400..499).cover?(response.code) && ![408, 429].include?(response.code) + @subscription.destroy! + elsif !(200...300).cover?(response.code) + raise Mastodon::UnexpectedResponseError, response + end + end + end def web_push_request @web_push_request || WebPushRequest.new(@subscription) diff --git a/db/migrate/20250108111200_add_standard_to_push_subscription.rb b/db/migrate/20250108111200_add_standard_to_push_subscription.rb new file mode 100644 index 00000000000000..eb72f9c62e41e7 --- /dev/null +++ b/db/migrate/20250108111200_add_standard_to_push_subscription.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddStandardToPushSubscription < ActiveRecord::Migration[8.0] + def change + add_column :web_push_subscriptions, :standard, :boolean, null: false, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 49b10cb7bd1047..13abd5c0cd682a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,9 +10,9 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_16_224825) do +ActiveRecord::Schema[8.0].define(version: 2025_01_08_111200) do # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" + enable_extension "pg_catalog.plpgsql" create_table "account_aliases", force: :cascade do |t| t.bigint "account_id", null: false @@ -1202,6 +1202,7 @@ t.datetime "updated_at", precision: nil, null: false t.bigint "access_token_id" t.bigint "user_id" + t.boolean "standard", default: false, null: false t.index ["access_token_id"], name: "index_web_push_subscriptions_on_access_token_id", where: "(access_token_id IS NOT NULL)" t.index ["user_id"], name: "index_web_push_subscriptions_on_user_id" end diff --git a/spec/workers/web/push_notification_worker_spec.rb b/spec/workers/web/push_notification_worker_spec.rb index 88d88f2f3deb3b..6ee8ae53f8e1ea 100644 --- a/spec/workers/web/push_notification_worker_spec.rb +++ b/spec/workers/web/push_notification_worker_spec.rb @@ -5,21 +5,36 @@ RSpec.describe Web::PushNotificationWorker do subject { described_class.new } - let(:p256dh) { 'BN4GvZtEZiZuqFxSKVZfSfluwKBD7UxHNBmWkfiZfCtgDE8Bwh-_MtLXbBxTBAWH9r7IPKL0lhdcaqtL1dfxU5E=' } - let(:auth) { 'Q2BoAjC09xH3ywDLNJr-dA==' } let(:endpoint) { 'https://updates.push.services.mozilla.com/push/v1/subscription-id' } let(:user) { Fabricate(:user) } let(:notification) { Fabricate(:notification) } - let(:subscription) { Fabricate(:web_push_subscription, user_id: user.id, key_p256dh: p256dh, key_auth: auth, endpoint: endpoint, data: { alerts: { notification.type => true } }) } let(:vapid_public_key) { 'BB37UCyc8LLX4PNQSe-04vSFvpUWGrENubUaslVFM_l5TxcGVMY0C3RXPeUJAQHKYlcOM2P4vTYmkoo0VZGZTM4=' } let(:vapid_private_key) { 'OPrw1Sum3gRoL4-DXfSCC266r-qfFSRZrnj8MgIhRHg=' } let(:vapid_key) { Webpush::VapidKey.from_keys(vapid_public_key, vapid_private_key) } let(:contact_email) { 'sender@example.com' } - let(:ciphertext) { "+\xB8\xDBT}\x13\xB6\xDD.\xF9\xB0\xA7\xC8\xD2\x80\xFD\x99#\xF7\xAC\x83\xA4\xDB,\x1F\xB5\xB9w\x85>\xF7\xADr" } - let(:salt) { "X\x97\x953\xE4X\xF8_w\xE7T\x95\xC51q\xFE" } - let(:server_public_key) { "\x04\b-RK9w\xDD$\x16lFz\xF9=\xB4~\xC6\x12k\xF3\xF40t\xA9\xC1\fR\xC3\x81\x80\xAC\f\x7F\xE4\xCC\x8E\xC2\x88 n\x8BB\xF1\x9C\x14\a\xFA\x8D\xC9\x80\xA1\xDDyU\\&c\x01\x88#\x118Ua" } - let(:shared_secret) { "\t\xA7&\x85\t\xC5m\b\xA8\xA7\xF8B{1\xADk\xE1y'm\xEDE\xEC\xDD\xEDj\xB3$s\xA9\xDA\xF0" } - let(:payload) { { ciphertext: ciphertext, salt: salt, server_public_key: server_public_key, shared_secret: shared_secret } } + + # Legacy values + let(:p256dh) { 'BN4GvZtEZiZuqFxSKVZfSfluwKBD7UxHNBmWkfiZfCtgDE8Bwh-_MtLXbBxTBAWH9r7IPKL0lhdcaqtL1dfxU5E=' } + let(:auth) { 'Q2BoAjC09xH3ywDLNJr-dA==' } + let(:legacy_subscription) { Fabricate(:web_push_subscription, user_id: user.id, key_p256dh: p256dh, key_auth: auth, endpoint: endpoint, data: { alerts: { notification.type => true } }) } + let(:legacy_payload) do + { + ciphertext: "+\xB8\xDBT}\x13\xB6\xDD.\xF9\xB0\xA7\xC8\xD2\x80\xFD\x99#\xF7\xAC\x83\xA4\xDB,\x1F\xB5\xB9w\x85>\xF7\xADr", + salt: "X\x97\x953\xE4X\xF8_w\xE7T\x95\xC51q\xFE", + server_public_key: "\x04\b-RK9w\xDD$\x16lFz\xF9=\xB4~\xC6\x12k\xF3\xF40t\xA9\xC1\fR\xC3\x81\x80\xAC\f\x7F\xE4\xCC\x8E\xC2\x88 n\x8BB\xF1\x9C\x14\a\xFA\x8D\xC9\x80\xA1\xDDyU\\&c\x01\x88#\x118Ua", + shared_secret: "\t\xA7&\x85\t\xC5m\b\xA8\xA7\xF8B{1\xADk\xE1y'm\xEDE\xEC\xDD\xEDj\xB3$s\xA9\xDA\xF0", + } + end + + # Standard values, from RFC8291 + let(:std_p256dh) { 'BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4' } + let(:std_auth) { 'BTBZMqHH6r4Tts7J_aSIgg' } + let(:std_as_public) { 'BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8' } + let(:std_as_private) { 'yfWPiYE-n46HLnH0KqZOF1fJJU3MYrct3AELtAQ-oRw' } + let(:std_salt) { 'DGv6ra1nlYgDCS1FRnbzlw' } + let(:std_subscription) { Fabricate(:web_push_subscription, user_id: user.id, key_p256dh: std_p256dh, key_auth: std_auth, endpoint: endpoint, standard: true, data: { alerts: { notification.type => true } }) } + let(:std_input) { 'When I grow up, I want to be a watermelon' } + let(:std_ciphertext) { 'DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPTpK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN' } describe 'perform' do around do |example| @@ -35,20 +50,40 @@ before do Setting.site_contact_email = contact_email - allow(Webpush::Legacy::Encryption).to receive(:encrypt).and_return(payload) allow(JWT).to receive(:encode).and_return('jwt.encoded.payload') stub_request(:post, endpoint).to_return(status: 201, body: '') end - it 'calls the relevant service with the correct headers' do - subject.perform(subscription.id, notification.id) + it 'Legacy push calls the relevant service with the legacy headers' do + allow(Webpush::Legacy::Encryption).to receive(:encrypt).and_return(legacy_payload) - expect(web_push_endpoint_request) + subject.perform(legacy_subscription.id, notification.id) + + expect(legacy_web_push_endpoint_request) .to have_been_made end - def web_push_endpoint_request + # We allow subject stub to encrypt the same input than the RFC8291 example + # rubocop:disable RSpec/SubjectStub + it 'Standard push calls the relevant service with the standard headers' do + # Mock server keys to match RFC example + allow(OpenSSL::PKey::EC).to receive(:generate).and_return(std_as_keys) + # Mock the random salt to match RFC example + rand = Random.new + allow(Random).to receive(:new).and_return(rand) + allow(rand).to receive(:bytes).and_return(Webpush.decode64(std_salt)) + # Mock input to match RFC example + allow(subject).to receive(:push_notification_json).and_return(std_input) + + subject.perform(std_subscription.id, notification.id) + + expect(standard_web_push_endpoint_request) + .to have_been_made + end + # rubocop:enable RSpec/SubjectStub + + def legacy_web_push_endpoint_request a_request( :post, endpoint @@ -66,5 +101,28 @@ def web_push_endpoint_request body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr" ) end + + def standard_web_push_endpoint_request + a_request( + :post, + endpoint + ).with( + headers: { + 'Content-Encoding' => 'aes128gcm', + 'Content-Type' => 'application/octet-stream', + 'Ttl' => '172800', + 'Urgency' => 'normal', + 'Authorization' => "vapid t=jwt.encoded.payload,k=#{vapid_public_key.delete('=')}", + 'Unsubscribe-URL' => %r{/api/web/push_subscriptions/}, + }, + body: Webpush.decode64(std_ciphertext) + ) + end + + def std_as_keys + # VapidKey contains a method to retrieve EC keypair from + # B64 raw keys, the keypair is stored in curve field + Webpush::VapidKey.from_keys(std_as_public, std_as_private).curve + end end end From a9a8b6b701026e41b2ee8edb897834b9c799cd2e Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 14 Jan 2025 05:27:21 -0500 Subject: [PATCH 44/73] Expand coverage of admin/trends/* areas (#33581) --- .../links/preview_card_providers_spec.rb | 49 +++++++++-- spec/system/admin/trends/links_spec.rb | 85 +++++++++++++++++-- spec/system/admin/trends/statuses_spec.rb | 83 ++++++++++++++++-- spec/system/admin/trends/tags_spec.rb | 46 ++++++++-- 4 files changed, 232 insertions(+), 31 deletions(-) diff --git a/spec/system/admin/trends/links/preview_card_providers_spec.rb b/spec/system/admin/trends/links/preview_card_providers_spec.rb index 0a5b5a7581b1e8..159a5b720ae0ea 100644 --- a/spec/system/admin/trends/links/preview_card_providers_spec.rb +++ b/spec/system/admin/trends/links/preview_card_providers_spec.rb @@ -5,20 +5,49 @@ RSpec.describe 'Admin::Trends::Links::PreviewCardProviders' do let(:current_user) { Fabricate(:admin_user) } - before do - sign_in current_user - end + before { sign_in current_user } describe 'Performing batch updates' do - before do - visit admin_trends_links_preview_card_providers_path - end - context 'without selecting any records' do it 'displays a notice about selection' do + visit admin_trends_links_preview_card_providers_path + click_on button_for_allow - expect(page).to have_content(selection_error_text) + expect(page) + .to have_content(selection_error_text) + end + end + + context 'with providers that are not trendable' do + let!(:provider) { Fabricate :preview_card_provider, trendable: false } + + it 'allows the providers' do + visit admin_trends_links_preview_card_providers_path + + check_item + + expect { click_on button_for_allow } + .to change { provider.reload.trendable? }.from(false).to(true) + end + end + + context 'with providers that are trendable' do + let!(:provider) { Fabricate :preview_card_provider, trendable: true } + + it 'disallows the providers' do + visit admin_trends_links_preview_card_providers_path + + check_item + + expect { click_on button_for_disallow } + .to change { provider.reload.trendable? }.from(true).to(false) + end + end + + def check_item + within '.batch-table__row' do + find('input[type=checkbox]').check end end @@ -26,6 +55,10 @@ def button_for_allow I18n.t('admin.trends.allow') end + def button_for_disallow + I18n.t('admin.trends.disallow') + end + def selection_error_text I18n.t('admin.trends.links.publishers.no_publisher_selected') end diff --git a/spec/system/admin/trends/links_spec.rb b/spec/system/admin/trends/links_spec.rb index 15138f42d119a0..879bbe8ad9ff62 100644 --- a/spec/system/admin/trends/links_spec.rb +++ b/spec/system/admin/trends/links_spec.rb @@ -5,20 +5,77 @@ RSpec.describe 'Admin::Trends::Links' do let(:current_user) { Fabricate(:admin_user) } - before do - sign_in current_user - end + before { sign_in current_user } describe 'Performing batch updates' do - before do - visit admin_trends_links_path - end - context 'without selecting any records' do it 'displays a notice about selection' do + visit admin_trends_links_path + click_on button_for_allow - expect(page).to have_content(selection_error_text) + expect(page) + .to have_content(selection_error_text) + end + end + + context 'with links that are not trendable' do + let!(:preview_card_trend) { Fabricate :preview_card_trend, preview_card: Fabricate(:preview_card, trendable: false) } + + it 'allows the links' do + visit admin_trends_links_path + + check_item + + expect { click_on button_for_allow } + .to change { preview_card_trend.preview_card.reload.trendable? }.from(false).to(true) + end + end + + context 'with links whose providers are not trendable' do + let(:preview_card_provider) { Fabricate :preview_card_provider, trendable: false } + let!(:preview_card_trend) { Fabricate :preview_card_trend, preview_card: Fabricate(:preview_card, url: "https://#{preview_card_provider.domain}/page") } + + it 'allows the providers of the links' do + visit admin_trends_links_path + + check_item + + expect { click_on button_for_allow_providers } + .to change { preview_card_trend.preview_card.provider.reload.trendable? }.from(false).to(true) + end + end + + context 'with links that are trendable' do + let!(:preview_card_trend) { Fabricate :preview_card_trend, preview_card: Fabricate(:preview_card, trendable: true) } + + it 'disallows the links' do + visit admin_trends_links_path + + check_item + + expect { click_on button_for_disallow } + .to change { preview_card_trend.preview_card.reload.trendable? }.from(true).to(false) + end + end + + context 'with links whose providers are trendable' do + let(:preview_card_provider) { Fabricate :preview_card_provider, trendable: true } + let!(:preview_card_trend) { Fabricate :preview_card_trend, preview_card: Fabricate(:preview_card, url: "https://#{preview_card_provider.domain}/page") } + + it 'disallows the links' do + visit admin_trends_links_path + + check_item + + expect { click_on button_for_disallow_providers } + .to change { preview_card_trend.preview_card.provider.reload.trendable? }.from(true).to(false) + end + end + + def check_item + within '.batch-table__row' do + find('input[type=checkbox]').check end end @@ -26,6 +83,18 @@ def button_for_allow I18n.t('admin.trends.links.allow') end + def button_for_allow_providers + I18n.t('admin.trends.links.allow_provider') + end + + def button_for_disallow + I18n.t('admin.trends.links.disallow') + end + + def button_for_disallow_providers + I18n.t('admin.trends.links.disallow_provider') + end + def selection_error_text I18n.t('admin.trends.links.no_link_selected') end diff --git a/spec/system/admin/trends/statuses_spec.rb b/spec/system/admin/trends/statuses_spec.rb index 45c048afb07787..be081df98936e5 100644 --- a/spec/system/admin/trends/statuses_spec.rb +++ b/spec/system/admin/trends/statuses_spec.rb @@ -5,20 +5,75 @@ RSpec.describe 'Admin::Trends::Statuses' do let(:current_user) { Fabricate(:admin_user) } - before do - sign_in current_user - end + before { sign_in current_user } describe 'Performing batch updates' do - before do - visit admin_trends_statuses_path - end - context 'without selecting any records' do it 'displays a notice about selection' do + visit admin_trends_statuses_path + click_on button_for_allow - expect(page).to have_content(selection_error_text) + expect(page) + .to have_content(selection_error_text) + end + end + + context 'with statuses that are not trendable' do + let!(:status_trend) { Fabricate :status_trend, status: Fabricate(:status, trendable: false) } + + it 'allows the statuses' do + visit admin_trends_statuses_path + + check_item + + expect { click_on button_for_allow } + .to change { status_trend.status.reload.trendable? }.from(false).to(true) + end + end + + context 'with statuses whose accounts are not trendable' do + let!(:status_trend) { Fabricate :status_trend, status: Fabricate(:status, account: Fabricate(:account, trendable: false)) } + + it 'allows the accounts of the statuses' do + visit admin_trends_statuses_path + + check_item + + expect { click_on button_for_allow_accounts } + .to change { status_trend.status.account.reload.trendable? }.from(false).to(true) + end + end + + context 'with statuses that are trendable' do + let!(:status_trend) { Fabricate :status_trend, status: Fabricate(:status, trendable: true) } + + it 'disallows the statuses' do + visit admin_trends_statuses_path + + check_item + + expect { click_on button_for_disallow } + .to change { status_trend.status.reload.trendable? }.from(true).to(false) + end + end + + context 'with statuses whose accounts are trendable' do + let!(:status_trend) { Fabricate :status_trend, status: Fabricate(:status, account: Fabricate(:account, trendable: true)) } + + it 'disallows the statuses' do + visit admin_trends_statuses_path + + check_item + + expect { click_on button_for_disallow_accounts } + .to change { status_trend.status.reload.trendable? }.from(true).to(false) + end + end + + def check_item + within '.batch-table__row' do + find('input[type=checkbox]').check end end @@ -26,6 +81,18 @@ def button_for_allow I18n.t('admin.trends.statuses.allow') end + def button_for_allow_accounts + I18n.t('admin.trends.statuses.allow_account') + end + + def button_for_disallow + I18n.t('admin.trends.statuses.disallow') + end + + def button_for_disallow_accounts + I18n.t('admin.trends.statuses.disallow_account') + end + def selection_error_text I18n.t('admin.trends.statuses.no_status_selected') end diff --git a/spec/system/admin/trends/tags_spec.rb b/spec/system/admin/trends/tags_spec.rb index 30b0850b938187..a71d9ba8cadf9f 100644 --- a/spec/system/admin/trends/tags_spec.rb +++ b/spec/system/admin/trends/tags_spec.rb @@ -5,27 +5,59 @@ RSpec.describe 'Admin::Trends::Tags' do let(:current_user) { Fabricate(:admin_user) } - before do - sign_in current_user - end + before { sign_in current_user } describe 'Performing batch updates' do - before do - visit admin_trends_tags_path - end - context 'without selecting any records' do it 'displays a notice about selection' do + visit admin_trends_tags_path + click_on button_for_allow expect(page).to have_content(selection_error_text) end end + context 'with tags that are not trendable' do + let!(:tag_trend) { Fabricate :tag_trend, tag: Fabricate(:tag, trendable: false) } + + it 'allows the tags' do + visit admin_trends_tags_path + + check_item + + expect { click_on button_for_allow } + .to change { tag_trend.tag.reload.trendable? }.from(false).to(true) + end + end + + context 'with tags that are trendable' do + let!(:tag_trend) { Fabricate :tag_trend, tag: Fabricate(:tag, trendable: true) } + + it 'disallows the tags' do + visit admin_trends_tags_path + + check_item + + expect { click_on button_for_disallow } + .to change { tag_trend.tag.reload.trendable? }.from(true).to(false) + end + end + + def check_item + within '.batch-table__row' do + find('input[type=checkbox]').check + end + end + def button_for_allow I18n.t('admin.trends.allow') end + def button_for_disallow + I18n.t('admin.trends.disallow') + end + def selection_error_text I18n.t('admin.trends.tags.no_tag_selected') end From e2f085e2b2cec08dc1f5ae825730c2a3bf62e054 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 14 Jan 2025 11:42:06 +0100 Subject: [PATCH 45/73] Use final specification for new WebPush subscriptions in web interface (#33587) --- .../mastodon/actions/push_notifications/registerer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js index b3d3850e31d115..647a6bd9fb9fe4 100644 --- a/app/javascript/mastodon/actions/push_notifications/registerer.js +++ b/app/javascript/mastodon/actions/push_notifications/registerer.js @@ -33,7 +33,7 @@ const unsubscribe = ({ registration, subscription }) => subscription ? subscription.unsubscribe().then(() => registration) : registration; const sendSubscriptionToBackend = (subscription) => { - const params = { subscription }; + const params = { subscription: { ...subscription.toJSON(), standard: true } }; if (me) { const data = pushNotificationsSetting.get(me); From e9462960a7cf416b9e459d599a0f2cc8c5b070f0 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 14 Jan 2025 14:10:48 +0100 Subject: [PATCH 46/73] Redirect new users to onboarding (#33471) --- app/javascript/mastodon/api_types/accounts.ts | 2 +- .../mastodon/features/onboarding/profile.tsx | 6 +++++- app/javascript/mastodon/features/ui/index.jsx | 13 +++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/api_types/accounts.ts b/app/javascript/mastodon/api_types/accounts.ts index fdbd7523fc15cd..3f8b27497f5b58 100644 --- a/app/javascript/mastodon/api_types/accounts.ts +++ b/app/javascript/mastodon/api_types/accounts.ts @@ -19,7 +19,7 @@ export interface BaseApiAccountJSON { avatar_static: string; bot: boolean; created_at: string; - discoverable: boolean; + discoverable?: boolean; indexable: boolean; display_name: string; emojis: ApiCustomEmojiJSON[]; diff --git a/app/javascript/mastodon/features/onboarding/profile.tsx b/app/javascript/mastodon/features/onboarding/profile.tsx index 1e5e868f18b88d..d9b394acfbb85d 100644 --- a/app/javascript/mastodon/features/onboarding/profile.tsx +++ b/app/javascript/mastodon/features/onboarding/profile.tsx @@ -12,6 +12,7 @@ import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import PersonIcon from '@/material-icons/400-24px/person.svg?react'; import { updateAccount } from 'mastodon/actions/accounts'; +import { closeOnboarding } from 'mastodon/actions/onboarding'; import { Button } from 'mastodon/components/button'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; @@ -58,7 +59,9 @@ export const Profile: React.FC<{ ); const [avatar, setAvatar] = useState(); const [header, setHeader] = useState(); - const [discoverable, setDiscoverable] = useState(true); + const [discoverable, setDiscoverable] = useState( + account?.discoverable ?? true, + ); const [isSaving, setIsSaving] = useState(false); const [errors, setErrors] = useState(); const avatarFileRef = createRef(); @@ -132,6 +135,7 @@ export const Profile: React.FC<{ ) .then(() => { history.push('/start/follows'); + dispatch(closeOnboarding()); return ''; }) // eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 1ecc52b6bdfd6d..b239e63ccd7f1b 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -92,6 +92,7 @@ const mapStateToProps = state => ({ hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0, canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']), firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION, + newAccount: !state.getIn(['accounts', me, 'note']) && !state.getIn(['accounts', me, 'bot']) && state.getIn(['accounts', me, 'following_count'], 0) === 0 && state.getIn(['accounts', me, 'statuses_count'], 0) === 0, username: state.getIn(['accounts', me, 'username']), }); @@ -135,6 +136,7 @@ class SwitchingColumnsArea extends PureComponent { children: PropTypes.node, location: PropTypes.object, singleColumn: PropTypes.bool, + forceOnboarding: PropTypes.bool, }; UNSAFE_componentWillMount () { @@ -165,14 +167,16 @@ class SwitchingColumnsArea extends PureComponent { }; render () { - const { children, singleColumn } = this.props; + const { children, singleColumn, forceOnboarding } = this.props; const { signedIn } = this.props.identity; const pathName = this.props.location.pathname; let redirect; if (signedIn) { - if (singleColumn) { + if (forceOnboarding) { + redirect = ; + } else if (singleColumn) { redirect = ; } else { redirect = ; @@ -276,6 +280,7 @@ class UI extends PureComponent { intl: PropTypes.object.isRequired, layout: PropTypes.string.isRequired, firstLaunch: PropTypes.bool, + newAccount: PropTypes.bool, username: PropTypes.string, ...WithRouterPropTypes, }; @@ -568,7 +573,7 @@ class UI extends PureComponent { render () { const { draggingOver } = this.state; - const { children, isComposing, location, layout } = this.props; + const { children, isComposing, location, layout, firstLaunch, newAccount } = this.props; const handlers = { help: this.handleHotkeyToggleHelp, @@ -597,7 +602,7 @@ class UI extends PureComponent {
- + {children} From 7b608b41f29ccb05fe9521fc83e3f81769f1e8df Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:13:58 +0100 Subject: [PATCH 47/73] Update dependency @babel/plugin-transform-nullish-coalescing-operator to v7.26.6 (#33584) 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 0b3b8b3d95a905..337956163f6f1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -935,13 +935,13 @@ __metadata: linkType: hard "@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.3, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.25.9": - version: 7.26.5 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.26.5" + version: 7.26.6 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.26.6" dependencies: "@babel/helper-plugin-utils": "npm:^7.26.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/2e4b84745f9e8c40caf3e611641de4d6c7da6f96c2925b7fe568e3b031ed1864e325b9dffc9cda4e442fc40be43ffabb088782e980d411e0562bd5222df547ec + checksum: 10c0/574d6db7cbc5c092db5d1dece8ce26195e642b9c40dbfeaf3082058a78ad7959c1c333471cdd45f38b784ec488850548075d527b178c5010ee9bff7aa527cc7a languageName: node linkType: hard From 6356870dae9159afceac2bc3b4a3f1bfc702d5be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:14:03 +0100 Subject: [PATCH 48/73] Update dependency sass to v1.83.4 (#33585) 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 337956163f6f1e..f112e77920c914 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15623,8 +15623,8 @@ __metadata: linkType: hard "sass@npm:^1.62.1": - version: 1.83.1 - resolution: "sass@npm:1.83.1" + version: 1.83.4 + resolution: "sass@npm:1.83.4" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" @@ -15635,7 +15635,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10c0/9772506cd8290df7b5e800055098e91a8a65100840fd9e90c660deb74b248b3ddbbd1a274b8f7f09777d472d2c873575357bd87939a40fb5a80bdf654985486f + checksum: 10c0/6f27f0eebfeb50222b14baaeef548ef58a05daf8abd9797e6c499334ed7ad40541767056c8693780d06ca83d8836348ea7396a923d3be439b133507993ca78be languageName: node linkType: hard From 87849d739e08036e44649643ca05e18b2723533a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:17:11 +0100 Subject: [PATCH 49/73] Update dependency rspec-github to v3 (#33589) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 2abdae151fef80..0efcc6562c4897 100644 --- a/Gemfile +++ b/Gemfile @@ -125,7 +125,7 @@ group :test do gem 'flatware-rspec' # Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab - gem 'rspec-github', '~> 2.4', require: false + gem 'rspec-github', '~> 3.0', require: false # RSpec helpers for email specs gem 'email_spec' diff --git a/Gemfile.lock b/Gemfile.lock index 89739f9053dbc1..a625765faf95d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -690,7 +690,7 @@ GEM rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-github (2.4.0) + rspec-github (3.0.0) rspec-core (~> 3.0) rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) @@ -994,7 +994,7 @@ DEPENDENCIES redis (~> 4.5) redis-namespace (~> 1.10) rqrcode (~> 2.2) - rspec-github (~> 2.4) + rspec-github (~> 3.0) rspec-rails (~> 7.0) rspec-sidekiq (~> 5.0) rubocop From 7c56517c7c2f3e284b912f7519682c3612d3af81 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 14 Jan 2025 09:32:29 -0500 Subject: [PATCH 50/73] Move mastodon version config to `config_for` yml (#33577) --- config/mastodon.yml | 4 ++++ lib/mastodon/version.rb | 16 ++++++++++++---- spec/presenters/instance_presenter_spec.rb | 6 ++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/config/mastodon.yml b/config/mastodon.yml index e20ba0ab053c1b..a4442e873c58be 100644 --- a/config/mastodon.yml +++ b/config/mastodon.yml @@ -2,6 +2,10 @@ shared: self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil) %> software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check') %> + source: + base_url: <%= ENV.fetch('SOURCE_BASE_URL', nil) %> + repository: <%= ENV.fetch('GITHUB_REPOSITORY', 'mastodon/mastodon') %> + tag: <%= ENV.fetch('SOURCE_TAG', nil) %> version: metadata: <%= ENV.fetch('MASTODON_VERSION_METADATA', nil) %> prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil) %> diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index ddde4a993d640a..19779eb609e3a8 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -50,16 +50,16 @@ def api_versions end def repository - ENV.fetch('GITHUB_REPOSITORY', 'mastodon/mastodon') + source_configuration[:repository] end def source_base_url - ENV.fetch('SOURCE_BASE_URL', "https://github.com/#{repository}") + source_configuration[:base_url] || "https://github.com/#{repository}" end # specify git tag or commit hash here def source_tag - ENV.fetch('SOURCE_TAG', nil) + source_configuration[:tag] end def source_url @@ -79,7 +79,15 @@ def user_agent end def version_configuration - Rails.configuration.x.mastodon.version + mastodon_configuration.version + end + + def source_configuration + mastodon_configuration.source + end + + def mastodon_configuration + Rails.configuration.x.mastodon end end end diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb index 42f5200f3af4c5..cc6e0533bb297d 100644 --- a/spec/presenters/instance_presenter_spec.rb +++ b/spec/presenters/instance_presenter_spec.rb @@ -68,6 +68,7 @@ context 'with the GITHUB_REPOSITORY env variable set' do around do |example| ClimateControl.modify GITHUB_REPOSITORY: 'other/repo' do + reload_configuration example.run end end @@ -80,6 +81,7 @@ context 'without the GITHUB_REPOSITORY env variable set' do around do |example| ClimateControl.modify GITHUB_REPOSITORY: nil do + reload_configuration example.run end end @@ -88,6 +90,10 @@ expect(instance_presenter.source_url).to eq('https://github.com/mastodon/mastodon') end end + + def reload_configuration + Rails.configuration.x.mastodon = Rails.application.config_for(:mastodon) + end end describe '#thumbnail' do From 50013b10a50a560ebf4432cbe1782426181dba6f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 14 Jan 2025 09:32:57 -0500 Subject: [PATCH 51/73] Add `Status::Visibility` concern to hold visibility logic (#33578) --- app/models/concerns/status/visibility.rb | 47 +++++ app/models/status.rb | 27 +-- spec/models/status_spec.rb | 32 +-- .../models/concerns/status/visibility.rb | 184 ++++++++++++++++++ 4 files changed, 234 insertions(+), 56 deletions(-) create mode 100644 app/models/concerns/status/visibility.rb create mode 100644 spec/support/examples/models/concerns/status/visibility.rb diff --git a/app/models/concerns/status/visibility.rb b/app/models/concerns/status/visibility.rb new file mode 100644 index 00000000000000..e17196eb156ad4 --- /dev/null +++ b/app/models/concerns/status/visibility.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Status::Visibility + extend ActiveSupport::Concern + + included do + enum :visibility, + { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4 }, + suffix: :visibility, + validate: true + + scope :distributable_visibility, -> { where(visibility: %i(public unlisted)) } + scope :list_eligible_visibility, -> { where(visibility: %i(public unlisted private)) } + scope :not_direct_visibility, -> { where.not(visibility: :direct) } + + validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog? + + before_validation :set_visibility, unless: :visibility? + end + + class_methods do + def selectable_visibilities + visibilities.keys - %w(direct limited) + end + end + + def hidden? + !distributable? + end + + def distributable? + public_visibility? || unlisted_visibility? + end + + alias sign? distributable? + + private + + def set_visibility + self.visibility ||= reblog.visibility if reblog? + self.visibility ||= visibility_from_account + end + + def visibility_from_account + account.locked? ? :private : :public + end +end diff --git a/app/models/status.rb b/app/models/status.rb index 5a81b0077347f1..c012b1ddfa21d6 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -38,6 +38,7 @@ class Status < ApplicationRecord include Status::SearchConcern include Status::SnapshotConcern include Status::ThreadingConcern + include Status::Visibility MEDIA_ATTACHMENTS_LIMIT = 4 @@ -52,8 +53,6 @@ class Status < ApplicationRecord update_index('statuses', :proper) update_index('public_statuses', :proper) - enum :visibility, { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4 }, suffix: :visibility, validate: true - belongs_to :application, class_name: 'Doorkeeper::Application', optional: true belongs_to :account, inverse_of: :statuses @@ -98,7 +97,6 @@ class Status < ApplicationRecord validates_with StatusLengthValidator validates_with DisallowedHashtagsValidator validates :reblog, uniqueness: { scope: :account }, if: :reblog? - validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog? accepts_nested_attributes_for :poll @@ -125,9 +123,6 @@ class Status < ApplicationRecord scope :tagged_with_none, lambda { |tag_ids| where('NOT EXISTS (SELECT * FROM statuses_tags forbidden WHERE forbidden.status_id = statuses.id AND forbidden.tag_id IN (?))', tag_ids) } - scope :distributable_visibility, -> { where(visibility: %i(public unlisted)) } - scope :list_eligible_visibility, -> { where(visibility: %i(public unlisted private)) } - scope :not_direct_visibility, -> { where.not(visibility: :direct) } after_create_commit :trigger_create_webhooks after_update_commit :trigger_update_webhooks @@ -140,7 +135,6 @@ class Status < ApplicationRecord before_validation :prepare_contents, if: :local? before_validation :set_reblog - before_validation :set_visibility before_validation :set_conversation before_validation :set_local @@ -242,16 +236,6 @@ def reset_preview_card! PreviewCardsStatus.where(status_id: id).delete_all end - def hidden? - !distributable? - end - - def distributable? - public_visibility? || unlisted_visibility? - end - - alias sign? distributable? - def with_media? ordered_media_attachments.any? end @@ -351,10 +335,6 @@ def requires_review_notification? end class << self - def selectable_visibilities - visibilities.keys - %w(direct limited) - end - def favourites_map(status_ids, account_id) Favourite.select(:status_id).where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true } end @@ -436,11 +416,6 @@ def set_poll_id update_column(:poll_id, poll.id) if association(:poll).loaded? && poll.present? end - def set_visibility - self.visibility = reblog.visibility if reblog? && visibility.nil? - self.visibility = (account.locked? ? :private : :public) if visibility.nil? - end - def set_conversation self.thread = thread.reblog if thread&.reblog? diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 36b13df815c94c..a197aaf1d2baeb 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -9,6 +9,8 @@ let(:bob) { Fabricate(:account, username: 'bob') } let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!') } + include_examples 'Status::Visibility' + describe '#local?' do it 'returns true when no remote URI is set' do expect(subject.local?).to be true @@ -84,36 +86,6 @@ end end - describe '#hidden?' do - context 'when private_visibility?' do - it 'returns true' do - subject.visibility = :private - expect(subject.hidden?).to be true - end - end - - context 'when direct_visibility?' do - it 'returns true' do - subject.visibility = :direct - expect(subject.hidden?).to be true - end - end - - context 'when public_visibility?' do - it 'returns false' do - subject.visibility = :public - expect(subject.hidden?).to be false - end - end - - context 'when unlisted_visibility?' do - it 'returns false' do - subject.visibility = :unlisted - expect(subject.hidden?).to be false - end - end - end - describe '#content' do it 'returns the text of the status if it is not a reblog' do expect(subject.content).to eql subject.text diff --git a/spec/support/examples/models/concerns/status/visibility.rb b/spec/support/examples/models/concerns/status/visibility.rb new file mode 100644 index 00000000000000..dd9e0bddf0ddfb --- /dev/null +++ b/spec/support/examples/models/concerns/status/visibility.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.shared_examples 'Status::Visibility' do + describe 'Validations' do + context 'when status is a reblog' do + subject { Fabricate.build :status, reblog: Fabricate(:status) } + + it { is_expected.to allow_values('public', 'unlisted', 'private').for(:visibility) } + it { is_expected.to_not allow_values('direct', 'limited').for(:visibility) } + end + + context 'when status is not reblog' do + subject { Fabricate.build :status, reblog_of_id: nil } + + it { is_expected.to allow_values('public', 'unlisted', 'private', 'direct', 'limited').for(:visibility) } + end + end + + describe 'Scopes' do + let!(:direct_status) { Fabricate :status, visibility: :direct } + let!(:limited_status) { Fabricate :status, visibility: :limited } + let!(:private_status) { Fabricate :status, visibility: :private } + let!(:public_status) { Fabricate :status, visibility: :public } + let!(:unlisted_status) { Fabricate :status, visibility: :unlisted } + + describe '.list_eligible_visibility' do + it 'returns appropriate records' do + expect(Status.list_eligible_visibility) + .to include( + private_status, + public_status, + unlisted_status + ) + .and not_include(direct_status) + .and not_include(limited_status) + end + end + + describe '.distributable_visibility' do + it 'returns appropriate records' do + expect(Status.distributable_visibility) + .to include( + public_status, + unlisted_status + ) + .and not_include(private_status) + .and not_include(direct_status) + .and not_include(limited_status) + end + end + + describe '.not_direct_visibility' do + it 'returns appropriate records' do + expect(Status.not_direct_visibility) + .to include( + limited_status, + private_status, + public_status, + unlisted_status + ) + .and not_include(direct_status) + end + end + end + + describe 'Callbacks' do + describe 'Setting visibility in before validation' do + subject { Fabricate.build :status, visibility: nil } + + context 'when explicit value is set' do + before { subject.visibility = :public } + + it 'does not change' do + expect { subject.valid? } + .to_not change(subject, :visibility) + end + end + + context 'when status is a reblog' do + before { subject.reblog = Fabricate(:status, visibility: :public) } + + it 'changes to match the reblog' do + expect { subject.valid? } + .to change(subject, :visibility).to('public') + end + end + + context 'when account is locked' do + before { subject.account = Fabricate.build(:account, locked: true) } + + it 'changes to private' do + expect { subject.valid? } + .to change(subject, :visibility).to('private') + end + end + + context 'when account is not locked' do + before { subject.account = Fabricate.build(:account, locked: false) } + + it 'changes to public' do + expect { subject.valid? } + .to change(subject, :visibility).to('public') + end + end + end + end + + describe '.selectable_visibilities' do + it 'returns options available for default privacy selection' do + expect(Status.selectable_visibilities) + .to match(%w(public unlisted private)) + end + end + + describe '#hidden?' do + subject { Status.new } + + context 'when visibility is private' do + before { subject.visibility = :private } + + it { is_expected.to be_hidden } + end + + context 'when visibility is direct' do + before { subject.visibility = :direct } + + it { is_expected.to be_hidden } + end + + context 'when visibility is limited' do + before { subject.visibility = :limited } + + it { is_expected.to be_hidden } + end + + context 'when visibility is public' do + before { subject.visibility = :public } + + it { is_expected.to_not be_hidden } + end + + context 'when visibility is unlisted' do + before { subject.visibility = :unlisted } + + it { is_expected.to_not be_hidden } + end + end + + describe '#distributable?' do + subject { Status.new } + + context 'when visibility is public' do + before { subject.visibility = :public } + + it { is_expected.to be_distributable } + end + + context 'when visibility is unlisted' do + before { subject.visibility = :unlisted } + + it { is_expected.to be_distributable } + end + + context 'when visibility is private' do + before { subject.visibility = :private } + + it { is_expected.to_not be_distributable } + end + + context 'when visibility is direct' do + before { subject.visibility = :direct } + + it { is_expected.to_not be_distributable } + end + + context 'when visibility is limited' do + before { subject.visibility = :limited } + + it { is_expected.to_not be_distributable } + end + end +end From bfe73e153d425312211eb1114ff20275aa369059 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:34:26 +0100 Subject: [PATCH 52/73] Update dependency postcss to v8.5.1 (#33586) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index f112e77920c914..a99c1e23c9766b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12332,12 +12332,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" +"nanoid@npm:^3.3.8": + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" bin: nanoid: bin/nanoid.cjs - checksum: 10c0/e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 + checksum: 10c0/4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120 languageName: node linkType: hard @@ -14209,13 +14209,13 @@ __metadata: linkType: hard "postcss@npm:^8.2.15, postcss@npm:^8.4.24, postcss@npm:^8.4.49": - version: 8.4.49 - resolution: "postcss@npm:8.4.49" + version: 8.5.1 + resolution: "postcss@npm:8.5.1" dependencies: - nanoid: "npm:^3.3.7" + nanoid: "npm:^3.3.8" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10c0/f1b3f17aaf36d136f59ec373459f18129908235e65dbdc3aee5eef8eba0756106f52de5ec4682e29a2eab53eb25170e7e871b3e4b52a8f1de3d344a514306be3 + checksum: 10c0/c4d90c59c98e8a0c102b77d3f4cac190f883b42d63dc60e2f3ed840f16197c0c8e25a4327d2e9a847b45a985612317dc0534178feeebd0a1cf3eb0eecf75cae4 languageName: node linkType: hard From e1d7efadc04dd0826c6bcfe43325688566e13881 Mon Sep 17 00:00:00 2001 From: Michael Stanclift Date: Tue, 14 Jan 2025 10:35:58 -0600 Subject: [PATCH 53/73] Fix libyaml missing from Dockerfile build stage (#33591) --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 97c7a91499cfb8..deeac8b466122e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -153,6 +153,7 @@ RUN \ libpq-dev \ libssl-dev \ libtool \ + libyaml-dev \ meson \ nasm \ pkg-config \ From 68c9f91ccb46b14ce43424479b3ff8ab28800997 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 14 Jan 2025 12:40:55 -0500 Subject: [PATCH 54/73] Treat non-null but blank account domain as local (#33576) --- app/models/account.rb | 4 +++- spec/models/account_spec.rb | 22 ++++++++++------------ spec/services/import_service_spec.rb | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 6258857b1bc212..05e833d5753dcd 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -126,6 +126,8 @@ class Account < ApplicationRecord validates :uri, absence: true end + validates :domain, exclusion: { in: [''] } + normalizes :username, with: ->(username) { username.squish } scope :without_internal, -> { where(id: 1...) } @@ -187,7 +189,7 @@ def local? end def remote? - domain.present? + !domain.nil? end def moved? diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 5b995b4af63433..a0187b62114c7e 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -49,14 +49,16 @@ end describe '#local?' do - it 'returns true when domain is null' do - account = Fabricate(:account, domain: nil) - expect(account).to be_local + context 'when the domain is null' do + subject { Fabricate.build :account, domain: nil } + + it { is_expected.to be_local } end - it 'returns false when domain is present' do - account = Fabricate(:account, domain: 'foreign.tld') - expect(account).to_not be_local + context 'when the domain is present' do + subject { Fabricate.build :account, domain: 'host.example' } + + it { is_expected.to_not be_local } end end @@ -67,12 +69,6 @@ it { is_expected.to_not be_remote } end - context 'when the domain is blank' do - subject { Fabricate.build :account, domain: '' } - - it { is_expected.to_not be_remote } - end - context 'when the domain is present' do subject { Fabricate.build :account, domain: 'host.example' } @@ -557,6 +553,8 @@ describe 'Validations' do it { is_expected.to validate_presence_of(:username) } + it { is_expected.to_not allow_value('').for(:domain) } + context 'when account is local' do subject { Fabricate.build :account, domain: nil } diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb index 0a99c5e748db55..2e1358c63501fc 100644 --- a/spec/services/import_service_spec.rb +++ b/spec/services/import_service_spec.rb @@ -204,7 +204,7 @@ subject { described_class.new } let(:csv) { attachment_fixture('bookmark-imports.txt') } - let(:local_account) { Fabricate(:account, username: 'foo', domain: '') } + let(:local_account) { Fabricate(:account, username: 'foo', domain: nil) } let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') } let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) } From 9f03e5b53ae0c4088ea989b3ad4e3555e33daaa1 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 15 Jan 2025 02:47:34 -0500 Subject: [PATCH 55/73] Expand coverage of `admin/*blocks` areas (#33594) --- spec/system/admin/email_domain_blocks_spec.rb | 25 ++++++++++++++++--- spec/system/admin/ip_blocks_spec.rb | 21 ++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/spec/system/admin/email_domain_blocks_spec.rb b/spec/system/admin/email_domain_blocks_spec.rb index acf5027eda0ceb..807cfb37689e26 100644 --- a/spec/system/admin/email_domain_blocks_spec.rb +++ b/spec/system/admin/email_domain_blocks_spec.rb @@ -5,9 +5,7 @@ RSpec.describe 'Admin::EmailDomainBlocks' do let(:current_user) { Fabricate(:admin_user) } - before do - sign_in current_user - end + before { sign_in current_user } describe 'Performing batch updates' do before do @@ -22,6 +20,27 @@ end end + context 'with a selected block' do + let!(:email_domain_block) { Fabricate :email_domain_block } + + it 'deletes the block' do + visit admin_email_domain_blocks_path + + check_item + + expect { click_on button_for_delete } + .to change(EmailDomainBlock, :count).by(-1) + expect { email_domain_block.reload } + .to raise_error(ActiveRecord::RecordNotFound) + end + end + + def check_item + within '.batch-table__row' do + find('input[type=checkbox]').check + end + end + def button_for_delete I18n.t('admin.email_domain_blocks.delete') end diff --git a/spec/system/admin/ip_blocks_spec.rb b/spec/system/admin/ip_blocks_spec.rb index 8e8c8031c8a627..3bed506b688506 100644 --- a/spec/system/admin/ip_blocks_spec.rb +++ b/spec/system/admin/ip_blocks_spec.rb @@ -48,6 +48,27 @@ def submit_form end end + context 'with a selected block' do + let!(:ip_block) { Fabricate :ip_block } + + it 'deletes the block' do + visit admin_ip_blocks_path + + check_item + + expect { click_on button_for_delete } + .to change(IpBlock, :count).by(-1) + expect { ip_block.reload } + .to raise_error(ActiveRecord::RecordNotFound) + end + end + + def check_item + within '.batch-table__row' do + find('input[type=checkbox]').check + end + end + def button_for_delete I18n.t('admin.ip_blocks.delete') end From 1ae574e32a634850141f02625958a1c69d2acefb Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:48:26 +0900 Subject: [PATCH 56/73] Enable parallel execution for linting tasks in HAML workflows (#33593) --- .github/workflows/lint-haml.yml | 2 +- lint-staged.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-haml.yml b/.github/workflows/lint-haml.yml index 499be2010adc99..9361358078ed87 100644 --- a/.github/workflows/lint-haml.yml +++ b/.github/workflows/lint-haml.yml @@ -43,4 +43,4 @@ jobs: - name: Run haml-lint run: | echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json" - bin/haml-lint --reporter github + bin/haml-lint --parallel --reporter github diff --git a/lint-staged.config.js b/lint-staged.config.js index 63f5258a940fb1..baf5d0d454bf0e 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -3,7 +3,7 @@ const config = { 'Gemfile|*.{rb,ruby,ru,rake}': 'bin/rubocop --force-exclusion -a', '*.{js,jsx,ts,tsx}': 'eslint --fix', '*.{css,scss}': 'stylelint --fix', - '*.haml': 'bin/haml-lint -a', + '*.haml': 'bin/haml-lint -a --parallel', '**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit', }; From 2a0951e98780f71ee7e672d33e2b9f9560cc9d98 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:48:52 +0100 Subject: [PATCH 57/73] Update dependency stackprof to v0.2.27 (#33596) 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 a625765faf95d0..3bc530f9835ead 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -793,7 +793,7 @@ GEM simplecov-html (0.13.1) simplecov-lcov (0.8.0) simplecov_json_formatter (0.1.4) - stackprof (0.2.26) + stackprof (0.2.27) stoplight (4.1.0) redlock (~> 1.0) stringio (3.1.2) From ea01ecd44103b34a6b5a0a115aba9509ed24850e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:49:12 +0100 Subject: [PATCH 58/73] Update dependency opentelemetry-instrumentation-rails to v0.34.1 (#33595) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3bc530f9835ead..a4c5f8ad9827e3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -529,7 +529,7 @@ GEM opentelemetry-instrumentation-rack (0.25.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-rails (0.34.0) + opentelemetry-instrumentation-rails (0.34.1) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-action_mailer (~> 0.3.0) opentelemetry-instrumentation-action_pack (~> 0.10.0) @@ -538,6 +538,7 @@ GEM opentelemetry-instrumentation-active_record (~> 0.8.0) opentelemetry-instrumentation-active_support (~> 0.7.0) opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-concurrent_ruby (~> 0.21.4) opentelemetry-instrumentation-redis (0.25.7) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) From c20824fa760061cf8fba258dbbd6b0a615df4749 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 15 Jan 2025 09:29:14 -0500 Subject: [PATCH 59/73] Promote `Style/WordArray` rule out of todo into main config (#33580) --- .rubocop/style.yml | 3 +++ .rubocop_todo.yml | 9 +-------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.rubocop/style.yml b/.rubocop/style.yml index df1da2ba36bf2e..f59340d452e871 100644 --- a/.rubocop/style.yml +++ b/.rubocop/style.yml @@ -58,3 +58,6 @@ Style/TrailingCommaInArrayLiteral: Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: comma + +Style/WordArray: + MinSize: 3 # Override default of 2 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 12ef0ad6201025..38aec67befaf1c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.69.2. +# using RuboCop version 1.70.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -103,10 +103,3 @@ Style/RedundantConstantBase: Exclude: - 'config/environments/production.rb' - 'config/initializers/sidekiq.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: WordRegex. -# SupportedStyles: percent, brackets -Style/WordArray: - EnforcedStyle: percent - MinSize: 3 From 72abf052693ed0c70c7437da24ce860b8f81fad2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 04:00:04 -0500 Subject: [PATCH 60/73] Add "needs refresh" scenario to `api/v1/polls` request spec (#33608) --- spec/requests/api/v1/polls_spec.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spec/requests/api/v1/polls_spec.rb b/spec/requests/api/v1/polls_spec.rb index fd38297931a155..c93231e1ee6a44 100644 --- a/spec/requests/api/v1/polls_spec.rb +++ b/spec/requests/api/v1/polls_spec.rb @@ -36,6 +36,31 @@ end end + context 'when poll is remote and needs refresh' do + let(:poll) { Fabricate(:poll, last_fetched_at: nil, account: remote_account, status: status) } + let(:remote_account) { Fabricate :account, domain: 'host.example' } + let(:service) { instance_double(ActivityPub::FetchRemotePollService, call: nil) } + let(:status) { Fabricate(:status, visibility: 'public', account: remote_account) } + + before { allow(ActivityPub::FetchRemotePollService).to receive(:new).and_return(service) } + + it 'returns poll data and calls fetch remote service' do + subject + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body).to match( + a_hash_including( + id: poll.id.to_s + ) + ) + expect(service) + .to have_received(:call).with(poll, user.account) + end + end + context 'when parent status is private' do let(:visibility) { 'private' } From 998cf0dd53de5d19ef47c3e8de575d07b05f42eb Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 04:03:46 -0500 Subject: [PATCH 61/73] Convert `auth/setup` spec controller->system/request (#33604) --- app/controllers/auth/setup_controller.rb | 2 +- .../controllers/auth/setup_controller_spec.rb | 25 ---------------- spec/requests/auth/setup_spec.rb | 27 +++++++++++++++++ spec/system/auth/setup_spec.rb | 30 +++++++++++++++++++ 4 files changed, 58 insertions(+), 26 deletions(-) delete mode 100644 spec/controllers/auth/setup_controller_spec.rb create mode 100644 spec/requests/auth/setup_spec.rb create mode 100644 spec/system/auth/setup_spec.rb diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb index ad872dc607285f..376a30c16fd6f5 100644 --- a/app/controllers/auth/setup_controller.rb +++ b/app/controllers/auth/setup_controller.rb @@ -18,7 +18,7 @@ def update if @user.update(user_params) @user.resend_confirmation_instructions unless @user.confirmed? - redirect_to auth_setup_path, notice: I18n.t('auth.setup.new_confirmation_instructions_sent') + redirect_to auth_setup_path, notice: t('auth.setup.new_confirmation_instructions_sent') else render :show end diff --git a/spec/controllers/auth/setup_controller_spec.rb b/spec/controllers/auth/setup_controller_spec.rb deleted file mode 100644 index 28b07cb4b289e9..00000000000000 --- a/spec/controllers/auth/setup_controller_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Auth::SetupController do - render_views - - describe 'GET #show' do - context 'with a signed out request' do - it 'returns http redirect' do - get :show - expect(response).to be_redirect - end - end - - context 'with an unconfirmed signed in user' do - before { sign_in Fabricate(:user, confirmed_at: nil) } - - it 'returns http success' do - get :show - expect(response).to have_http_status(200) - end - end - end -end diff --git a/spec/requests/auth/setup_spec.rb b/spec/requests/auth/setup_spec.rb new file mode 100644 index 00000000000000..fa3c19680566db --- /dev/null +++ b/spec/requests/auth/setup_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Auth Setup' do + describe 'GET /auth/setup' do + context 'with a signed out request' do + it 'redirects to root' do + get '/auth/setup' + + expect(response) + .to redirect_to(new_user_session_url) + end + end + + context 'with a confirmed signed in user' do + before { sign_in Fabricate(:user, confirmed_at: 2.days.ago) } + + it 'redirects to root' do + get '/auth/setup' + + expect(response) + .to redirect_to(root_url) + end + end + end +end diff --git a/spec/system/auth/setup_spec.rb b/spec/system/auth/setup_spec.rb new file mode 100644 index 00000000000000..154f8cd5fac5bd --- /dev/null +++ b/spec/system/auth/setup_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Auth Setup' do + context 'with an unconfirmed signed in user' do + let(:user) { Fabricate(:user, confirmed_at: nil) } + + before { sign_in(user) } + + it 'can update email address' do + visit auth_setup_path + + expect(page) + .to have_content(I18n.t('auth.setup.title')) + + find('summary.lead').click + fill_in 'user_email', with: 'new-email@example.host' + + expect { submit_form } + .to(change { user.reload.unconfirmed_email }) + expect(page) + .to have_content(I18n.t('auth.setup.new_confirmation_instructions_sent')) + end + + def submit_form + find('[name=button]').click + end + end +end From 3db84989039c2dbba5dc6797092fa7cbbbcb7db8 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 04:09:06 -0500 Subject: [PATCH 62/73] Fix `Style/MutableConstant` cop (#33602) --- .rubocop_todo.yml | 9 --------- app/models/tag.rb | 8 ++++---- app/services/delete_account_service.rb | 2 +- lib/mastodon/migration_warning.rb | 2 +- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 38aec67befaf1c..f2d3418b64aa0d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -69,15 +69,6 @@ Style/MapToHash: Exclude: - 'app/models/status.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: literals, strict -Style/MutableConstant: - Exclude: - - 'app/models/tag.rb' - - 'app/services/delete_account_service.rb' - - 'lib/mastodon/migration_warning.rb' - # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: diff --git a/app/models/tag.rb b/app/models/tag.rb index c9115b905b38ab..d29cd220f036b0 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -35,11 +35,11 @@ class Tag < ApplicationRecord has_one :trend, class_name: 'TagTrend', inverse_of: :tag, dependent: :destroy HASHTAG_SEPARATORS = "_\u00B7\u30FB\u200c" - HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]" - HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]" - HASHTAG_FIRST_SEQUENCE = "(#{HASHTAG_FIRST_SEQUENCE_CHUNK_ONE}#{HASHTAG_FIRST_SEQUENCE_CHUNK_TWO})" + HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]".freeze + HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]".freeze + HASHTAG_FIRST_SEQUENCE = "(#{HASHTAG_FIRST_SEQUENCE_CHUNK_ONE}#{HASHTAG_FIRST_SEQUENCE_CHUNK_TWO})".freeze HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' - HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}" + HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze HASHTAG_RE = %r{(? Date: Thu, 16 Jan 2025 10:13:39 +0100 Subject: [PATCH 63/73] New Crowdin Translations (automated) (#33609) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/sk.json | 9 +++++++++ config/locales/activerecord.de.yml | 2 ++ config/locales/eo.yml | 7 +++++++ config/locales/simple_form.de.yml | 1 + config/locales/sk.yml | 2 ++ 5 files changed, 21 insertions(+) diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index d0617a52c84b5f..00eb948985dda6 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -378,6 +378,8 @@ "ignore_notifications_modal.not_followers_title": "Nevšímať si oznámenia od ľudí, ktorí ťa nenasledujú?", "ignore_notifications_modal.not_following_title": "Nevšímať si oznámenia od ľudí, ktorých nenasleduješ?", "ignore_notifications_modal.private_mentions_title": "Nevšímať si oznámenia o nevyžiadaných súkromných spomínaniach?", + "interaction_modal.action.favourite": "Pre pokračovanie si musíš obľúbiť zo svojho účtu.", + "interaction_modal.action.follow": "Pre pokračovanie musíš nasledovať zo svojho účtu.", "interaction_modal.action.reply": "Pre pokračovanie musíš odpovedať s tvojho účtu.", "interaction_modal.action.vote": "Pre pokračovanie musíš hlasovať s tvojho účtu.", "interaction_modal.go": "Prejdi", @@ -389,6 +391,7 @@ "interaction_modal.title.reblog": "Zdieľať príspevok od {name}", "interaction_modal.title.reply": "Odpovedať na príspevok od {name}", "interaction_modal.title.vote": "Hlasuj v ankete od {name}", + "interaction_modal.username_prompt": "Napr. {example}", "intervals.full.days": "{number, plural, one {# deň} few {# dni} many {# dní} other {# dní}}", "intervals.full.hours": "{number, plural, one {# hodina} few {# hodiny} many {# hodín} other {# hodín}}", "intervals.full.minutes": "{number, plural, one {# minúta} few {# minúty} many {# minút} other {# minút}}", @@ -517,6 +520,7 @@ "notification.moderation_warning": "Dostal/a si varovanie od moderátora", "notification.moderation_warning.action_delete_statuses": "Niektoré z tvojich príspevkov boli odstránené.", "notification.moderation_warning.action_disable": "Tvoj účet bol vypnutý.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Niektoré tvoje príspevky boli označené za chúlostivé.", "notification.moderation_warning.action_none": "Tvoj účet dostal upozornenie od moderátora.", "notification.moderation_warning.action_sensitive": "Tvoje príspevky budú odteraz označované ako chúlostivé.", "notification.moderation_warning.action_silence": "Tvoj účet bol obmedzený.", @@ -575,9 +579,11 @@ "notifications.policy.accept_hint": "Ukáž v oznámeniach", "notifications.policy.drop": "Ignoruj", "notifications.policy.filter": "Triediť", + "notifications.policy.filter_limited_accounts_hint": "Obmedzené moderátormi servera", "notifications.policy.filter_limited_accounts_title": "Moderované účty", "notifications.policy.filter_new_accounts_title": "Nové účty", "notifications.policy.filter_not_followers_title": "Ľudia, ktorí ťa nenasledujú", + "notifications.policy.filter_not_following_hint": "Pokiaľ ich ručne neschváliš", "notifications.policy.filter_not_following_title": "Ľudia, ktorých nenasleduješ", "notifications.policy.filter_private_mentions_title": "Nevyžiadané priame spomenutia", "notifications.policy.title": "Spravuj oznámenia od…", @@ -625,6 +631,7 @@ "privacy_policy.title": "Pravidlá ochrany súkromia", "recommended": "Odporúčané", "refresh": "Obnoviť", + "regeneration_indicator.please_stand_by": "Prosím, čakajte.", "regeneration_indicator.preparing_your_home_feed": "Pripravuje sa tvoj domáci kanál…", "relative_time.days": "{number} dní", "relative_time.full.days": "Pred {number, plural, one {# dňom} other {# dňami}}", @@ -716,6 +723,7 @@ "server_banner.about_active_users": "Ľudia používajúci tento server za posledných 30 dní (aktívni používatelia za mesiac)", "server_banner.active_users": "Aktívne účty", "server_banner.administered_by": "Správa servera:", + "server_banner.is_one_of_many": "{domain} je jeden z mnohých nezávislých Mastodon serverov, ktoré môžeš použiť na zúčastňovanie sa v rámci fediversa.", "server_banner.server_stats": "Štatistiky servera:", "sign_in_banner.create_account": "Vytvoriť účet", "sign_in_banner.sign_in": "Prihlásiť sa", @@ -758,6 +766,7 @@ "status.reblogs.empty": "Nikto ešte tento príspevok nezdieľal. Keď tak niekto urobí, zobrazí sa to tu.", "status.redraft": "Vymazať a prepísať", "status.remove_bookmark": "Odstrániť záložku", + "status.remove_favourite": "Odstráň z obľúbených", "status.replied_in_thread": "Odpovedal/a vo vlákne", "status.replied_to": "Odpoveď na {name}", "status.reply": "Odpovedať", diff --git a/config/locales/activerecord.de.yml b/config/locales/activerecord.de.yml index bdd916634a6a1c..99d50d49e44171 100644 --- a/config/locales/activerecord.de.yml +++ b/config/locales/activerecord.de.yml @@ -24,6 +24,8 @@ de: models: account: attributes: + fields: + fields_with_values_missing_labels: enthält Werte, bei denen Beschriftungen fehlen username: invalid: nur Buchstaben, Ziffern und Unterstriche reserved: ist bereits vergeben diff --git a/config/locales/eo.yml b/config/locales/eo.yml index ee45921aaa6379..400b03958ef6e7 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -936,17 +936,22 @@ eo: generate: Uzi ŝablonon generates: action: Generi + chance_to_review_html: "La generataj kondiĉoj de uzado ne aŭtomate publikiĝos. Estos oportuni por vi kontroli la rezultojn. Bonvole entajpu la necesajn detalojn por daŭrigi." + explanation_html: La modelo por la kondiĉoj de la servo disponeblas sole por informi. Ĝi nepre ne estas leĝa konsilo pri iu ajn temo. Bonvole konsultu vian propran leĝan konsilanton pri via situacio kaj iuj leĝaj neklarecoj. title: Agordo de kondiĉoj de uzado history: Historio live: Antaŭmontro no_history: Ankoraŭ ne estas registritaj ŝanĝoj de la kondiĉoj de la servo. no_terms_of_service_html: Vi nuntempe ne havas iujn ajn kondiĉojn de la servo agordita. La kondiĉoj de la servo celas doni klarecon kaj protekti vin kontraŭ eblaj respondecoj en disputoj kun viaj uzantoj. + notified_on_html: Uzantojn sciigis je %{date} notify_users: Informu uzantojn preview: + explanation_html: 'La retmesaĝo estos alsendata al %{display_count} uzantoj, kiuj kreis konton antaŭ %{date}. La sekvonta teksto inkluziviĝos en la retmesaĝo:' send_preview: Sendu antaŭrigardon al %{email} send_to_all: one: Sendi %{display_count} retpoŝton other: Sendi %{display_count} retpoŝtojn + title: Antaŭmontri sciigon pri la kondiĉoj de la servo publish: Publikigi published_on_html: Publikigita je %{date} save_draft: Konservi malneton @@ -1930,6 +1935,8 @@ eo: subject: Via konto estas alirita de nova IP-adreso title: Nova saluto terms_of_service_changed: + agreement: Se vi daŭrige uzos %{domain}, vi aŭtomate interkonsentos pri ĉi tiuj kondiĉoj. Se vi malkonsentas pri la novaj kondiĉoj, vi ĉiutempe rajtas nuligi la interkonsenton kun %{domain} per forigi vian konton. + changelog: 'Facile dirite, la ŝanĝoj estas la jenaj:' description: 'Vi ricevas ĉi tiun retmesaĝon ĉar ni faras iujn ŝanĝojn al niaj servokondiĉoj ĉe %{domain}. Ni instigas vin revizii la ĝisdatigitajn kondiĉojn tute ĉi tie:' description_html: Vi ricevas ĉi tiun retmesaĝon ĉar ni faras iujn ŝanĝojn al niaj servokondiĉoj ĉe %{domain}. Ni instigas vin revizii la ĝisdatigitajn kondiĉojn plene ĉi tie. sign_off: La teamo de %{domain} diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 201af831ad4371..ea0681e1af9eff 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -137,6 +137,7 @@ de: admin_email: Rechtliche Hinweise umfassen Gegendarstellungen, Gerichtsbeschlüsse, Anfragen zum Herunternehmen von Inhalten und Anfragen von Strafverfolgungsbehörden. arbitration_address: Kann wie die Anschrift hierüber sein oder „N/A“, falls eine E-Mail verwendet wird arbitration_website: Kann ein Webformular sein oder „N/A“, falls eine E-Mail verwendet wird + dmca_address: US-Betreiber sollten die im „DMCA Designated Agent Directory“ eingetragene Adresse verwenden. Eine Postfachadresse ist auf direkte Anfrage verfügbar. Verwenden Sie die „DMCA Designated Agent Post Box Waiver“-Anfrage, um per E-Mail die Urheberrechtsbehörde darüber zu unterrichten, dass Sie Inhalte per Heimarbeit moderieren, eventuelle Rache oder Vergeltung für Ihre Handlungen befürchten und deshalb eine Postfachadresse benötigen, um Ihre Privatadresse nicht preiszugeben. dmca_email: Kann dieselbe E-Mail wie bei „E-Mail-Adresse für rechtliche Hinweise“ sein domain: Einzigartige Identifizierung des angebotenen Online-Services. jurisdiction: Gib das Land an, in dem die Person lebt, die alle Rechnungen bezahlt. Falls es sich dabei um ein Unternehmen oder eine andere Einrichtung handelt, gib das Land mit dem Sitz an, sowie die Stadt oder Region. diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 72ff7af37fc810..e7fb218e2d68cc 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -28,6 +28,7 @@ sk: admin: account_actions: action: Vykonaj + already_silenced: Tento účet už bol obmedzený. title: Vykonaj moderovací úkon voči %{acct} account_moderation_notes: create: Zanechaj poznámku @@ -204,6 +205,7 @@ sk: enable_user: Povoľ užívateľa memorialize_account: Zmena na „in memoriam“ promote_user: Povýš užívateľskú rolu + publish_terms_of_service: Zverejni podmienky prevozu reject_appeal: Zamietni námietku reject_user: Zamietni užívateľa remove_avatar_user: Vymaž avatar From da4e55eb17e459fbc6d1a19fac3303508324324c Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 16 Jan 2025 11:10:08 +0100 Subject: [PATCH 64/73] Merge commit from fork --- app/lib/delivery_failure_tracker.rb | 2 ++ .../activitypub/process_account_service.rb | 27 ++++++++++++++----- spec/lib/delivery_failure_tracker_spec.rb | 4 +-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/lib/delivery_failure_tracker.rb b/app/lib/delivery_failure_tracker.rb index e17b45d667a354..96292923f4289f 100644 --- a/app/lib/delivery_failure_tracker.rb +++ b/app/lib/delivery_failure_tracker.rb @@ -46,6 +46,8 @@ def without_unavailable(urls) urls.reject do |url| host = Addressable::URI.parse(url).normalized_host unavailable_domains_map[host] + rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError + true end end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index df6f23c0216caf..e5c23197284eda 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -9,6 +9,8 @@ class ActivityPub::ProcessAccountService < BaseService SUBDOMAINS_RATELIMIT = 10 DISCOVERIES_PER_REQUEST = 400 + VALID_URI_SCHEMES = %w(http https).freeze + # Should be called with confirmed valid JSON # and WebFinger-resolved username and domain def call(username, domain, json, options = {}) @@ -96,16 +98,28 @@ def update_account end def set_immediate_protocol_attributes! - @account.inbox_url = @json['inbox'] || '' - @account.outbox_url = @json['outbox'] || '' - @account.shared_inbox_url = (@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) || '' - @account.followers_url = @json['followers'] || '' + @account.inbox_url = valid_collection_uri(@json['inbox']) + @account.outbox_url = valid_collection_uri(@json['outbox']) + @account.shared_inbox_url = valid_collection_uri(@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) + @account.followers_url = valid_collection_uri(@json['followers']) @account.url = url || @uri @account.uri = @uri @account.actor_type = actor_type @account.created_at = @json['published'] if @json['published'].present? end + def valid_collection_uri(uri) + uri = uri.first if uri.is_a?(Array) + uri = uri['id'] if uri.is_a?(Hash) + return '' unless uri.is_a?(String) + + parsed_uri = Addressable::URI.parse(uri) + + VALID_URI_SCHEMES.include?(parsed_uri.scheme) && parsed_uri.host.present? ? parsed_uri : '' + rescue Addressable::URI::InvalidURIError + '' + end + def set_immediate_attributes! @account.featured_collection_url = @json['featured'] || '' @account.display_name = @json['name'] || '' @@ -268,10 +282,11 @@ def followers_private? end def collection_info(type) - return [nil, nil] if @json[type].blank? + collection_uri = valid_collection_uri(@json[type]) + return [nil, nil] if collection_uri.blank? return @collections[type] if @collections.key?(type) - collection = fetch_resource_without_id_validation(@json[type]) + collection = fetch_resource_without_id_validation(collection_uri) total_items = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil has_first_page = collection.is_a?(Hash) && collection['first'].present? diff --git a/spec/lib/delivery_failure_tracker_spec.rb b/spec/lib/delivery_failure_tracker_spec.rb index 40c8adc4c80620..34912c8133e65c 100644 --- a/spec/lib/delivery_failure_tracker_spec.rb +++ b/spec/lib/delivery_failure_tracker_spec.rb @@ -42,8 +42,8 @@ Fabricate(:unavailable_domain, domain: 'foo.bar') end - it 'removes URLs that are unavailable' do - results = described_class.without_unavailable(['http://example.com/good/inbox', 'http://foo.bar/unavailable/inbox']) + it 'removes URLs that are bogus or unavailable' do + results = described_class.without_unavailable(['http://example.com/good/inbox', 'http://foo.bar/unavailable/inbox', '{foo:']) expect(results).to include('http://example.com/good/inbox') expect(results).to_not include('http://foo.bar/unavailable/inbox') From 0aa9bb8130b77b01f6a08c64b7e93876d892868b Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 16 Jan 2025 12:02:54 +0100 Subject: [PATCH 65/73] Bump version to v4.4.0-alpha.2 (#33615) --- CHANGELOG.md | 18 ++++++++++++++++++ docker-compose.yml | 6 +++--- lib/mastodon/version.rb | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea18b3cb92a48c..ef6a87ebb94797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. +## [4.3.3] - 2025-01-16 + +### Security + +- Fix insufficient validation of account URIs ([GHSA-5wxh-3p65-r4g6](https://github.com/mastodon/mastodon/security/advisories/GHSA-5wxh-3p65-r4g6)) +- Update dependencies + +### Fixed + +- Fix `libyaml` missing from `Dockerfile` build stage (#33591 by @vmstan) +- Fix incorrect notification settings migration for non-followers (#33348 by @ClearlyClaire) +- Fix down clause for notification policy v2 migrations (#33340 by @jesseplusplus) +- Fix error decrementing status count when `FeaturedTags#last_status_at` is `nil` (#33320 by @ClearlyClaire) +- Fix last paginated notification group only including data on a single notification (#33271 by @ClearlyClaire) +- Fix processing of mentions for post edits with an existing corresponding silent mention (#33227 by @ClearlyClaire) +- Fix deletion of unconfirmed users with Webauthn set (#33186 by @ClearlyClaire) +- Fix empty authors preview card serialization (#33151, #33466 by @mjankowski and @ClearlyClaire) + ## [4.3.2] - 2024-12-03 ### Added diff --git a/docker-compose.yml b/docker-compose.yml index 63f17bf4955cf1..6c4497756211c1 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.3.2 + image: ghcr.io/mastodon/mastodon:v4.3.3 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.3.2 + image: ghcr.io/mastodon/mastodon-streaming:v4.3.3 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.3.2 + image: ghcr.io/mastodon/mastodon:v4.3.3 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 19779eb609e3a8..3aa93bbba6a29d 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -17,7 +17,7 @@ def patch end def default_prerelease - 'alpha.1' + 'alpha.2' end def prerelease From 69b8a0b9bfef54d73be39527335ce6a0863f9f30 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 09:58:59 -0500 Subject: [PATCH 66/73] Collect errors in setup rake task (#33603) --- lib/tasks/mastodon.rake | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 692fe0a5071d90..2338b4393f2ba6 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -17,7 +17,7 @@ namespace :mastodon do ENV.delete('SIDEKIQ_REDIS_URL') begin - errors = false + errors = [] prompt.say('Your instance is identified by its domain name. Changing it afterward will break things.') env['LOCAL_DOMAIN'] = prompt.ask('Domain name:') do |q| @@ -109,7 +109,7 @@ namespace :mastodon do unless prompt.yes?('Try again?') return prompt.warn 'Nothing saved. Bye!' unless prompt.yes?('Continue anyway?') - errors = true + errors << 'Database connection could not be established.' break end end @@ -155,7 +155,7 @@ namespace :mastodon do unless prompt.yes?('Try again?') return prompt.warn 'Nothing saved. Bye!' unless prompt.yes?('Continue anyway?') - errors = true + errors << 'Redis connection could not be established.' break end end @@ -450,7 +450,7 @@ namespace :mastodon do unless prompt.yes?('Try again?') return prompt.warn 'Nothing saved. Bye!' unless prompt.yes?('Continue anyway?') - errors = true + errors << 'E-email was not sent successfully.' break end end @@ -498,7 +498,7 @@ namespace :mastodon do prompt.ok 'Done!' else prompt.error 'That failed! Perhaps your configuration is not right' - errors = true + errors << 'Preparing the database failed' end end @@ -515,14 +515,15 @@ namespace :mastodon do prompt.say 'Done!' else prompt.error 'That failed! Maybe you need swap space?' - errors = true + errors << 'Compiling assets failed.' end end end prompt.say "\n" - if errors - prompt.warn 'Your Mastodon server is set up, but there were some errors along the way, you may have to fix them.' + if errors.any? + prompt.warn 'Your Mastodon server is set up, but there were some errors along the way, you may have to fix them:' + errors.each { |error| prompt.warn "- #{error}" } else prompt.ok 'All done! You can now power on the Mastodon server 🐘' end From da208771b9377c210065570ab6dfc387153f058d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:23:32 +0100 Subject: [PATCH 67/73] Update opentelemetry-ruby (non-major) (#33613) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 26 ++++++------ Gemfile.lock | 116 +++++++++++++++++++++++++-------------------------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/Gemfile b/Gemfile index 0efcc6562c4897..5becc118d30bfa 100644 --- a/Gemfile +++ b/Gemfile @@ -104,19 +104,19 @@ gem 'opentelemetry-api', '~> 1.4.0' group :opentelemetry do gem 'opentelemetry-exporter-otlp', '~> 0.29.0', require: false - gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false - gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.21.0', require: false - gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false - gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false - gem 'opentelemetry-instrumentation-faraday', '~> 0.25.0', require: false - gem 'opentelemetry-instrumentation-http', '~> 0.23.2', require: false - gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false - gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false - gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false - gem 'opentelemetry-instrumentation-rack', '~> 0.25.0', require: false - gem 'opentelemetry-instrumentation-rails', '~> 0.34.0', require: false - gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false - gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false + gem 'opentelemetry-instrumentation-active_job', '~> 0.8.0', require: false + gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false + gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false + gem 'opentelemetry-instrumentation-excon', '~> 0.23.0', require: false + gem 'opentelemetry-instrumentation-faraday', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-http', '~> 0.24.0', require: false + gem 'opentelemetry-instrumentation-http_client', '~> 0.23.0', require: false + gem 'opentelemetry-instrumentation-net_http', '~> 0.23.0', require: false + gem 'opentelemetry-instrumentation-pg', '~> 0.30.0', require: false + gem 'opentelemetry-instrumentation-rack', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-rails', '~> 0.35.0', require: false + gem 'opentelemetry-instrumentation-redis', '~> 0.26.0', require: false + gem 'opentelemetry-instrumentation-sidekiq', '~> 0.26.0', require: false gem 'opentelemetry-sdk', '~> 1.4', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index a4c5f8ad9827e3..f5e3ac23a1565e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -473,78 +473,78 @@ GEM opentelemetry-common (~> 0.20) opentelemetry-sdk (~> 1.2) opentelemetry-semantic_conventions - opentelemetry-helpers-sql-obfuscation (0.2.1) + opentelemetry-helpers-sql-obfuscation (0.3.0) opentelemetry-common (~> 0.21) - opentelemetry-instrumentation-action_mailer (0.3.0) + opentelemetry-instrumentation-action_mailer (0.4.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-active_support (~> 0.7) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-action_pack (0.10.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-action_pack (0.11.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-rack (~> 0.21) - opentelemetry-instrumentation-action_view (0.8.0) + opentelemetry-instrumentation-action_view (0.9.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-active_support (~> 0.7) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_job (0.7.8) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-active_job (0.8.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_model_serializers (0.21.1) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-active_model_serializers (0.22.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-active_support (>= 0.7.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_record (0.8.1) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-active_record (0.9.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_support (0.7.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-active_support (0.8.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-base (0.22.6) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-base (0.23.0) opentelemetry-api (~> 1.0) opentelemetry-common (~> 0.21) opentelemetry-registry (~> 0.1) - opentelemetry-instrumentation-concurrent_ruby (0.21.4) + opentelemetry-instrumentation-concurrent_ruby (0.22.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-excon (0.22.5) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-excon (0.23.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-faraday (0.25.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-faraday (0.26.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-http (0.23.5) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-http (0.24.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-http_client (0.22.8) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-http_client (0.23.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-net_http (0.22.8) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-net_http (0.23.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-pg (0.29.2) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-pg (0.30.0) opentelemetry-api (~> 1.0) opentelemetry-helpers-sql-obfuscation - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-rack (0.25.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-rack (0.26.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-rails (0.34.1) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-rails (0.35.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-action_mailer (~> 0.3.0) - opentelemetry-instrumentation-action_pack (~> 0.10.0) - opentelemetry-instrumentation-action_view (~> 0.8.0) - opentelemetry-instrumentation-active_job (~> 0.7.0) - opentelemetry-instrumentation-active_record (~> 0.8.0) - opentelemetry-instrumentation-active_support (~> 0.7.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-concurrent_ruby (~> 0.21.4) - opentelemetry-instrumentation-redis (0.25.7) + opentelemetry-instrumentation-action_mailer (~> 0.4.0) + opentelemetry-instrumentation-action_pack (~> 0.11.0) + opentelemetry-instrumentation-action_view (~> 0.9.0) + opentelemetry-instrumentation-active_job (~> 0.8.0) + opentelemetry-instrumentation-active_record (~> 0.9.0) + opentelemetry-instrumentation-active_support (~> 0.8.0) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) + opentelemetry-instrumentation-redis (0.26.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-sidekiq (0.25.7) + opentelemetry-instrumentation-base (~> 0.23.0) + opentelemetry-instrumentation-sidekiq (0.26.0) opentelemetry-api (~> 1.0) - opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-registry (0.3.1) opentelemetry-api (~> 1.1) opentelemetry-sdk (1.6.0) @@ -960,19 +960,19 @@ DEPENDENCIES omniauth_openid_connect (~> 0.6.1) opentelemetry-api (~> 1.4.0) opentelemetry-exporter-otlp (~> 0.29.0) - opentelemetry-instrumentation-active_job (~> 0.7.1) - opentelemetry-instrumentation-active_model_serializers (~> 0.21.0) - opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2) - opentelemetry-instrumentation-excon (~> 0.22.0) - opentelemetry-instrumentation-faraday (~> 0.25.0) - opentelemetry-instrumentation-http (~> 0.23.2) - opentelemetry-instrumentation-http_client (~> 0.22.3) - opentelemetry-instrumentation-net_http (~> 0.22.4) - opentelemetry-instrumentation-pg (~> 0.29.0) - opentelemetry-instrumentation-rack (~> 0.25.0) - opentelemetry-instrumentation-rails (~> 0.34.0) - opentelemetry-instrumentation-redis (~> 0.25.3) - opentelemetry-instrumentation-sidekiq (~> 0.25.2) + opentelemetry-instrumentation-active_job (~> 0.8.0) + opentelemetry-instrumentation-active_model_serializers (~> 0.22.0) + opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) + opentelemetry-instrumentation-excon (~> 0.23.0) + opentelemetry-instrumentation-faraday (~> 0.26.0) + opentelemetry-instrumentation-http (~> 0.24.0) + opentelemetry-instrumentation-http_client (~> 0.23.0) + opentelemetry-instrumentation-net_http (~> 0.23.0) + opentelemetry-instrumentation-pg (~> 0.30.0) + opentelemetry-instrumentation-rack (~> 0.26.0) + opentelemetry-instrumentation-rails (~> 0.35.0) + opentelemetry-instrumentation-redis (~> 0.26.0) + opentelemetry-instrumentation-sidekiq (~> 0.26.0) opentelemetry-sdk (~> 1.4) ox (~> 2.14) parslet From 9e2f930eed8ac4653f795dd0c74e6ac2f11396cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:23:37 +0100 Subject: [PATCH 68/73] Update dependency aws-sdk-s3 to v1.178.0 (#33611) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f5e3ac23a1565e..29f410bdec0838 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -94,20 +94,20 @@ GEM ast (2.4.2) attr_required (1.0.2) aws-eventstream (1.3.0) - aws-partitions (1.1032.0) - aws-sdk-core (3.214.1) + aws-partitions (1.1038.0) + aws-sdk-core (3.216.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.96.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-kms (1.97.0) + aws-sdk-core (~> 3, >= 3.216.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.177.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-s3 (1.178.0) + aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sigv4 (1.10.1) + aws-sigv4 (1.11.0) aws-eventstream (~> 1, >= 1.0.2) azure-blob (0.5.4) rexml From 29b355b6997a7c8f2ab37763fb90dbdb34e337f6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:24:31 +0000 Subject: [PATCH 69/73] Update dependency concurrent-ruby to v1.3.5 (#33610) 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 29f410bdec0838..80c733d2021f23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -159,7 +159,7 @@ GEM climate_control (1.2.0) cocoon (1.2.15) color_diff (0.1) - concurrent-ruby (1.3.4) + concurrent-ruby (1.3.5) connection_pool (2.5.0) cose (1.3.1) cbor (~> 0.5.9) From a19141441d462638fc1d994c9e845492164532f1 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 10:45:02 -0500 Subject: [PATCH 70/73] Move clear environment portion of `mastodon:setup` to private method (#33616) --- lib/tasks/mastodon.rake | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 2338b4393f2ba6..0081b4a8f604c1 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -8,13 +8,7 @@ namespace :mastodon do prompt = TTY::Prompt.new env = {} - # When the application code gets loaded, it runs `lib/mastodon/redis_configuration.rb`. - # This happens before application environment configuration and sets REDIS_URL etc. - # These variables are then used even when REDIS_HOST etc. are changed, so clear them - # out so they don't interfere with our new configuration. - ENV.delete('REDIS_URL') - ENV.delete('CACHE_REDIS_URL') - ENV.delete('SIDEKIQ_REDIS_URL') + clear_environment! begin errors = [] @@ -580,6 +574,17 @@ namespace :mastodon do private + def clear_environment! + # When the application code gets loaded, it runs `lib/mastodon/redis_configuration.rb`. + # This happens before application environment configuration and sets REDIS_URL etc. + # These variables are then used even when REDIS_HOST etc. are changed, so clear them + # out so they don't interfere with our new configuration. + + ENV.delete('REDIS_URL') + ENV.delete('CACHE_REDIS_URL') + ENV.delete('SIDEKIQ_REDIS_URL') + end + def generate_header(include_warning) default_message = "# Generated with mastodon:setup on #{Time.now.utc}\n\n" From 3af6739f21f5a479d1dd4d2816775c4278722b08 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 10:45:48 -0500 Subject: [PATCH 71/73] Add coverage for `AnnualReport.prepare` method (#33618) --- spec/lib/annual_report_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/lib/annual_report_spec.rb b/spec/lib/annual_report_spec.rb index bd4d0f33876a06..fa898d3ac551bd 100644 --- a/spec/lib/annual_report_spec.rb +++ b/spec/lib/annual_report_spec.rb @@ -13,4 +13,13 @@ .to change(GeneratedAnnualReport, :count).by(1) end end + + describe '.prepare' do + before { Fabricate :status } + + it 'generates records from source class which prepare data' do + expect { described_class.prepare(Time.current.year) } + .to change(AnnualReport::StatusesPerAccountCount, :count).by(1) + end + end end From 02450da9a5586568a2803c7186e546567adc110a Mon Sep 17 00:00:00 2001 From: KMY Date: Fri, 17 Jan 2025 16:34:58 +0900 Subject: [PATCH 72/73] Fix lint --- app/services/concerns/payloadable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/concerns/payloadable.rb b/app/services/concerns/payloadable.rb index 113b389fac2e53..4fa534c6d18d0c 100644 --- a/app/services/concerns/payloadable.rb +++ b/app/services/concerns/payloadable.rb @@ -17,7 +17,7 @@ def serialize_payload(record, serializer, options = {}) always_sign_unsafe = options.delete(:always_sign_unsafe) payload = ActiveModelSerializers::SerializableResource.new(record, options.merge(serializer: serializer, adapter: ActivityPub::Adapter)).as_json object = record.respond_to?(:virtual_object) ? record.virtual_object : record - bearcap = object.is_a?(String) && record.respond_to?(:type) && (record.type == 'Create' || record.type == 'Update') + bearcap = object.is_a?(String) && record.respond_to?(:type) && ['Create', 'Update'].include?(record.type) if ((object.respond_to?(:sign?) && object.sign?) && signer && (always_sign || signing_enabled?)) || bearcap || (signer && always_sign_unsafe) ActivityPub::LinkedDataSignature.new(payload).sign!(signer, sign_with: sign_with) From 16c90bf5f06f47b21b4785c3b5e9e6b0701bfa1d Mon Sep 17 00:00:00 2001 From: KMY Date: Fri, 17 Jan 2025 16:55:15 +0900 Subject: [PATCH 73/73] Fix test --- spec/support/examples/models/concerns/status/visibility.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/examples/models/concerns/status/visibility.rb b/spec/support/examples/models/concerns/status/visibility.rb index 7b26b8fdeb25f6..e9b97dbf796abe 100644 --- a/spec/support/examples/models/concerns/status/visibility.rb +++ b/spec/support/examples/models/concerns/status/visibility.rb @@ -110,7 +110,7 @@ describe '.selectable_visibilities' do it 'returns options available for default privacy selection' do expect(Status.selectable_visibilities) - .to match(%w(public unlisted private)) + .to match(%w(public public_unlisted login unlisted private)) end end