From 3f81e7dcc8afbb86e48c36773fe2bc7018b44379 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:16:24 +0200 Subject: [PATCH 01/84] chore(deps): update dependency @types/http-link-header to v1.0.6 (#30814) 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 ff4e1110d..b6117d821 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3622,11 +3622,11 @@ __metadata: linkType: hard "@types/http-link-header@npm:^1.0.3": - version: 1.0.5 - resolution: "@types/http-link-header@npm:1.0.5" + version: 1.0.6 + resolution: "@types/http-link-header@npm:1.0.6" dependencies: "@types/node": "npm:*" - checksum: 10c0/adeb13381b38c3625478149820772924c154b4a7250dca62c346810a8378f8968fc7f3a9a4f55ec61de5d06083637540f862c8a920f6a710310c9645d19a077d + checksum: 10c0/63f3f7ab5ff6312280727ba8cf836abf5d1b76f9dc5eefc8cd4389db29d57a72fb0e028db99735ada5ccfd3c2cc6607e096b5cc142fc53c2bb5688b6295f61af languageName: node linkType: hard From 6d14cfbf298eec6e2ffcd4ad69a118cdd168b3a1 Mon Sep 17 00:00:00 2001 From: Nick Schonning <nschonni@gmail.com> Date: Mon, 24 Jun 2024 06:18:36 -0400 Subject: [PATCH 02/84] Unset Rails/UnusedIgnoredColumns (#30800) --- .rubocop/rails.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.rubocop/rails.yml b/.rubocop/rails.yml index 4e08f1ab9..68c90143a 100644 --- a/.rubocop/rails.yml +++ b/.rubocop/rails.yml @@ -18,6 +18,3 @@ Rails/RakeEnvironment: Rails/SkipsModelValidations: Enabled: false - -Rails/UnusedIgnoredColumns: - Enabled: false # Preserve ability to migrate from arbitrary old versions From b3710098a8edcb5cc317a280758b2a772ea722ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:18:51 +0200 Subject: [PATCH 03/84] chore(deps): update dependency opentelemetry-instrumentation-faraday to v0.24.5 (#30797) 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 0fe1c03b2..abd31f49a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -532,7 +532,7 @@ GEM opentelemetry-instrumentation-excon (0.22.3) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-faraday (0.24.4) + opentelemetry-instrumentation-faraday (0.24.5) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-http (0.23.3) From 54cc204473302eda7e6e7e75b343a14859524ab1 Mon Sep 17 00:00:00 2001 From: Essem <smswessem@gmail.com> Date: Mon, 24 Jun 2024 05:29:00 -0500 Subject: [PATCH 04/84] Use WebSocketServer instead of WebSocket.Server in streaming (#30788) --- streaming/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/streaming/index.js b/streaming/index.js index 154ecbc02..65a63bb11 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -12,7 +12,7 @@ import { Redis } from 'ioredis'; import { JSDOM } from 'jsdom'; import pg from 'pg'; import pgConnectionString from 'pg-connection-string'; -import WebSocket from 'ws'; +import { WebSocketServer } from 'ws'; import { AuthenticationError, RequestError, extractStatusAndMessage as extractErrorStatusAndMessage } from './errors.js'; import { logger, httpLogger, initializeLogLevel, attachWebsocketHttpLogger, createWebsocketLogger } from './logging.js'; @@ -289,7 +289,7 @@ const CHANNEL_NAMES = [ const startServer = async () => { const pgPool = new pg.Pool(pgConfigFromEnv(process.env)); const server = http.createServer(); - const wss = new WebSocket.Server({ noServer: true }); + const wss = new WebSocketServer({ noServer: true }); // Set the X-Request-Id header on WebSockets: wss.on("headers", function onHeaders(headers, req) { From 1af6313ced5b39041cb8c480b1f0c65155429238 Mon Sep 17 00:00:00 2001 From: Essem <smswessem@gmail.com> Date: Mon, 24 Jun 2024 05:36:26 -0500 Subject: [PATCH 05/84] Fix CMD syntax in streaming Dockerfile (#30795) --- streaming/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/Dockerfile b/streaming/Dockerfile index 319d5b7fd..d9f7615fa 100644 --- a/streaming/Dockerfile +++ b/streaming/Dockerfile @@ -110,4 +110,4 @@ USER mastodon # Expose default Streaming ports EXPOSE 4000 # Run streaming when started -CMD [ node ./streaming/index.js ] +CMD [ "node", "./streaming/index.js" ] From 61722b1b1fb36ff5c3d6c470981f5fc6648e2d14 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:46:53 +0200 Subject: [PATCH 06/84] New Crowdin Translations (automated) (#30808) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/fil.json | 7 +++++++ app/javascript/mastodon/locales/pt-BR.json | 4 ++++ app/javascript/mastodon/locales/zh-TW.json | 2 +- config/locales/doorkeeper.pt-BR.yml | 2 ++ config/locales/pt-BR.yml | 1 + config/locales/ru.yml | 4 ++++ 6 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json index 9e459f767..b8a2987ef 100644 --- a/app/javascript/mastodon/locales/fil.json +++ b/app/javascript/mastodon/locales/fil.json @@ -118,6 +118,7 @@ "confirmations.delete_list.confirm": "Tanggalin", "confirmations.delete_list.message": "Sigurado ka bang gusto mong burahin ang listahang ito?", "confirmations.discard_edit_media.confirm": "Ipagpaliban", + "confirmations.domain_block.confirm": "Harangan ang serbiro", "confirmations.edit.confirm": "Baguhin", "confirmations.reply.confirm": "Tumugon", "conversation.mark_as_read": "Markahan bilang nabasa na", @@ -186,6 +187,7 @@ "follow_request.authorize": "Tanggapin", "follow_request.reject": "Tanggihan", "follow_suggestions.dismiss": "Huwag nang ipakita muli", + "follow_suggestions.popular_suggestion_longer": "Sikat sa {domain}", "follow_suggestions.view_all": "Tingnan lahat", "follow_suggestions.who_to_follow": "Sinong maaaring sundan", "footer.about": "Tungkol dito", @@ -220,6 +222,7 @@ "link_preview.author": "Ni/ng {name}", "lists.account.add": "Idagdag sa talaan", "lists.account.remove": "Tanggalin mula sa talaan", + "lists.delete": "Burahin ang talaan", "lists.new.create": "Idagdag sa talaan", "lists.new.title_placeholder": "Bagong pangalan ng talaan", "lists.replies_policy.title": "Ipakita ang mga tugon sa:", @@ -287,9 +290,13 @@ "reply_indicator.cancel": "Ipagpaliban", "report.block": "Harangan", "report.categories.other": "Iba pa", + "report.categories.violation": "Lumalabag ang nilalaman sa isa o higit pang mga patakaran ng serbiro", + "report.category.subtitle": "Piliin ang pinakamahusay na tugma", "report.category.title": "Sabihin mo sa amin kung anong nangyari sa {type} na ito", "report.close": "Tapos na", "report.next": "Sunod", + "report.placeholder": "Mga Karagdagang Puna", + "report.reasons.dislike": "Hindi ko gusto ito", "report.reasons.violation": "Lumalabag ito sa mga panuntunan ng serbiro", "report.reasons.violation_description": "Alam mo na lumalabag ito sa mga partikular na panuntunan", "report.rules.title": "Aling mga patakaran ang nilabag?", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index afe549054..4d3bd2d28 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -415,6 +415,7 @@ "limited_account_hint.title": "Este perfil foi ocultado pelos moderadores do {domain}.", "link_preview.author": "Por {name}", "link_preview.more_from_author": "Mais de {name}", + "link_preview.shares": "{count, plural, one {{counter} publicação} other {{counter} publicações}}", "lists.account.add": "Adicionar à lista", "lists.account.remove": "Remover da lista", "lists.delete": "Excluir lista", @@ -695,8 +696,11 @@ "server_banner.about_active_users": "Pessoas usando este servidor durante os últimos 30 dias (Usuários ativos mensalmente)", "server_banner.active_users": "usuários ativos", "server_banner.administered_by": "Administrado por:", + "server_banner.is_one_of_many": "{domain} é um dos muitos servidores Mastodon independentes que você pode usar para participar do fediverso.", "server_banner.server_stats": "Estatísticas do servidor:", "sign_in_banner.create_account": "Criar conta", + "sign_in_banner.follow_anyone": "Siga alguém pelo fediverso e veja tudo em ordem cronológica. Sem algoritmos, anúncios ou clickbait à vista.", + "sign_in_banner.mastodon_is": "O Mastodon é a melhor maneira de acompanhar o que está acontecendo.", "sign_in_banner.sign_in": "Entrar", "sign_in_banner.sso_redirect": "Entrar ou Registrar-se", "status.admin_account": "Abrir interface de moderação para @{name}", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index e6cd62162..4ab22daba 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -62,7 +62,7 @@ "account.requested": "正在等候審核。按一下以取消跟隨請求", "account.requested_follow": "{name} 要求跟隨您", "account.share": "分享 @{name} 的個人檔案", - "account.show_reblogs": "顯示來自 @{name} 的嘟文", + "account.show_reblogs": "顯示來自 @{name} 的轉嘟", "account.statuses_counter": "{count, plural,one {{counter} 則}other {{counter} 則}}嘟文", "account.unblock": "解除封鎖 @{name}", "account.unblock_domain": "解除封鎖網域 {domain}", diff --git a/config/locales/doorkeeper.pt-BR.yml b/config/locales/doorkeeper.pt-BR.yml index d7e9353b5..6b076e908 100644 --- a/config/locales/doorkeeper.pt-BR.yml +++ b/config/locales/doorkeeper.pt-BR.yml @@ -135,6 +135,7 @@ pt-BR: media: Mídias anexadas mutes: Silenciados notifications: Notificações + profile: Seu perfil do Mastodon push: Notificações push reports: Denúncias search: Buscar @@ -165,6 +166,7 @@ pt-BR: admin:write:reports: executar ações de moderação em denúncias crypto: usar criptografia de ponta-a-ponta follow: alterar o relacionamento das contas + profile: ler somente as informações do perfil da sua conta push: receber notificações push read: ler todos os dados da sua conta read:accounts: ver informações das contas diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 8d3b53f77..584a9253b 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -293,6 +293,7 @@ pt-BR: filter_by_action: Filtrar por ação filter_by_user: Filtrar por usuário title: Auditar histórico + unavailable_instance: "(nome de domínio indisponível)" announcements: destroyed_msg: Anúncio excluído! edit: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 6dff92bb6..5f5b3676f 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1699,6 +1699,7 @@ ru: import: Импорт import_and_export: Импорт и экспорт migrate: Миграция учётной записи + notifications: Уведомления по электронной почте preferences: Настройки profile: Профиль relationships: Подписки и подписчики @@ -1706,6 +1707,9 @@ ru: strikes: Замечания модерации two_factor_authentication: Подтверждение входа webauthn_authentication: Ключи безопасности + severed_relationships: + event_type: + user_domain_block: Вы заблокировали %{target_name} statuses: attached: audio: From 8827cd597e695c0368dfdce582755eda7f667272 Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Mon, 24 Jun 2024 15:11:10 +0200 Subject: [PATCH 07/84] Fix `/admin/accounts/:account_id/statuses/:id` for edited posts with media attachments (#30819) --- app/models/status_edit.rb | 2 +- spec/controllers/admin/statuses_controller_spec.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/status_edit.rb b/app/models/status_edit.rb index 50dabb91f..089c42fb9 100644 --- a/app/models/status_edit.rb +++ b/app/models/status_edit.rb @@ -42,7 +42,7 @@ class StatusEdit < ApplicationRecord scope :ordered, -> { order(id: :asc) } delegate :local?, :application, :edited?, :edited_at, - :discarded?, :visibility, to: :status + :discarded?, :visibility, :language, to: :status def emojis return @emojis if defined?(@emojis) diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb index 4e8bf9ead..4144d97d6 100644 --- a/spec/controllers/admin/statuses_controller_spec.rb +++ b/spec/controllers/admin/statuses_controller_spec.rb @@ -44,6 +44,11 @@ describe Admin::StatusesController do describe 'GET #show' do before do + status.media_attachments << Fabricate(:media_attachment, type: :image, account: status.account) + status.save! + status.snapshot!(at_time: status.created_at, rate_limit: false) + status.update!(text: 'Hello, this is an edited post') + status.snapshot!(rate_limit: false) get :show, params: { account_id: account.id, id: status.id } end From f6e466058a5a70eba5001c7ad3a80d86c07eb25d Mon Sep 17 00:00:00 2001 From: Tim Rogers <rogers.timothy.john@gmail.com> Date: Mon, 24 Jun 2024 09:41:04 -0500 Subject: [PATCH 08/84] Added check for STATSD_ADDR setting to emit a warning and proceed rather than crashing if the address is unreachable (#30691) --- config/initializers/statsd.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/config/initializers/statsd.rb b/config/initializers/statsd.rb index a655c1071..f1628a9d1 100644 --- a/config/initializers/statsd.rb +++ b/config/initializers/statsd.rb @@ -3,13 +3,17 @@ if ENV['STATSD_ADDR'].present? host, port = ENV['STATSD_ADDR'].split(':') - statsd = Statsd.new(host, port) - statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') } + begin + statsd = Statsd.new(host, port) + statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') } - NSA.inform_statsd(statsd) do |informant| - informant.collect(:action_controller, :web) - informant.collect(:active_record, :db) - informant.collect(:active_support_cache, :cache) - informant.collect(:sidekiq, :sidekiq) if ENV['STATSD_SIDEKIQ'] == 'true' + NSA.inform_statsd(statsd) do |informant| + informant.collect(:action_controller, :web) + informant.collect(:active_record, :db) + informant.collect(:active_support_cache, :cache) + informant.collect(:sidekiq, :sidekiq) if ENV['STATSD_SIDEKIQ'] == 'true' + end + rescue + Rails.logger.warn("statsd address #{ENV['STATSD_ADDR']} not reachable, proceeding without statsd") end end From 39d80e84be660e6d3eb121ee8f1067bd87cc3783 Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Mon, 24 Jun 2024 10:47:14 -0400 Subject: [PATCH 09/84] Remove `lockfileMaintenance` setting (#30799) --- .github/renovate.json5 | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 03787dfac..2cf7bec8e 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -14,9 +14,6 @@ // to `null` after any other rule set it to something. dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).', postUpdateOptions: ['yarnDedupeHighest'], - lockFileMaintenance: { - enabled: true, - }, packageRules: [ { // Require Dependency Dashboard Approval for major version bumps of these node packages From 6527d5039141fe4a80645147b581d76952a64f39 Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Mon, 24 Jun 2024 10:50:37 -0400 Subject: [PATCH 10/84] Disable `Rails/BulkChangeTable` cop (#30820) --- .rubocop/rails.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop/rails.yml b/.rubocop/rails.yml index 68c90143a..ae31c1f26 100644 --- a/.rubocop/rails.yml +++ b/.rubocop/rails.yml @@ -1,4 +1,7 @@ --- +Rails/BulkChangeTable: + Enabled: false # Conflicts with strong_migrations features + Rails/FilePath: EnforcedStyle: arguments From 052c90b8de2164b8003c29f445f165c7e802fd25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:45:58 +0200 Subject: [PATCH 11/84] New Crowdin Translations (automated) (#30825) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/lt.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index b365d6458..bb69b7339 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -1,18 +1,18 @@ { "about.blocks": "Prižiūrimi serveriai", "about.contact": "Kontaktai:", - "about.disclaimer": "Mastodon – tai nemokama atvirojo kodo programinė įranga ir Mastodon gGmbH prekės ženklas.", + "about.disclaimer": "„Mastodon“ – tai nemokama atvirojo kodo programinė įranga ir „Mastodon“ gGmbH prekės ženklas.", "about.domain_blocks.no_reason_available": "Priežastis nepateikta", - "about.domain_blocks.preamble": "Mastodon paprastai leidžia peržiūrėti turinį ir bendrauti su naudotojais iš bet kurio kito fediverse esančio serverio. Šios yra išimtys, kurios buvo padarytos šiame konkrečiame serveryje.", + "about.domain_blocks.preamble": "„Mastodon“ paprastai leidžia peržiūrėti turinį ir bendrauti su naudotojais iš bet kurio kito fediverse esančio serverio. Šios yra išimtys, kurios buvo padarytos šiame konkrečiame serveryje.", "about.domain_blocks.silenced.explanation": "Paprastai nematysi profilių ir turinio iš šio serverio, nebent jį aiškiai ieškosi arba pasirinksi jį sekdamas (-a).", "about.domain_blocks.silenced.title": "Ribota", "about.domain_blocks.suspended.explanation": "Jokie duomenys iš šio serverio nebus apdorojami, saugomi ar keičiami, todėl bet kokia sąveika ar bendravimas su šio serverio naudotojais bus neįmanomas.", - "about.domain_blocks.suspended.title": "Uždrausta", + "about.domain_blocks.suspended.title": "Pristabdyta", "about.not_available": "Ši informacija nebuvo pateikta šiame serveryje.", - "about.powered_by": "Decentralizuota socialinė medija, kurią valdo {mastodon}", + "about.powered_by": "Decentralizuota socialinė medija, veikianti pagal „{mastodon}“", "about.rules": "Serverio taisyklės", "account.account_note_header": "Pastaba", - "account.add_or_remove_from_list": "Pridėti arba ištrinti iš sąrašų", + "account.add_or_remove_from_list": "Pridėti arba pašalinti iš sąrašų", "account.badges.bot": "Automatizuotas", "account.badges.group": "Grupė", "account.block": "Blokuoti @{name}", From 30ae5952d228b31af58534d76a8d78bf27a171f9 Mon Sep 17 00:00:00 2001 From: Emelia Smith <ThisIsMissEm@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:46:53 +0200 Subject: [PATCH 12/84] Fix: Ensure "With Media" is highlighted from Admin Accounts page (#30812) --- app/views/admin/statuses/index.html.haml | 2 +- spec/controllers/admin/statuses_controller_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml index 770d972d9..b03b8ac51 100644 --- a/app/views/admin/statuses/index.html.haml +++ b/app/views/admin/statuses/index.html.haml @@ -8,7 +8,7 @@ %strong= t('admin.statuses.media.title') %ul %li= filter_link_to t('generic.all'), media: nil, id: nil - %li= filter_link_to t('admin.statuses.with_media'), media: '1' + %li= filter_link_to t('admin.statuses.with_media'), media: true .back-link - if params[:report_id] = link_to admin_report_path(params[:report_id].to_i) do diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb index 4144d97d6..4ab6d109e 100644 --- a/spec/controllers/admin/statuses_controller_spec.rb +++ b/spec/controllers/admin/statuses_controller_spec.rb @@ -33,7 +33,7 @@ describe Admin::StatusesController do context 'when filtering by media' do before do - get :index, params: { account_id: account.id, media: '1' } + get :index, params: { account_id: account.id, media: true } end it 'returns http success' do From 309274839dd446d334ae2ea5c6e033debe56e4ed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:56:38 +0200 Subject: [PATCH 13/84] chore(deps): update dependency aws-sdk-s3 to v1.153.0 (#30824) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index abd31f49a..5d735eb75 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,17 +100,17 @@ GEM attr_required (1.0.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.940.0) - aws-sdk-core (3.197.0) + aws-partitions (1.947.0) + aws-sdk-core (3.198.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.83.0) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-kms (1.86.0) + aws-sdk-core (~> 3, >= 3.198.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.152.3) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-s3 (1.153.0) + aws-sdk-core (~> 3, >= 3.198.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) From 547e97945df0abc68dc473ed60d2faeb2feb2b06 Mon Sep 17 00:00:00 2001 From: Renaud Chaput <renchap@gmail.com> Date: Tue, 25 Jun 2024 15:45:41 +0200 Subject: [PATCH 14/84] Change `apiRequest` to accept both `params` and `data` (#30818) --- app/javascript/mastodon/api.ts | 37 ++++++++++++++++++- app/javascript/mastodon/api/accounts.ts | 8 ++-- app/javascript/mastodon/api/interactions.ts | 10 +++-- .../mastodon/api/notification_policies.ts | 8 ++-- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts index e133125a2..24672290c 100644 --- a/app/javascript/mastodon/api.ts +++ b/app/javascript/mastodon/api.ts @@ -59,16 +59,49 @@ export default function api(withAuthorization = true) { }); } +type RequestParamsOrData = Record<string, unknown>; + export async function apiRequest<ApiResponse = unknown>( method: Method, url: string, - params?: Record<string, unknown>, + args: { + params?: RequestParamsOrData; + data?: RequestParamsOrData; + } = {}, ) { const { data } = await api().request<ApiResponse>({ method, url: '/api/' + url, - data: params, + ...args, }); return data; } + +export async function apiRequestGet<ApiResponse = unknown>( + url: string, + params?: RequestParamsOrData, +) { + return apiRequest<ApiResponse>('GET', url, { params }); +} + +export async function apiRequestPost<ApiResponse = unknown>( + url: string, + data?: RequestParamsOrData, +) { + return apiRequest<ApiResponse>('POST', url, { data }); +} + +export async function apiRequestPut<ApiResponse = unknown>( + url: string, + data?: RequestParamsOrData, +) { + return apiRequest<ApiResponse>('PUT', url, { data }); +} + +export async function apiRequestDelete<ApiResponse = unknown>( + url: string, + params?: RequestParamsOrData, +) { + return apiRequest<ApiResponse>('DELETE', url, { params }); +} diff --git a/app/javascript/mastodon/api/accounts.ts b/app/javascript/mastodon/api/accounts.ts index 3d89e44b2..e58608785 100644 --- a/app/javascript/mastodon/api/accounts.ts +++ b/app/javascript/mastodon/api/accounts.ts @@ -1,7 +1,9 @@ -import { apiRequest } from 'mastodon/api'; +import { apiRequestPost } from 'mastodon/api'; import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; export const apiSubmitAccountNote = (id: string, value: string) => - apiRequest<ApiRelationshipJSON>('post', `v1/accounts/${id}/note`, { - comment: value, + apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, { + data: { + comment: value, + }, }); diff --git a/app/javascript/mastodon/api/interactions.ts b/app/javascript/mastodon/api/interactions.ts index 4c466a1b4..0bdaffbda 100644 --- a/app/javascript/mastodon/api/interactions.ts +++ b/app/javascript/mastodon/api/interactions.ts @@ -1,10 +1,12 @@ -import { apiRequest } from 'mastodon/api'; +import { apiRequestPost } from 'mastodon/api'; import type { Status, StatusVisibility } from 'mastodon/models/status'; export const apiReblog = (statusId: string, visibility: StatusVisibility) => - apiRequest<{ reblog: Status }>('post', `v1/statuses/${statusId}/reblog`, { - visibility, + apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, { + data: { + visibility, + }, }); export const apiUnreblog = (statusId: string) => - apiRequest<Status>('post', `v1/statuses/${statusId}/unreblog`); + apiRequestPost<Status>(`v1/statuses/${statusId}/unreblog`); diff --git a/app/javascript/mastodon/api/notification_policies.ts b/app/javascript/mastodon/api/notification_policies.ts index b2a1e5ac3..5c1ef9c1d 100644 --- a/app/javascript/mastodon/api/notification_policies.ts +++ b/app/javascript/mastodon/api/notification_policies.ts @@ -1,10 +1,12 @@ -import { apiRequest } from 'mastodon/api'; +import { apiRequestGet, apiRequestPut } from 'mastodon/api'; import type { NotificationPolicyJSON } from 'mastodon/api_types/notification_policies'; export const apiGetNotificationPolicy = () => - apiRequest<NotificationPolicyJSON>('GET', '/v1/notifications/policy'); + apiRequestGet<NotificationPolicyJSON>('/v1/notifications/policy'); export const apiUpdateNotificationsPolicy = ( policy: Partial<NotificationPolicyJSON>, ) => - apiRequest<NotificationPolicyJSON>('PUT', '/v1/notifications/policy', policy); + apiRequestPut<NotificationPolicyJSON>('/v1/notifications/policy', { + data: policy, + }); From 8ef59729a10fd77121507dcf9ef5138ff9037d39 Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Tue, 25 Jun 2024 09:57:40 -0400 Subject: [PATCH 15/84] Ignore intermittent chrome/manifest/icon interaction failure (#30793) --- spec/support/javascript_errors.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/support/javascript_errors.rb b/spec/support/javascript_errors.rb index 28a43b3b8..764528536 100644 --- a/spec/support/javascript_errors.rb +++ b/spec/support/javascript_errors.rb @@ -2,7 +2,14 @@ RSpec.configure do |config| config.after(:each, :js, type: :system) do - errors = page.driver.browser.logs.get(:browser) + # Classes of intermittent ignorable errors + ignored_errors = [ + /Error while trying to use the following icon from the Manifest/, # https://github.com/mastodon/mastodon/pull/30793 + ] + errors = page.driver.browser.logs.get(:browser).reject do |error| + ignored_errors.any? { |pattern| pattern.match(error.message) } + end + if errors.present? aggregate_failures 'javascript errrors' do errors.each do |error| From 845fe1c6936a7b386fd74ae567c19600a88e795a Mon Sep 17 00:00:00 2001 From: Renaud Chaput <renchap@gmail.com> Date: Tue, 25 Jun 2024 16:05:24 +0200 Subject: [PATCH 16/84] Add the Interlingua locale (#30828) --- config/initializers/i18n.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/i18n.rb b/config/initializers/i18n.rb index 8643060fa..5e8d3a545 100644 --- a/config/initializers/i18n.rb +++ b/config/initializers/i18n.rb @@ -41,6 +41,7 @@ Rails.application.configure do :hr, :hu, :hy, + :ia, :id, :ie, :ig, From 2c7eed1fa1e7af72dd03a041a60f2cfd42e913e0 Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Tue, 25 Jun 2024 18:53:03 +0200 Subject: [PATCH 17/84] Fix API requests after #30818 (#30837) --- app/javascript/mastodon/api/accounts.ts | 4 +--- app/javascript/mastodon/api/interactions.ts | 4 +--- app/javascript/mastodon/api/notification_policies.ts | 5 +---- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/javascript/mastodon/api/accounts.ts b/app/javascript/mastodon/api/accounts.ts index e58608785..bd1757e82 100644 --- a/app/javascript/mastodon/api/accounts.ts +++ b/app/javascript/mastodon/api/accounts.ts @@ -3,7 +3,5 @@ import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; export const apiSubmitAccountNote = (id: string, value: string) => apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, { - data: { - comment: value, - }, + comment: value, }); diff --git a/app/javascript/mastodon/api/interactions.ts b/app/javascript/mastodon/api/interactions.ts index 0bdaffbda..118b5f06d 100644 --- a/app/javascript/mastodon/api/interactions.ts +++ b/app/javascript/mastodon/api/interactions.ts @@ -3,9 +3,7 @@ import type { Status, StatusVisibility } from 'mastodon/models/status'; export const apiReblog = (statusId: string, visibility: StatusVisibility) => apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, { - data: { - visibility, - }, + visibility, }); export const apiUnreblog = (statusId: string) => diff --git a/app/javascript/mastodon/api/notification_policies.ts b/app/javascript/mastodon/api/notification_policies.ts index 5c1ef9c1d..4032134fb 100644 --- a/app/javascript/mastodon/api/notification_policies.ts +++ b/app/javascript/mastodon/api/notification_policies.ts @@ -6,7 +6,4 @@ export const apiGetNotificationPolicy = () => export const apiUpdateNotificationsPolicy = ( policy: Partial<NotificationPolicyJSON>, -) => - apiRequestPut<NotificationPolicyJSON>('/v1/notifications/policy', { - data: policy, - }); +) => apiRequestPut<NotificationPolicyJSON>('/v1/notifications/policy', policy); From 07d222665b9974a97c78d2a500a55555ebedd640 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 17:25:11 +0000 Subject: [PATCH 18/84] chore(deps): update dependency typescript to v5.5.2 (#30815) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index b6117d821..dc73fb93b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17151,22 +17151,22 @@ __metadata: linkType: hard "typescript@npm:5, typescript@npm:^5.0.4": - version: 5.4.5 - resolution: "typescript@npm:5.4.5" + version: 5.5.2 + resolution: "typescript@npm:5.5.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/2954022ada340fd3d6a9e2b8e534f65d57c92d5f3989a263754a78aba549f7e6529acc1921913560a4b816c46dce7df4a4d29f9f11a3dc0d4213bb76d043251e + checksum: 10c0/8ca39b27b5f9bd7f32db795045933ab5247897660627251e8254180b792a395bf061ea7231947d5d7ffa5cb4cc771970fd4ef543275f9b559f08c9325cccfce3 languageName: node linkType: hard "typescript@patch:typescript@npm%3A5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>": - version: 5.4.5 - resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin<compat/typescript>::version=5.4.5&hash=5adc0c" + version: 5.5.2 + resolution: "typescript@patch:typescript@npm%3A5.5.2#optional!builtin<compat/typescript>::version=5.5.2&hash=379a07" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/db2ad2a16ca829f50427eeb1da155e7a45e598eec7b086d8b4e8ba44e5a235f758e606d681c66992230d3fc3b8995865e5fd0b22a2c95486d0b3200f83072ec9 + checksum: 10c0/a7b7ede75dc7fc32a76d0d0af6b91f5fbd8620890d84c906f663d8783bf3de6d7bd50f0430b8bb55eac88a38934af847ff709e7156e5138b95ae94cbd5f73e5b languageName: node linkType: hard From a40831b3636fbca1e6d6b096e3d3cdd66569baf9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Tue, 25 Jun 2024 22:37:48 +0200 Subject: [PATCH 19/84] Fix account search results (#30803) --- app/services/account_search_service.rb | 52 ++++++++++---------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index b86c9b9e7..dab5f748b 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -28,9 +28,7 @@ class AccountSearchService < BaseService }, functions: [ - reputation_score_function, followers_score_function, - time_distance_function, ], }, }, @@ -81,36 +79,12 @@ class AccountSearchService < BaseService } end - # This function deranks accounts that follow more people than follow them - def reputation_score_function - { - script_score: { - script: { - source: "(Math.max(doc['followers_count'].value, 0) + 0.0) / (Math.max(doc['followers_count'].value, 0) + Math.max(doc['following_count'].value, 0) + 1)", - }, - }, - } - end - # This function promotes accounts that have more followers def followers_score_function { script_score: { script: { - source: "(Math.max(doc['followers_count'].value, 0) / (Math.max(doc['followers_count'].value, 0) + 1))", - }, - }, - } - end - - # This function deranks accounts that haven't posted in a long time - def time_distance_function - { - gauss: { - last_status_at: { - scale: '30d', - offset: '30d', - decay: 0.3, + source: "Math.log10((Math.max(doc['followers_count'].value, 0) + 1))", }, }, } @@ -126,10 +100,24 @@ class AccountSearchService < BaseService def core_query { - multi_match: { - query: @query, - type: 'bool_prefix', - fields: %w(username^2 username.*^2 display_name display_name.*), + dis_max: { + queries: [ + { + multi_match: { + query: @query, + type: 'most_fields', + fields: %w(username username.*), + }, + }, + + { + multi_match: { + query: @query, + type: 'most_fields', + fields: %w(display_name display_name.*), + }, + }, + ], }, } end @@ -142,7 +130,7 @@ class AccountSearchService < BaseService { multi_match: { query: @query, - type: 'most_fields', + type: 'best_fields', fields: %w(username^2 display_name^2 text text.*), operator: 'and', }, From 8c0ff6498e090a2919e8f8104339796ed2d3d212 Mon Sep 17 00:00:00 2001 From: Renaud Chaput <renchap@gmail.com> Date: Tue, 25 Jun 2024 23:57:22 +0200 Subject: [PATCH 20/84] Change light mode to apply CSS variables to the body (#30839) --- app/javascript/styles/mastodon-light/variables.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss index 09a75a834..3cdbd9bf6 100644 --- a/app/javascript/styles/mastodon-light/variables.scss +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -56,11 +56,11 @@ $account-background-color: $white !default; $emojis-requiring-inversion: 'chains'; -.theme-mastodon-light { +body { --dropdown-border-color: #d9e1e8; --dropdown-background-color: #fff; --background-border-color: #d9e1e8; --background-color: #fff; - --background-color-tint: rgba(255, 255, 255, 90%); + --background-color-tint: rgba(255, 255, 255, 80%); --background-filter: blur(10px); } From 2b43c05a6a1c38a7480119a0bd12fefa8b5589c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:42:25 +0200 Subject: [PATCH 21/84] chore(deps): update dependency aws-sdk-s3 to v1.154.0 (#30838) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5d735eb75..e8b54ed56 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,16 +101,16 @@ GEM awrence (1.2.1) aws-eventstream (1.3.0) aws-partitions (1.947.0) - aws-sdk-core (3.198.0) + aws-sdk-core (3.199.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.86.0) - aws-sdk-core (~> 3, >= 3.198.0) + aws-sdk-kms (1.87.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.153.0) - aws-sdk-core (~> 3, >= 3.198.0) + aws-sdk-s3 (1.154.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) From 7a84b76bb1716b131c35d9569bfcef3b32d73e60 Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Wed, 26 Jun 2024 15:44:08 +0200 Subject: [PATCH 22/84] Drop favicon.ico generation (#30375) --- app/models/site_upload.rb | 11 ++--------- app/views/layouts/application.html.haml | 2 -- config/imagemagick/policy.xml | 2 +- lib/tasks/branding.rake | 3 --- public/favicon.ico | Bin 15086 -> 0 bytes spec/requests/account_show_page_spec.rb | 2 +- 6 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 public/favicon.ico diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 6431d1007..273dd6de9 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -31,17 +31,10 @@ class SiteUpload < ApplicationRecord [:"#{size}", { format: 'png', geometry: "#{size}x#{size}#", file_geometry_parser: FastGeometryParser }] end.freeze, - favicon: { - ico: { - format: 'ico', - geometry: '48x48#', - file_geometry_parser: FastGeometryParser, - }.freeze, - }.merge( + favicon: FAVICON_SIZES.to_h do |size| [:"#{size}", { format: 'png', geometry: "#{size}x#{size}#", file_geometry_parser: FastGeometryParser }] - end - ).freeze, + end.freeze, thumbnail: { '@1x': { diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index e7f1a595e..0c0512e81 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,8 +11,6 @@ - if storage_host? %link{ rel: 'dns-prefetch', href: storage_host }/ - %link{ rel: 'icon', href: favicon_path('ico') || '/favicon.ico', type: 'image/x-icon' }/ - - SiteUpload::FAVICON_SIZES.each do |size| %link{ rel: 'icon', sizes: "#{size}x#{size}", href: favicon_path(size.to_i) || frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ diff --git a/config/imagemagick/policy.xml b/config/imagemagick/policy.xml index 2730a9f84..e2aa202f2 100644 --- a/config/imagemagick/policy.xml +++ b/config/imagemagick/policy.xml @@ -23,5 +23,5 @@ <!-- Disallow any coder by default, and only enable ones required by Mastodon --> <policy domain="coder" rights="none" pattern="*" /> <policy domain="coder" rights="read | write" pattern="{JPEG,PNG,GIF,WEBP,HEIC,AVIF}" /> - <policy domain="coder" rights="write" pattern="{HISTOGRAM,RGB,INFO,ICO}" /> + <policy domain="coder" rights="write" pattern="{HISTOGRAM,RGB,INFO}" /> </policymap> diff --git a/lib/tasks/branding.rake b/lib/tasks/branding.rake index 608fb3af9..be72454ce 100644 --- a/lib/tasks/branding.rake +++ b/lib/tasks/branding.rake @@ -42,7 +42,6 @@ namespace :branding do output_dest = Rails.root.join('app', 'javascript', 'icons') rsvg_convert = Terrapin::CommandLine.new('rsvg-convert', '-w :size -h :size --keep-aspect-ratio :input -o :output') - convert = Terrapin::CommandLine.new('convert', ':input :output', environment: { 'MAGICK_CONFIGURE_PATH' => nil }) favicon_sizes = [16, 32, 48] apple_icon_sizes = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180, 1024] @@ -56,8 +55,6 @@ namespace :branding do rsvg_convert.run(size: size, input: favicon_source, output: output_path) end - convert.run(input: favicons, output: Rails.public_path.join('favicon.ico')) - apple_icon_sizes.each do |size| rsvg_convert.run(size: size, input: app_icon_source, output: output_dest.join("apple-touch-icon-#{size}x#{size}.png")) end diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index b09a98bb9b0649cb67305b6663bd56b3cfb17222..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmdU0d303O8Gn{m+S9sq@gI+sdfIc^b8PjTdaRg0MwkFW3;~?w&Ax;HN^k)eL`<|& z1*?|DswkUO)VePfAuIt!5wnnlY=nf607(d0XL~dAX1;#+P41gFZ{8awAyE3x`Q^L! ze(Sx-yf^RrzA(&X%oyhCs~M!@nE5|snA;eJ88<FAe+Sn$;@T~@49$PXFjIcTFuz9` zWT8fE9SOsrT&k;TqV6~48T#kTGjtUd8G0`wSdn39t;o=QP?@32PNiF&rJq|h(ZC^q zpn9TV;Bc1VFagdx5zT7)wRak39=X#PMF2tVB;(d^#_N6}m2b*1Un!kS#|@L&%KFJH zP}FHn+PcYXcKsxlt=F0g>a|7_j_<D5n*Z38lQUMTTdUFi?C2Cz6ai^s0OeL!O|kxz z*lC$+x~*lJ35E~$6QY0OE|c!WU1lKgY~Kyuy<ZDsx+bNb&r86!|67sYp3DB})J%)> zWUd7WLM<nN@9GxDWZvN{gbw%p;yl>Y0(?)eIB(B2&+W{!6?WuVfgpT5rcdXgW5<C% z(=G7GS_t(cs89JFvuqEanQdQsW{wRAsJ=LFj_Hf@V?@7Ckd3Wbm-XoEPxk2TKoCZG zVxP|WQymZ;9E{ITv_Yi%tT-pSg9gXL14cU#gueX_AR2|hmbW1AuXg~#VTf`ZaPNN% zf!8+y@H~WTY6V#$-*0d{<}*2Gdd&_X@IO%uQNJJP72NhNaO*b+Hifld5CSV!3bI7r zXR;SSVd2<-)!B&v0&dZR5L)vx1eX;Fb7AeVWx^Ww;1Y2S*Ezq<b{+W}2)kUFA%|-K z0cpYR3WpqS7ty5`psk48{d^=pzZ~gV#2^BO(1)}KQHR(BZa2Aw2v_S33{#4D>Nvv` zlTXHFLx{0ee-@)$>6qAkW*kas5aj+OT;#?x%!_<3%kR)l!u!t(y#MSd%g{IC*oWwi z!BAg*hyEkHCp}$phi*K?A|0leWbuQ4F<)7cX?(OI)6j{4bWxdUXs(!GyeHXW`YRO~ z##;|hFti|GgxHn7w?xUBiAHyImLY@y6-01-AL2E{N<ynNhIjBjzZ1tN5ab>|BLCGI zHm@ej7^%@1K^cZG@xJ`q#E27Tl^Vy@H4_bgNL)SltbVfX7qxd9dk~-uLuIW_clo(3 zjZC6eYbwTP6Q}{7VT(s*IX$fgEn9;CRb-oV_{^M&&(ufiwe0$aZ1z2*B`E)WgVuD2 zj4{R!k-xQZGW++&Y~y{6*{12WTHUYD6#;pMAI)YX2)Gbv%4QoIr?5-OGhHbb<YzVE zbN(Wry-91nT^>X8RP+31{H}13;Axp^xmg-RbB=jsOO6>X8m5~MLvisBXbi2>&D#(l zMOV@D(7W*+0o^OtCGezjYU8dao{Q_h@Jh0NjyZ?=Z<}HM0s&HZcYj46_rO)xCGezj zYGcoTN<{tO(dH!m_PZ^wQUC2TEC+B7Qut*4rMz^;o-cMK_)pS7pVa@UT=S7r_`OgH z-yX^SYw8ksM6T=JN8s%8X9b*jXn7LfEAbz~{Y!1f#@06K|8$=91OlY+OZ*4Xzm!MD zz8!nw`>Sn8=5LShKh)Hm#6LaL+Clwy&a!qPK#IWL`2LB1DUXc(GXF_@nSVJS=Vz(^ zuGzLh1V|CU@B8!$9;r{@5jp?Py_5~1x`t%_m%HP9{C+OAJv1Jq{=4t71rZ<xR~q*p zLjO`883#%d{U`Bv#QlfSznqWrAobs)v+)R!Lh3(UTc5xqatVC={YrHO{!WR1e7}(M zaUP}qd-Zl60a9?gO2zZz+fF9%h+Me7QRGMZ2cXyBNRSC0{9fb}S*h)zvAF+!qdkBC zDFW|(B<hoQ7tf>1MZV|p;`qDAkd$xDD<XfOpb(-ypQs-?a8Tq)e0b>o@tvn>fOUWr z-lv`w^~r#||AfB&MtGMZ?@q$npa&v&Ho_H&K)9>|Li@ggXkTA!ZRls<jm-(Rhy3(V z{~nXQ#A9+m0?ba}k2WRPkx#hw8^Qn7ilnoU%0=<L(d(L*Xq)&lJH*ew-|SfBBkyf8 z_!ljPXh(<CoH!1b>=xyNZ@#4%Lln;^u>2`ePHG?D6Zg^m`>d{8{rHZk2=_b)ZG8*) zvT}$VsRbU-H@NDBBw4QLX$bGy1AKK2@Rd~%djCTR+*_C=C;G6t9;W^Y<!sIm5O9%j z227@Fr2c^|_+`-UYC*sS2G7}Dxl-SB3{L0uVW+z?>~z5e0>?X@7V1xllDq-Fi$4)@ zxq1;WT8O$_yTRp3_}-`FpRR$!@ng&f<Y6o;qi*-7h$D#RXny`lq`e3)0uYg*Iaxb_ zXhzhcTnXkJwm^RVVzhG;wJWE<j0K(<1013s(K^-xrB?xz-hg13qGE=*3NaQj265T> zpcM5HV_MN(KicIPCX3`lF-6Ka5MrJp<r^jn4&(Z_PB*4J!*F}qc-><a<Mo>>#_RT$ zjn}te&Z1X>GdM0QyF<SXdCO6LHs(2hJ`6XbDq8mEf*)ZXLnq`_GWGqKTY(XPN1c6` zlUbnl-ZiR0qcL*UhcgXM%+<7E{4jdZcU4T#&l&A_rS_^bO*d9fFzg#)zUQ2IqN)kT z^-_PMIj+*M(=d<UPxAX03>PT=8jaq9IV)bsTdC)~<higT)f)Y}sw~63nENuKt}d20 zGd@_QF&1Mk>_x;UxZZ+@#%z!N`J>$&%#($cb7jdnv}%p9qE^FZ4(E5@kdb>ubrw4w z_nDNp7>*se?BPke>#(NNO=~;nq#AaQy!Iovc`@flCb4fT=Lr?_hg{tx6K2ydjjK*$ zxT$Uu8%01`m~8y`(uh131?Cz5Mr(R$Q7v1j7C*^7)=V*8UpEEM1aVYry0Knk9EbPg zYxZh1=ctUTSxvo;InzqjJgagJ)=6@y4cVqg8@22<)U8JJBBE*7;ZUcO<Y-a$-3G1k zIXts@SgV{UuX(19I>v3s`fFo08%Dq-0Dq&__}Nh{I|uODpHBQF|C(k!H{H7LD9N!k zjoIeO=i0-usiuWZQ!!V4X(DoTn(2vDHMnCrrnSc~*L^9YIfs2x?f1|!&HVZBYx*PU z12`|8DAp3DEA8QUj=2s2{|9Iw?;-L%V7}k0mh(U9xF0$ne>MT97Cf4q`<K-k)a=RE z+bs(dY!hFn?^~*B|M+wZD~+Gj52bw@@^RmWZDM|j4!kA%#J&!>{BY+zt6!&PMe6^y zOx^wr)BDo+kIk}P+cv|3wG3q_#=mR7s=S&VwROe#50<5}@0He>+Gm)4msAIl)Lr^F zDx5#YpGs#qyxuhm=a2EHvT=HP{Ci_tuJu;h)1yTDHzmjK!}yhCN3zztHaY$P#-GyG zjQBI{)J&Te`;tHn14{Al-mfa(HSZzlU9~0w-Ajs8<@;3PSBr<NpUku9>1W_{o+S_K zMxcg)O>c{zD?0FF{A%+3uWX`XBK7Y*pf11m4N;B`0td@g<psaF7Bh{%Gta_`Jz9CO z-Y)5UaI<p!QW@!ZKq>yx1MxmEnUDT;$?<dFm8;4NzGm6%G=7{r6zXVVZB8wIHJyQ1 zH_>AhX#nF_m48_|el_`_^|>_ut~r(kU9+uNi&KVYOY->#_Uu=cAAC(A{(T43<yGQW zi-)YAnPt6?#@{{1{vhqyQ=(p#_?2YIn&-96;<?j70OMDaAN=PADkf6yP`Ro+vC}=r z_7IK#tj_kBLLE}9O?ux{IRCx-MZR3uvpzX~4&#^0(YZ$<{vgIr`AU?;9FO0+4CkN* zpF;c`#;+#t-SDP(j&u;jTA`Y}=auC1C-JMvll7i^Y(??-?aOEnrxI0)Klsge@xIQX zeo(6G|L8Lswn)QR3#WWJ^=;fL%FzLdUoMw8cNEk3dv*3@eHeope6Mb#W<?rqY*LjE zeS1ihqXW|C+K07dB_G_UUr;fT21~zEl#T1h<L}qom-ZV-f2%Tlt6mcAiUaJ89V}du zq%$z@exZj}l#>j+E1pl1?>AvBtFucY6&rG&e4Z>TiLcKniQiyffVFC|Pgg!3oWCf^ zK3U+g{_a_{MAY*vUJ5++1gKq6tmpflSsANYqrXAdd<d4r`%R>J{ukGZcIEMqIiB@0 z`uF2u?Q;gP&YlYVv1Y0HgfXcnlX|xNA~pCBe$M4r)T1m<<wzah<Gwd#JOd`j{WN~B z#dUK+jb2`}$9Z7&%hY^&6nh)|e|-+T1q*@1JDe-@<<8`~zAUCe*6F12Lr&pXth<Lu zk9I1A4`RP~npbH14$&6(k9CTD07|CFXTgWH{Zw%fp3m%@LE|UXZ*lDQS#UiS9QnYf zv4`;fufS(>CD;mX!Fwe3$EUg?ZEfJke)g0x#Nvy3tk!F#@%t>!1!50^TF=AKdhn&y z5UDwm7!My2dK?H(%?`J$2>7oL00aW@ZQ$Qo<oI!+m%@+dqoylcKO~Kvj7eP_d-U5$ z|9;wVxP|_IuJ8c};CZL5hd#LZ3xRt8|3AQ0Ff2X6hs9-+$4}+~tHXMc`aI63pQHi` zlJh@wKiDI(<pTGKoKGK$Kjd(bwgS})^VOGQkI9E={a;7A|Nk*Ma(vK{KU+0$34{E8 zc$aGp?jw4E`elaKhl4#cVTXH~B*e6ayxV;j{;y3v?gjpb_ze4VZcHm+1tC)Z;PdlY z?7i86Jv%&t{*TXvqyhBvHug<rDTFlQB|L{K5O+ep>w(Bz_uA-O_vg`k*CE{7QKVhb zeC$7pfkO%sL?t{Ta{_g-7YTLq-P<Gi?#+m2U@m^?al13fy*%9@u}NtxU||DV5rh_F za}uFwfKbv*N+|Xe36x`Q;K{t7$--w4p+y31MQt)~W%w~MiVaCPFGW43XozMF(W_*1 z?9d!5U4lH)VxmVW{_mAAzXWJ0i3{Tps3hK2vW|1?zrrr8A&UFt$oz)bIWo9^Aandd J&YPUie*xt08p;3w diff --git a/spec/requests/account_show_page_spec.rb b/spec/requests/account_show_page_spec.rb index 81e965e6e..830d77860 100644 --- a/spec/requests/account_show_page_spec.rb +++ b/spec/requests/account_show_page_spec.rb @@ -9,7 +9,7 @@ describe 'The account show page' do get '/@alice' - expect(head_link_icons.size).to eq(4) # One general favicon and three with sizes + expect(head_link_icons.size).to eq(3) # Three favicons with sizes expect(head_meta_content('og:title')).to match alice.display_name expect(head_meta_content('og:type')).to eq 'profile' From 528a7f57fa8a31d1a901c247cc55b214e2dc1af5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Wed, 26 Jun 2024 09:51:11 -0400 Subject: [PATCH 23/84] Fix `Rails/ReversibleMigration` cop for `change_column` (#30835) --- db/migrate/20160223164502_make_uris_nullable_in_statuses.rb | 6 +++++- ...170322143850_change_primary_key_to_bigint_on_statuses.rb | 6 +++++- .../20170609145826_remove_default_language_from_statuses.rb | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/db/migrate/20160223164502_make_uris_nullable_in_statuses.rb b/db/migrate/20160223164502_make_uris_nullable_in_statuses.rb index fff07093c..ebb572bd6 100644 --- a/db/migrate/20160223164502_make_uris_nullable_in_statuses.rb +++ b/db/migrate/20160223164502_make_uris_nullable_in_statuses.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true class MakeUrisNullableInStatuses < ActiveRecord::Migration[4.2] - def change + def up change_column :statuses, :uri, :string, null: true, default: nil end + + def down + raise ActiveRecord::IrreversibleMigration + end end diff --git a/db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb b/db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb index b98fffab8..e7fcb75a4 100644 --- a/db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb +++ b/db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ChangePrimaryKeyToBigintOnStatuses < ActiveRecord::Migration[5.0] - def change + def up change_table(:statuses, bulk: true) do |t| t.change :id, :bigint t.change :reblog_of_id, :bigint @@ -16,4 +16,8 @@ class ChangePrimaryKeyToBigintOnStatuses < ActiveRecord::Migration[5.0] change_column :statuses_tags, :status_id, :bigint change_column :stream_entries, :activity_id, :bigint end + + def down + raise ActiveRecord::IrreversibleMigration + end end diff --git a/db/migrate/20170609145826_remove_default_language_from_statuses.rb b/db/migrate/20170609145826_remove_default_language_from_statuses.rb index 28b4172a8..122c32228 100644 --- a/db/migrate/20170609145826_remove_default_language_from_statuses.rb +++ b/db/migrate/20170609145826_remove_default_language_from_statuses.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true class RemoveDefaultLanguageFromStatuses < ActiveRecord::Migration[5.1] - def change + def up change_column :statuses, :language, :string, default: nil, null: true end + + def down + raise ActiveRecord::IrreversibleMigration + end end From 51f581e03e1b2611eceddf5010ef736680e1f62b Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Wed, 26 Jun 2024 09:51:44 -0400 Subject: [PATCH 24/84] Fix `Rails/ReversibleMigration` cop for `remove` (#30833) --- db/migrate/20170520145338_change_language_filter_to_opt_out.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20170520145338_change_language_filter_to_opt_out.rb b/db/migrate/20170520145338_change_language_filter_to_opt_out.rb index f0a95819c..0e91c4680 100644 --- a/db/migrate/20170520145338_change_language_filter_to_opt_out.rb +++ b/db/migrate/20170520145338_change_language_filter_to_opt_out.rb @@ -5,7 +5,7 @@ class ChangeLanguageFilterToOptOut < ActiveRecord::Migration[5.0] remove_index :users, :allowed_languages change_table(:users, bulk: true) do |t| - t.remove :allowed_languages + t.remove :allowed_languages, type: :string, array: true, default: [], null: false t.column :filtered_languages, :string, array: true, default: [], null: false end From 863c470a2bc4e13a5b8df4d66a1322f4b84e2db2 Mon Sep 17 00:00:00 2001 From: Renaud Chaput <renchap@gmail.com> Date: Wed, 26 Jun 2024 20:04:50 +0200 Subject: [PATCH 25/84] Convert `<Directory>` to Typescript / function component (#30829) --- app/javascript/mastodon/actions/directory.js | 62 ---- app/javascript/mastodon/actions/directory.ts | 37 +++ app/javascript/mastodon/api/directory.ts | 15 + .../directory/components/account_card.jsx | 234 --------------- .../directory/components/account_card.tsx | 269 ++++++++++++++++++ .../mastodon/features/directory/index.jsx | 181 ------------ .../mastodon/features/directory/index.tsx | 217 ++++++++++++++ .../mastodon/reducers/user_lists.js | 32 +-- 8 files changed, 553 insertions(+), 494 deletions(-) delete mode 100644 app/javascript/mastodon/actions/directory.js create mode 100644 app/javascript/mastodon/actions/directory.ts create mode 100644 app/javascript/mastodon/api/directory.ts delete mode 100644 app/javascript/mastodon/features/directory/components/account_card.jsx create mode 100644 app/javascript/mastodon/features/directory/components/account_card.tsx delete mode 100644 app/javascript/mastodon/features/directory/index.jsx create mode 100644 app/javascript/mastodon/features/directory/index.tsx diff --git a/app/javascript/mastodon/actions/directory.js b/app/javascript/mastodon/actions/directory.js deleted file mode 100644 index 7a0748029..000000000 --- a/app/javascript/mastodon/actions/directory.js +++ /dev/null @@ -1,62 +0,0 @@ -import api from '../api'; - -import { fetchRelationships } from './accounts'; -import { importFetchedAccounts } from './importer'; - -export const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST'; -export const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS'; -export const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL'; - -export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST'; -export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS'; -export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL'; - -export const fetchDirectory = params => (dispatch) => { - dispatch(fetchDirectoryRequest()); - - api().get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => { - dispatch(importFetchedAccounts(data)); - dispatch(fetchDirectorySuccess(data)); - dispatch(fetchRelationships(data.map(x => x.id))); - }).catch(error => dispatch(fetchDirectoryFail(error))); -}; - -export const fetchDirectoryRequest = () => ({ - type: DIRECTORY_FETCH_REQUEST, -}); - -export const fetchDirectorySuccess = accounts => ({ - type: DIRECTORY_FETCH_SUCCESS, - accounts, -}); - -export const fetchDirectoryFail = error => ({ - type: DIRECTORY_FETCH_FAIL, - error, -}); - -export const expandDirectory = params => (dispatch, getState) => { - dispatch(expandDirectoryRequest()); - - const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size; - - api().get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => { - dispatch(importFetchedAccounts(data)); - dispatch(expandDirectorySuccess(data)); - dispatch(fetchRelationships(data.map(x => x.id))); - }).catch(error => dispatch(expandDirectoryFail(error))); -}; - -export const expandDirectoryRequest = () => ({ - type: DIRECTORY_EXPAND_REQUEST, -}); - -export const expandDirectorySuccess = accounts => ({ - type: DIRECTORY_EXPAND_SUCCESS, - accounts, -}); - -export const expandDirectoryFail = error => ({ - type: DIRECTORY_EXPAND_FAIL, - error, -}); diff --git a/app/javascript/mastodon/actions/directory.ts b/app/javascript/mastodon/actions/directory.ts new file mode 100644 index 000000000..34ac309c6 --- /dev/null +++ b/app/javascript/mastodon/actions/directory.ts @@ -0,0 +1,37 @@ +import type { List as ImmutableList } from 'immutable'; + +import { apiGetDirectory } from 'mastodon/api/directory'; +import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; + +import { fetchRelationships } from './accounts'; +import { importFetchedAccounts } from './importer'; + +export const fetchDirectory = createDataLoadingThunk( + 'directory/fetch', + async (params: Parameters<typeof apiGetDirectory>[0]) => + apiGetDirectory(params), + (data, { dispatch }) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchRelationships(data.map((x) => x.id))); + + return { accounts: data }; + }, +); + +export const expandDirectory = createDataLoadingThunk( + 'directory/expand', + async (params: Parameters<typeof apiGetDirectory>[0], { getState }) => { + const loadedItems = getState().user_lists.getIn([ + 'directory', + 'items', + ]) as ImmutableList<unknown>; + + return apiGetDirectory({ ...params, offset: loadedItems.size }, 20); + }, + (data, { dispatch }) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchRelationships(data.map((x) => x.id))); + + return { accounts: data }; + }, +); diff --git a/app/javascript/mastodon/api/directory.ts b/app/javascript/mastodon/api/directory.ts new file mode 100644 index 000000000..cd39f8f26 --- /dev/null +++ b/app/javascript/mastodon/api/directory.ts @@ -0,0 +1,15 @@ +import { apiRequestGet } from 'mastodon/api'; +import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; + +export const apiGetDirectory = ( + params: { + order: string; + local: boolean; + offset?: number; + }, + limit = 20, +) => + apiRequestGet<ApiAccountJSON[]>('v1/directory', { + ...params, + limit, + }); diff --git a/app/javascript/mastodon/features/directory/components/account_card.jsx b/app/javascript/mastodon/features/directory/components/account_card.jsx deleted file mode 100644 index 9c5e68812..000000000 --- a/app/javascript/mastodon/features/directory/components/account_card.jsx +++ /dev/null @@ -1,234 +0,0 @@ -import PropTypes from 'prop-types'; - -import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; - -import classNames from 'classnames'; -import { Link } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - -import { - followAccount, - unfollowAccount, - unblockAccount, - unmuteAccount, -} from 'mastodon/actions/accounts'; -import { openModal } from 'mastodon/actions/modal'; -import { Avatar } from 'mastodon/components/avatar'; -import { Button } from 'mastodon/components/button'; -import { DisplayName } from 'mastodon/components/display_name'; -import { ShortNumber } from 'mastodon/components/short_number'; -import { autoPlayGif, me } from 'mastodon/initial_state'; -import { makeGetAccount } from 'mastodon/selectors'; - -const messages = defineMessages({ - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, - follow: { id: 'account.follow', defaultMessage: 'Follow' }, - cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' }, - cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' }, - requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, - unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' }, - unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' }, - unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, - edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, -}); - -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); - - const mapStateToProps = (state, { id }) => ({ - account: getAccount(state, id), - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch, { intl }) => ({ - onFollow(account) { - if (account.getIn(['relationship', 'following'])) { - dispatch( - openModal({ - modalType: 'CONFIRM', - modalProps: { - message: ( - <FormattedMessage - id='confirmations.unfollow.message' - defaultMessage='Are you sure you want to unfollow {name}?' - values={{ name: <strong>@{account.get('acct')}</strong> }} - /> - ), - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - } }), - ); - } else if (account.getIn(['relationship', 'requested'])) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - }, - })); - } else { - dispatch(followAccount(account.get('id'))); - } - }, - - onBlock(account) { - if (account.getIn(['relationship', 'blocking'])) { - dispatch(unblockAccount(account.get('id'))); - } - }, - - onMute(account) { - if (account.getIn(['relationship', 'muting'])) { - dispatch(unmuteAccount(account.get('id'))); - } - }, - -}); - -class AccountCard extends ImmutablePureComponent { - - static propTypes = { - account: ImmutablePropTypes.record.isRequired, - intl: PropTypes.object.isRequired, - onFollow: PropTypes.func.isRequired, - onBlock: PropTypes.func.isRequired, - onMute: PropTypes.func.isRequired, - }; - - handleMouseEnter = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-original'); - } - }; - - handleMouseLeave = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-static'); - } - }; - - handleFollow = () => { - this.props.onFollow(this.props.account); - }; - - handleBlock = () => { - this.props.onBlock(this.props.account); - }; - - handleMute = () => { - this.props.onMute(this.props.account); - }; - - handleEditProfile = () => { - window.open('/settings/profile', '_blank'); - }; - - render() { - const { account, intl } = this.props; - - let actionBtn; - - if (me !== account.get('id')) { - if (!account.get('relationship')) { // Wait until the relationship is loaded - actionBtn = ''; - } else if (account.getIn(['relationship', 'requested'])) { - actionBtn = <Button text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />; - } else if (account.getIn(['relationship', 'muting'])) { - actionBtn = <Button text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />; - } else if (!account.getIn(['relationship', 'blocking'])) { - actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />; - } else if (account.getIn(['relationship', 'blocking'])) { - actionBtn = <Button text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />; - } - } else { - actionBtn = <Button text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />; - } - - return ( - <div className='account-card'> - <Link to={`/@${account.get('acct')}`} className='account-card__permalink'> - <div className='account-card__header'> - <img - src={ - autoPlayGif ? account.get('header') : account.get('header_static') - } - alt='' - /> - </div> - - <div className='account-card__title'> - <div className='account-card__title__avatar'><Avatar account={account} size={56} /></div> - <DisplayName account={account} /> - </div> - </Link> - - {account.get('note').length > 0 && ( - <div - className='account-card__bio translate' - onMouseEnter={this.handleMouseEnter} - onMouseLeave={this.handleMouseLeave} - dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }} - /> - )} - - <div className='account-card__actions'> - <div className='account-card__counters'> - <div className='account-card__counters__item'> - <ShortNumber value={account.get('statuses_count')} /> - <small> - <FormattedMessage id='account.posts' defaultMessage='Posts' /> - </small> - </div> - - <div className='account-card__counters__item'> - <ShortNumber value={account.get('followers_count')} />{' '} - <small> - <FormattedMessage - id='account.followers' - defaultMessage='Followers' - /> - </small> - </div> - - <div className='account-card__counters__item'> - <ShortNumber value={account.get('following_count')} />{' '} - <small> - <FormattedMessage - id='account.following' - defaultMessage='Following' - /> - </small> - </div> - </div> - - <div className='account-card__actions__button'> - {actionBtn} - </div> - </div> - </div> - ); - } - -} - -export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(AccountCard)); diff --git a/app/javascript/mastodon/features/directory/components/account_card.tsx b/app/javascript/mastodon/features/directory/components/account_card.tsx new file mode 100644 index 000000000..7201f6135 --- /dev/null +++ b/app/javascript/mastodon/features/directory/components/account_card.tsx @@ -0,0 +1,269 @@ +import type { MouseEventHandler } from 'react'; +import { useCallback } from 'react'; + +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; +import { Link } from 'react-router-dom'; + +import { + followAccount, + unfollowAccount, + unblockAccount, + unmuteAccount, +} from 'mastodon/actions/accounts'; +import { openModal } from 'mastodon/actions/modal'; +import { Avatar } from 'mastodon/components/avatar'; +import { Button } from 'mastodon/components/button'; +import { DisplayName } from 'mastodon/components/display_name'; +import { ShortNumber } from 'mastodon/components/short_number'; +import { autoPlayGif, me } from 'mastodon/initial_state'; +import type { Account } from 'mastodon/models/account'; +import { makeGetAccount } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, + follow: { id: 'account.follow', defaultMessage: 'Follow' }, + cancel_follow_request: { + id: 'account.cancel_follow_request', + defaultMessage: 'Withdraw follow request', + }, + cancelFollowRequestConfirm: { + id: 'confirmations.cancel_follow_request.confirm', + defaultMessage: 'Withdraw request', + }, + requested: { + id: 'account.requested', + defaultMessage: 'Awaiting approval. Click to cancel follow request', + }, + unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' }, + unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' }, + unfollowConfirm: { + id: 'confirmations.unfollow.confirm', + defaultMessage: 'Unfollow', + }, + edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, +}); + +const getAccount = makeGetAccount(); + +export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => { + const intl = useIntl(); + const account = useAppSelector((s) => getAccount(s, accountId)); + const dispatch = useAppDispatch(); + + const handleMouseEnter = useCallback<MouseEventHandler>( + ({ currentTarget }) => { + if (autoPlayGif) { + return; + } + const emojis = + currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji'); + + emojis.forEach((emoji) => { + const original = emoji.getAttribute('data-original'); + if (original) emoji.src = original; + }); + }, + [], + ); + + const handleMouseLeave = useCallback<MouseEventHandler>( + ({ currentTarget }) => { + if (autoPlayGif) { + return; + } + + const emojis = + currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji'); + + emojis.forEach((emoji) => { + const staticUrl = emoji.getAttribute('data-static'); + if (staticUrl) emoji.src = staticUrl; + }); + }, + [], + ); + + const handleFollow = useCallback(() => { + if (!account) return; + + if (account.getIn(['relationship', 'following'])) { + dispatch( + openModal({ + modalType: 'CONFIRM', + modalProps: { + message: ( + <FormattedMessage + id='confirmations.unfollow.message' + defaultMessage='Are you sure you want to unfollow {name}?' + values={{ name: <strong>@{account.get('acct')}</strong> }} + /> + ), + confirm: intl.formatMessage(messages.unfollowConfirm), + onConfirm: () => { + dispatch(unfollowAccount(account.get('id'))); + }, + }, + }), + ); + } else if (account.getIn(['relationship', 'requested'])) { + dispatch( + openModal({ + modalType: 'CONFIRM', + modalProps: { + message: ( + <FormattedMessage + id='confirmations.cancel_follow_request.message' + defaultMessage='Are you sure you want to withdraw your request to follow {name}?' + values={{ name: <strong>@{account.get('acct')}</strong> }} + /> + ), + confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), + onConfirm: () => { + dispatch(unfollowAccount(account.get('id'))); + }, + }, + }), + ); + } else { + dispatch(followAccount(account.get('id'))); + } + }, [account, dispatch, intl]); + + const handleBlock = useCallback(() => { + if (account?.relationship?.blocking) { + dispatch(unblockAccount(account.get('id'))); + } + }, [account, dispatch]); + + const handleMute = useCallback(() => { + if (account?.relationship?.muting) { + dispatch(unmuteAccount(account.get('id'))); + } + }, [account, dispatch]); + + const handleEditProfile = useCallback(() => { + window.open('/settings/profile', '_blank'); + }, []); + + if (!account) return null; + + let actionBtn; + + if (me !== account.get('id')) { + if (!account.get('relationship')) { + // Wait until the relationship is loaded + actionBtn = ''; + } else if (account.getIn(['relationship', 'requested'])) { + actionBtn = ( + <Button + text={intl.formatMessage(messages.cancel_follow_request)} + title={intl.formatMessage(messages.requested)} + onClick={handleFollow} + /> + ); + } else if (account.getIn(['relationship', 'muting'])) { + actionBtn = ( + <Button + text={intl.formatMessage(messages.unmute)} + onClick={handleMute} + /> + ); + } else if (!account.getIn(['relationship', 'blocking'])) { + actionBtn = ( + <Button + disabled={account.relationship?.blocked_by} + className={classNames({ + 'button--destructive': account.getIn(['relationship', 'following']), + })} + text={intl.formatMessage( + account.getIn(['relationship', 'following']) + ? messages.unfollow + : messages.follow, + )} + onClick={handleFollow} + /> + ); + } else if (account.getIn(['relationship', 'blocking'])) { + actionBtn = ( + <Button + text={intl.formatMessage(messages.unblock)} + onClick={handleBlock} + /> + ); + } + } else { + actionBtn = ( + <Button + text={intl.formatMessage(messages.edit_profile)} + onClick={handleEditProfile} + /> + ); + } + + return ( + <div className='account-card'> + <Link to={`/@${account.get('acct')}`} className='account-card__permalink'> + <div className='account-card__header'> + <img + src={ + autoPlayGif ? account.get('header') : account.get('header_static') + } + alt='' + /> + </div> + + <div className='account-card__title'> + <div className='account-card__title__avatar'> + <Avatar account={account as Account} size={56} /> + </div> + <DisplayName account={account as Account} /> + </div> + </Link> + + {account.get('note').length > 0 && ( + <div + className='account-card__bio translate' + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }} + /> + )} + + <div className='account-card__actions'> + <div className='account-card__counters'> + <div className='account-card__counters__item'> + <ShortNumber value={account.get('statuses_count')} /> + <small> + <FormattedMessage id='account.posts' defaultMessage='Posts' /> + </small> + </div> + + <div className='account-card__counters__item'> + <ShortNumber value={account.get('followers_count')} />{' '} + <small> + <FormattedMessage + id='account.followers' + defaultMessage='Followers' + /> + </small> + </div> + + <div className='account-card__counters__item'> + <ShortNumber value={account.get('following_count')} />{' '} + <small> + <FormattedMessage + id='account.following' + defaultMessage='Following' + /> + </small> + </div> + </div> + + <div className='account-card__actions__button'>{actionBtn}</div> + </div> + </div> + ); +}; diff --git a/app/javascript/mastodon/features/directory/index.jsx b/app/javascript/mastodon/features/directory/index.jsx deleted file mode 100644 index 0d3408146..000000000 --- a/app/javascript/mastodon/features/directory/index.jsx +++ /dev/null @@ -1,181 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { defineMessages, injectIntl } from 'react-intl'; - -import { Helmet } from 'react-helmet'; - -import { List as ImmutableList } from 'immutable'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; - -import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; -import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'mastodon/actions/columns'; -import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory'; -import Column from 'mastodon/components/column'; -import ColumnHeader from 'mastodon/components/column_header'; -import { LoadMore } from 'mastodon/components/load_more'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import { RadioButton } from 'mastodon/components/radio_button'; -import ScrollContainer from 'mastodon/containers/scroll_container'; - -import AccountCard from './components/account_card'; - -const messages = defineMessages({ - title: { id: 'column.directory', defaultMessage: 'Browse profiles' }, - recentlyActive: { id: 'directory.recently_active', defaultMessage: 'Recently active' }, - newArrivals: { id: 'directory.new_arrivals', defaultMessage: 'New arrivals' }, - local: { id: 'directory.local', defaultMessage: 'From {domain} only' }, - federated: { id: 'directory.federated', defaultMessage: 'From known fediverse' }, -}); - -const mapStateToProps = state => ({ - accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()), - isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true), - domain: state.getIn(['meta', 'domain']), -}); - -class Directory extends PureComponent { - - static propTypes = { - isLoading: PropTypes.bool, - accountIds: ImmutablePropTypes.list.isRequired, - dispatch: PropTypes.func.isRequired, - columnId: PropTypes.string, - intl: PropTypes.object.isRequired, - multiColumn: PropTypes.bool, - domain: PropTypes.string.isRequired, - params: PropTypes.shape({ - order: PropTypes.string, - local: PropTypes.bool, - }), - }; - - state = { - order: null, - local: null, - }; - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('DIRECTORY', this.getParams(this.props, this.state))); - } - }; - - getParams = (props, state) => ({ - order: state.order === null ? (props.params.order || 'active') : state.order, - local: state.local === null ? (props.params.local || false) : state.local, - }); - - handleMove = dir => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - componentDidMount () { - const { dispatch } = this.props; - dispatch(fetchDirectory(this.getParams(this.props, this.state))); - } - - componentDidUpdate (prevProps, prevState) { - const { dispatch } = this.props; - const paramsOld = this.getParams(prevProps, prevState); - const paramsNew = this.getParams(this.props, this.state); - - if (paramsOld.order !== paramsNew.order || paramsOld.local !== paramsNew.local) { - dispatch(fetchDirectory(paramsNew)); - } - } - - setRef = c => { - this.column = c; - }; - - handleChangeOrder = e => { - const { dispatch, columnId } = this.props; - - if (columnId) { - dispatch(changeColumnParams(columnId, ['order'], e.target.value)); - } else { - this.setState({ order: e.target.value }); - } - }; - - handleChangeLocal = e => { - const { dispatch, columnId } = this.props; - - if (columnId) { - dispatch(changeColumnParams(columnId, ['local'], e.target.value === '1')); - } else { - this.setState({ local: e.target.value === '1' }); - } - }; - - handleLoadMore = () => { - const { dispatch } = this.props; - dispatch(expandDirectory(this.getParams(this.props, this.state))); - }; - - render () { - const { isLoading, accountIds, intl, columnId, multiColumn, domain } = this.props; - const { order, local } = this.getParams(this.props, this.state); - const pinned = !!columnId; - - const scrollableArea = ( - <div className='scrollable'> - <div className='filter-form'> - <div className='filter-form__column' role='group'> - <RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === 'active'} onChange={this.handleChangeOrder} /> - <RadioButton name='order' value='new' label={intl.formatMessage(messages.newArrivals)} checked={order === 'new'} onChange={this.handleChangeOrder} /> - </div> - - <div className='filter-form__column' role='group'> - <RadioButton name='local' value='1' label={intl.formatMessage(messages.local, { domain })} checked={local} onChange={this.handleChangeLocal} /> - <RadioButton name='local' value='0' label={intl.formatMessage(messages.federated)} checked={!local} onChange={this.handleChangeLocal} /> - </div> - </div> - - <div className='directory__list'> - {isLoading ? <LoadingIndicator /> : accountIds.map(accountId => ( - <AccountCard id={accountId} key={accountId} /> - ))} - </div> - - <LoadMore onClick={this.handleLoadMore} visible={!isLoading} /> - </div> - ); - - return ( - <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> - <ColumnHeader - icon='address-book-o' - iconComponent={PeopleIcon} - title={intl.formatMessage(messages.title)} - onPin={this.handlePin} - onMove={this.handleMove} - onClick={this.handleHeaderClick} - pinned={pinned} - multiColumn={multiColumn} - /> - - {multiColumn && !pinned ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea} - - <Helmet> - <title>{intl.formatMessage(messages.title)}</title> - <meta name='robots' content='noindex' /> - </Helmet> - </Column> - ); - } - -} - -export default connect(mapStateToProps)(injectIntl(Directory)); diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx new file mode 100644 index 000000000..482c6858f --- /dev/null +++ b/app/javascript/mastodon/features/directory/index.tsx @@ -0,0 +1,217 @@ +import type { ChangeEventHandler } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import { List as ImmutableList } from 'immutable'; + +import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; +import { + addColumn, + removeColumn, + moveColumn, + changeColumnParams, +} from 'mastodon/actions/columns'; +import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory'; +import Column from 'mastodon/components/column'; +import ColumnHeader from 'mastodon/components/column_header'; +import { LoadMore } from 'mastodon/components/load_more'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { RadioButton } from 'mastodon/components/radio_button'; +import ScrollContainer from 'mastodon/containers/scroll_container'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +import { AccountCard } from './components/account_card'; + +const messages = defineMessages({ + title: { id: 'column.directory', defaultMessage: 'Browse profiles' }, + recentlyActive: { + id: 'directory.recently_active', + defaultMessage: 'Recently active', + }, + newArrivals: { id: 'directory.new_arrivals', defaultMessage: 'New arrivals' }, + local: { id: 'directory.local', defaultMessage: 'From {domain} only' }, + federated: { + id: 'directory.federated', + defaultMessage: 'From known fediverse', + }, +}); + +export const Directory: React.FC<{ + columnId?: string; + multiColumn?: boolean; + params?: { order: string; local?: boolean }; +}> = ({ columnId, multiColumn, params }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const [state, setState] = useState<{ + order: string | null; + local: boolean | null; + }>({ + order: null, + local: null, + }); + + const column = useRef<Column>(null); + + const order = state.order ?? params?.order ?? 'active'; + const local = state.local ?? params?.local ?? false; + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('DIRECTORY', { order, local })); + } + }, [dispatch, columnId, order, local]); + + const domain = useAppSelector((s) => s.meta.get('domain') as string); + const accountIds = useAppSelector( + (state) => + state.user_lists.getIn( + ['directory', 'items'], + ImmutableList(), + ) as ImmutableList<string>, + ); + const isLoading = useAppSelector( + (state) => + state.user_lists.getIn(['directory', 'isLoading'], true) as boolean, + ); + + useEffect(() => { + void dispatch(fetchDirectory({ order, local })); + }, [dispatch, order, local]); + + const handleMove = useCallback( + (dir: string) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + column.current?.scrollTop(); + }, []); + + const handleChangeOrder = useCallback<ChangeEventHandler<HTMLInputElement>>( + (e) => { + if (columnId) { + dispatch(changeColumnParams(columnId, ['order'], e.target.value)); + } else { + setState((s) => ({ order: e.target.value, local: s.local })); + } + }, + [dispatch, columnId], + ); + + const handleChangeLocal = useCallback<ChangeEventHandler<HTMLInputElement>>( + (e) => { + if (columnId) { + dispatch( + changeColumnParams(columnId, ['local'], e.target.value === '1'), + ); + } else { + setState((s) => ({ local: e.target.value === '1', order: s.order })); + } + }, + [dispatch, columnId], + ); + + const handleLoadMore = useCallback(() => { + void dispatch(expandDirectory({ order, local })); + }, [dispatch, order, local]); + + const pinned = !!columnId; + + const scrollableArea = ( + <div className='scrollable'> + <div className='filter-form'> + <div className='filter-form__column' role='group'> + <RadioButton + name='order' + value='active' + label={intl.formatMessage(messages.recentlyActive)} + checked={order === 'active'} + onChange={handleChangeOrder} + /> + <RadioButton + name='order' + value='new' + label={intl.formatMessage(messages.newArrivals)} + checked={order === 'new'} + onChange={handleChangeOrder} + /> + </div> + + <div className='filter-form__column' role='group'> + <RadioButton + name='local' + value='1' + label={intl.formatMessage(messages.local, { domain })} + checked={local} + onChange={handleChangeLocal} + /> + <RadioButton + name='local' + value='0' + label={intl.formatMessage(messages.federated)} + checked={!local} + onChange={handleChangeLocal} + /> + </div> + </div> + + <div className='directory__list'> + {isLoading ? ( + <LoadingIndicator /> + ) : ( + accountIds.map((accountId) => ( + <AccountCard accountId={accountId} key={accountId} /> + )) + )} + </div> + + <LoadMore onClick={handleLoadMore} visible={!isLoading} /> + </div> + ); + + return ( + <Column + bindToDocument={!multiColumn} + ref={column} + label={intl.formatMessage(messages.title)} + > + <ColumnHeader + // @ts-expect-error ColumnHeader is not properly typed yet + icon='address-book-o' + iconComponent={PeopleIcon} + title={intl.formatMessage(messages.title)} + onPin={handlePin} + onMove={handleMove} + onClick={handleHeaderClick} + pinned={pinned} + multiColumn={multiColumn} + /> + + {multiColumn && !pinned ? ( + // @ts-expect-error ScrollContainer is not properly typed yet + <ScrollContainer scrollKey='directory'> + {scrollableArea} + </ScrollContainer> + ) : ( + scrollableArea + )} + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='noindex' /> + </Helmet> + </Column> + ); +}; + +// eslint-disable-next-line import/no-default-export -- Needed because this is called as an async components +export default Directory; diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index 2f17fed5f..7a4c04c5c 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -1,12 +1,8 @@ import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { - DIRECTORY_FETCH_REQUEST, - DIRECTORY_FETCH_SUCCESS, - DIRECTORY_FETCH_FAIL, - DIRECTORY_EXPAND_REQUEST, - DIRECTORY_EXPAND_SUCCESS, - DIRECTORY_EXPAND_FAIL, + expandDirectory, + fetchDirectory } from 'mastodon/actions/directory'; import { FEATURED_TAGS_FETCH_REQUEST, @@ -117,6 +113,7 @@ const normalizeFeaturedTags = (state, path, featuredTags, accountId) => { })); }; +/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */ export default function userLists(state = initialState, action) { switch(action.type) { case FOLLOWERS_FETCH_SUCCESS: @@ -194,16 +191,6 @@ export default function userLists(state = initialState, action) { case MUTES_FETCH_FAIL: case MUTES_EXPAND_FAIL: return state.setIn(['mutes', 'isLoading'], false); - case DIRECTORY_FETCH_SUCCESS: - return normalizeList(state, ['directory'], action.accounts, action.next); - case DIRECTORY_EXPAND_SUCCESS: - return appendToList(state, ['directory'], action.accounts, action.next); - case DIRECTORY_FETCH_REQUEST: - case DIRECTORY_EXPAND_REQUEST: - return state.setIn(['directory', 'isLoading'], true); - case DIRECTORY_FETCH_FAIL: - case DIRECTORY_EXPAND_FAIL: - return state.setIn(['directory', 'isLoading'], false); case FEATURED_TAGS_FETCH_SUCCESS: return normalizeFeaturedTags(state, ['featured_tags', action.id], action.tags, action.id); case FEATURED_TAGS_FETCH_REQUEST: @@ -211,6 +198,17 @@ export default function userLists(state = initialState, action) { case FEATURED_TAGS_FETCH_FAIL: return state.setIn(['featured_tags', action.id, 'isLoading'], false); default: - return state; + if(fetchDirectory.fulfilled.match(action)) + return normalizeList(state, ['directory'], action.payload.accounts, undefined); + else if( expandDirectory.fulfilled.match(action)) + return appendToList(state, ['directory'], action.payload.accounts, undefined); + else if(fetchDirectory.pending.match(action) || + expandDirectory.pending.match(action)) + return state.setIn(['directory', 'isLoading'], true); + else if(fetchDirectory.rejected.match(action) || + expandDirectory.rejected.match(action)) + return state.setIn(['directory', 'isLoading'], false); + else + return state; } } From e89317d4c1da991b728b6d4a21671ed33f057cc4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Wed, 26 Jun 2024 21:33:38 +0200 Subject: [PATCH 26/84] Add hover cards in web UI (#30754) Co-authored-by: Renaud Chaput <renchap@gmail.com> --- app/javascript/hooks/useLinks.ts | 61 ++++++ app/javascript/hooks/useTimeout.ts | 29 +++ .../mastodon/components/account_bio.tsx | 20 ++ .../mastodon/components/account_fields.tsx | 42 +++++ .../mastodon/components/follow_button.tsx | 93 +++++++++ .../components/hover_card_account.tsx | 74 ++++++++ .../components/hover_card_controller.tsx | 117 ++++++++++++ app/javascript/mastodon/components/status.jsx | 6 +- .../mastodon/components/status_content.jsx | 3 +- .../explore/components/author_link.jsx | 2 +- .../features/explore/components/card.jsx | 17 +- .../components/inline_follow_suggestions.jsx | 15 +- .../notifications/components/notification.jsx | 4 +- .../status/components/detailed_status.jsx | 2 +- app/javascript/mastodon/features/ui/index.jsx | 2 + app/javascript/mastodon/locales/en.json | 6 +- .../styles/mastodon-light/variables.scss | 2 + .../styles/mastodon/components.scss | 178 +++++++++++++++++- 18 files changed, 631 insertions(+), 42 deletions(-) create mode 100644 app/javascript/hooks/useLinks.ts create mode 100644 app/javascript/hooks/useTimeout.ts create mode 100644 app/javascript/mastodon/components/account_bio.tsx create mode 100644 app/javascript/mastodon/components/account_fields.tsx create mode 100644 app/javascript/mastodon/components/follow_button.tsx create mode 100644 app/javascript/mastodon/components/hover_card_account.tsx create mode 100644 app/javascript/mastodon/components/hover_card_controller.tsx diff --git a/app/javascript/hooks/useLinks.ts b/app/javascript/hooks/useLinks.ts new file mode 100644 index 000000000..f08b9500d --- /dev/null +++ b/app/javascript/hooks/useLinks.ts @@ -0,0 +1,61 @@ +import { useCallback } from 'react'; + +import { useHistory } from 'react-router-dom'; + +import { openURL } from 'mastodon/actions/search'; +import { useAppDispatch } from 'mastodon/store'; + +const isMentionClick = (element: HTMLAnchorElement) => + element.classList.contains('mention'); + +const isHashtagClick = (element: HTMLAnchorElement) => + element.textContent?.[0] === '#' || + element.previousSibling?.textContent?.endsWith('#'); + +export const useLinks = () => { + const history = useHistory(); + const dispatch = useAppDispatch(); + + const handleHashtagClick = useCallback( + (element: HTMLAnchorElement) => { + const { textContent } = element; + + if (!textContent) return; + + history.push(`/tags/${textContent.replace(/^#/, '')}`); + }, + [history], + ); + + const handleMentionClick = useCallback( + (element: HTMLAnchorElement) => { + dispatch( + openURL(element.href, history, () => { + window.location.href = element.href; + }), + ); + }, + [dispatch, history], + ); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + const target = (e.target as HTMLElement).closest('a'); + + if (!target || e.button !== 0 || e.ctrlKey || e.metaKey) { + return; + } + + if (isMentionClick(target)) { + e.preventDefault(); + handleMentionClick(target); + } else if (isHashtagClick(target)) { + e.preventDefault(); + handleHashtagClick(target); + } + }, + [handleMentionClick, handleHashtagClick], + ); + + return handleClick; +}; diff --git a/app/javascript/hooks/useTimeout.ts b/app/javascript/hooks/useTimeout.ts new file mode 100644 index 000000000..f1814ae8e --- /dev/null +++ b/app/javascript/hooks/useTimeout.ts @@ -0,0 +1,29 @@ +import { useRef, useCallback, useEffect } from 'react'; + +export const useTimeout = () => { + const timeoutRef = useRef<ReturnType<typeof setTimeout>>(); + + const set = useCallback((callback: () => void, delay: number) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + timeoutRef.current = setTimeout(callback, delay); + }, []); + + const cancel = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = undefined; + } + }, []); + + useEffect( + () => () => { + cancel(); + }, + [cancel], + ); + + return [set, cancel] as const; +}; diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx new file mode 100644 index 000000000..9d523c740 --- /dev/null +++ b/app/javascript/mastodon/components/account_bio.tsx @@ -0,0 +1,20 @@ +import { useLinks } from 'mastodon/../hooks/useLinks'; + +export const AccountBio: React.FC<{ + note: string; + className: string; +}> = ({ note, className }) => { + const handleClick = useLinks(); + + if (note.length === 0 || note === '<p></p>') { + return null; + } + + return ( + <div + className={`${className} translate`} + dangerouslySetInnerHTML={{ __html: note }} + onClickCapture={handleClick} + /> + ); +}; diff --git a/app/javascript/mastodon/components/account_fields.tsx b/app/javascript/mastodon/components/account_fields.tsx new file mode 100644 index 000000000..e297f99e3 --- /dev/null +++ b/app/javascript/mastodon/components/account_fields.tsx @@ -0,0 +1,42 @@ +import classNames from 'classnames'; + +import CheckIcon from '@/material-icons/400-24px/check.svg?react'; +import { useLinks } from 'mastodon/../hooks/useLinks'; +import { Icon } from 'mastodon/components/icon'; +import type { Account } from 'mastodon/models/account'; + +export const AccountFields: React.FC<{ + fields: Account['fields']; + limit: number; +}> = ({ fields, limit = -1 }) => { + const handleClick = useLinks(); + + if (fields.size === 0) { + return null; + } + + return ( + <div className='account-fields' onClickCapture={handleClick}> + {fields.take(limit).map((pair, i) => ( + <dl + key={i} + className={classNames({ verified: pair.get('verified_at') })} + > + <dt + dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} + className='translate' + /> + + <dd className='translate' title={pair.get('value_plain') ?? ''}> + {pair.get('verified_at') && ( + <Icon id='check' icon={CheckIcon} className='verified__mark' /> + )} + <span + dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} + /> + </dd> + </dl> + ))} + </div> + ); +}; diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx new file mode 100644 index 000000000..4b4d27831 --- /dev/null +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -0,0 +1,93 @@ +import { useCallback, useEffect } from 'react'; + +import { useIntl, defineMessages } from 'react-intl'; + +import { + fetchRelationships, + followAccount, + unfollowAccount, +} from 'mastodon/actions/accounts'; +import { Button } from 'mastodon/components/button'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { me } from 'mastodon/initial_state'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, + follow: { id: 'account.follow', defaultMessage: 'Follow' }, + followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' }, + mutual: { id: 'account.mutual', defaultMessage: 'Mutual' }, + cancel_follow_request: { + id: 'account.cancel_follow_request', + defaultMessage: 'Withdraw follow request', + }, + edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, +}); + +export const FollowButton: React.FC<{ + accountId: string; +}> = ({ accountId }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const relationship = useAppSelector((state) => + state.relationships.get(accountId), + ); + const following = relationship?.following || relationship?.requested; + + useEffect(() => { + dispatch(fetchRelationships([accountId])); + }, [dispatch, accountId]); + + const handleClick = useCallback(() => { + if (!relationship) return; + if (accountId === me) { + return; + } else if (relationship.following || relationship.requested) { + dispatch(unfollowAccount(accountId)); + } else { + dispatch(followAccount(accountId)); + } + }, [dispatch, accountId, relationship]); + + let label; + + if (accountId === me) { + label = intl.formatMessage(messages.edit_profile); + } else if (!relationship) { + label = <LoadingIndicator />; + } else if (relationship.requested) { + label = intl.formatMessage(messages.cancel_follow_request); + } else if (relationship.following && relationship.followed_by) { + label = intl.formatMessage(messages.mutual); + } else if (!relationship.following && relationship.followed_by) { + label = intl.formatMessage(messages.followBack); + } else if (relationship.following) { + label = intl.formatMessage(messages.unfollow); + } else { + label = intl.formatMessage(messages.follow); + } + + if (accountId === me) { + return ( + <a + href='/settings/profile' + target='_blank' + rel='noreferrer noopener' + className='button button-secondary' + > + {label} + </a> + ); + } + + return ( + <Button + onClick={handleClick} + disabled={relationship?.blocked_by || relationship?.blocking} + secondary={following} + className={following ? 'button--destructive' : undefined} + > + {label} + </Button> + ); +}; diff --git a/app/javascript/mastodon/components/hover_card_account.tsx b/app/javascript/mastodon/components/hover_card_account.tsx new file mode 100644 index 000000000..59f957783 --- /dev/null +++ b/app/javascript/mastodon/components/hover_card_account.tsx @@ -0,0 +1,74 @@ +import { useEffect, forwardRef } from 'react'; + +import classNames from 'classnames'; +import { Link } from 'react-router-dom'; + +import { fetchAccount } from 'mastodon/actions/accounts'; +import { AccountBio } from 'mastodon/components/account_bio'; +import { AccountFields } from 'mastodon/components/account_fields'; +import { Avatar } from 'mastodon/components/avatar'; +import { FollowersCounter } from 'mastodon/components/counters'; +import { DisplayName } from 'mastodon/components/display_name'; +import { FollowButton } from 'mastodon/components/follow_button'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { ShortNumber } from 'mastodon/components/short_number'; +import { domain } from 'mastodon/initial_state'; +import { useAppSelector, useAppDispatch } from 'mastodon/store'; + +export const HoverCardAccount = forwardRef< + HTMLDivElement, + { accountId: string } +>(({ accountId }, ref) => { + const dispatch = useAppDispatch(); + + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + + useEffect(() => { + if (accountId && !account) { + dispatch(fetchAccount(accountId)); + } + }, [dispatch, accountId, account]); + + return ( + <div + ref={ref} + id='hover-card' + role='tooltip' + className={classNames('hover-card dropdown-animation', { + 'hover-card--loading': !account, + })} + > + {account ? ( + <> + <Link to={`/@${account.acct}`} className='hover-card__name'> + <Avatar account={account} size={46} /> + <DisplayName account={account} localDomain={domain} /> + </Link> + + <div className='hover-card__text-row'> + <AccountBio + note={account.note_emojified} + className='hover-card__bio' + /> + <AccountFields fields={account.fields} limit={2} /> + </div> + + <div className='hover-card__number'> + <ShortNumber + value={account.followers_count} + renderer={FollowersCounter} + /> + </div> + + <FollowButton accountId={accountId} /> + </> + ) : ( + <LoadingIndicator /> + )} + </div> + ); +}); + +HoverCardAccount.displayName = 'HoverCardAccount'; diff --git a/app/javascript/mastodon/components/hover_card_controller.tsx b/app/javascript/mastodon/components/hover_card_controller.tsx new file mode 100644 index 000000000..0130390ef --- /dev/null +++ b/app/javascript/mastodon/components/hover_card_controller.tsx @@ -0,0 +1,117 @@ +import { useEffect, useRef, useState, useCallback } from 'react'; + +import { useLocation } from 'react-router-dom'; + +import Overlay from 'react-overlays/Overlay'; +import type { + OffsetValue, + UsePopperOptions, +} from 'react-overlays/esm/usePopper'; + +import { useTimeout } from 'mastodon/../hooks/useTimeout'; +import { HoverCardAccount } from 'mastodon/components/hover_card_account'; + +const offset = [-12, 4] as OffsetValue; +const enterDelay = 650; +const leaveDelay = 250; +const popperConfig = { strategy: 'fixed' } as UsePopperOptions; + +const isHoverCardAnchor = (element: HTMLElement) => + element.matches('[data-hover-card-account]'); + +export const HoverCardController: React.FC = () => { + const [open, setOpen] = useState(false); + const [accountId, setAccountId] = useState<string | undefined>(); + const [anchor, setAnchor] = useState<HTMLElement | null>(null); + const cardRef = useRef<HTMLDivElement>(null); + const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout(); + const [setEnterTimeout, cancelEnterTimeout] = useTimeout(); + const location = useLocation(); + + const handleAnchorMouseEnter = useCallback( + (e: MouseEvent) => { + const { target } = e; + + if (target instanceof HTMLElement && isHoverCardAnchor(target)) { + cancelLeaveTimeout(); + + setEnterTimeout(() => { + target.setAttribute('aria-describedby', 'hover-card'); + setAnchor(target); + setOpen(true); + setAccountId( + target.getAttribute('data-hover-card-account') ?? undefined, + ); + }, enterDelay); + } + + if (target === cardRef.current?.parentNode) { + cancelLeaveTimeout(); + } + }, + [cancelLeaveTimeout, setEnterTimeout, setOpen, setAccountId, setAnchor], + ); + + const handleAnchorMouseLeave = useCallback( + (e: MouseEvent) => { + if (e.target === anchor || e.target === cardRef.current?.parentNode) { + cancelEnterTimeout(); + + setLeaveTimeout(() => { + anchor?.removeAttribute('aria-describedby'); + setOpen(false); + setAnchor(null); + }, leaveDelay); + } + }, + [cancelEnterTimeout, setLeaveTimeout, setOpen, setAnchor, anchor], + ); + + const handleClose = useCallback(() => { + cancelEnterTimeout(); + cancelLeaveTimeout(); + setOpen(false); + setAnchor(null); + }, [cancelEnterTimeout, cancelLeaveTimeout, setOpen, setAnchor]); + + useEffect(() => { + handleClose(); + }, [handleClose, location]); + + useEffect(() => { + document.body.addEventListener('mouseenter', handleAnchorMouseEnter, { + passive: true, + capture: true, + }); + document.body.addEventListener('mouseleave', handleAnchorMouseLeave, { + passive: true, + capture: true, + }); + + return () => { + document.body.removeEventListener('mouseenter', handleAnchorMouseEnter); + document.body.removeEventListener('mouseleave', handleAnchorMouseLeave); + }; + }, [handleAnchorMouseEnter, handleAnchorMouseLeave]); + + if (!accountId) return null; + + return ( + <Overlay + rootClose + onHide={handleClose} + show={open} + target={anchor} + placement='bottom-start' + flip + offset={offset} + popperConfig={popperConfig} + > + {({ props }) => ( + <div {...props} className='hover-card-controller'> + <HoverCardAccount accountId={accountId} ref={cardRef} /> + </div> + )} + </Overlay> + ); +}; diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 7b97e4576..dce48d703 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -425,7 +425,7 @@ class Status extends ImmutablePureComponent { prepend = ( <div className='status__prepend'> <div className='status__prepend-icon-wrapper'><Icon id='retweet' icon={RepeatIcon} className='status__prepend-icon' /></div> - <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} /> + <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} /> </div> ); @@ -446,7 +446,7 @@ class Status extends ImmutablePureComponent { prepend = ( <div className='status__prepend'> <div className='status__prepend-icon-wrapper'><Icon id='reply' icon={ReplyIcon} className='status__prepend-icon' /></div> - <FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} /> + <FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} /> </div> ); } @@ -562,7 +562,7 @@ class Status extends ImmutablePureComponent { <RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>} </a> - <a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'> + <a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name' target='_blank' rel='noopener noreferrer'> <div className='status__avatar'> {statusAvatar} </div> diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 24483cf51..82135b85c 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -116,8 +116,9 @@ class StatusContent extends PureComponent { if (mention) { link.addEventListener('click', this.onMentionClick.bind(this, mention), false); - link.setAttribute('title', `@${mention.get('acct')}`); + link.removeAttribute('title'); link.setAttribute('href', `/@${mention.get('acct')}`); + link.setAttribute('data-hover-card-account', mention.get('id')); } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`); diff --git a/app/javascript/mastodon/features/explore/components/author_link.jsx b/app/javascript/mastodon/features/explore/components/author_link.jsx index b9dec3367..8dd9b0dab 100644 --- a/app/javascript/mastodon/features/explore/components/author_link.jsx +++ b/app/javascript/mastodon/features/explore/components/author_link.jsx @@ -9,7 +9,7 @@ export const AuthorLink = ({ accountId }) => { const account = useAppSelector(state => state.getIn(['accounts', accountId])); return ( - <Link to={`/@${account.get('acct')}`} className='story__details__shared__author-link'> + <Link to={`/@${account.get('acct')}`} className='story__details__shared__author-link' data-hover-card-account={accountId}> <Avatar account={account} size={16} /> <bdi dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /> </Link> diff --git a/app/javascript/mastodon/features/explore/components/card.jsx b/app/javascript/mastodon/features/explore/components/card.jsx index 316203060..190864851 100644 --- a/app/javascript/mastodon/features/explore/components/card.jsx +++ b/app/javascript/mastodon/features/explore/components/card.jsx @@ -8,34 +8,21 @@ import { Link } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; -import { followAccount, unfollowAccount } from 'mastodon/actions/accounts'; import { dismissSuggestion } from 'mastodon/actions/suggestions'; import { Avatar } from 'mastodon/components/avatar'; -import { Button } from 'mastodon/components/button'; import { DisplayName } from 'mastodon/components/display_name'; +import { FollowButton } from 'mastodon/components/follow_button'; import { IconButton } from 'mastodon/components/icon_button'; import { domain } from 'mastodon/initial_state'; const messages = defineMessages({ - follow: { id: 'account.follow', defaultMessage: 'Follow' }, - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, dismiss: { id: 'follow_suggestions.dismiss', defaultMessage: "Don't show again" }, }); export const Card = ({ id, source }) => { const intl = useIntl(); const account = useSelector(state => state.getIn(['accounts', id])); - const relationship = useSelector(state => state.getIn(['relationships', id])); const dispatch = useDispatch(); - const following = relationship?.get('following') ?? relationship?.get('requested'); - - const handleFollow = useCallback(() => { - if (following) { - dispatch(unfollowAccount(id)); - } else { - dispatch(followAccount(id)); - } - }, [id, following, dispatch]); const handleDismiss = useCallback(() => { dispatch(dismissSuggestion(id)); @@ -74,7 +61,7 @@ export const Card = ({ id, source }) => { <div className='explore__suggestions__card__body__main__name-button'> <Link className='explore__suggestions__card__body__main__name-button__name' to={`/@${account.get('acct')}`}><DisplayName account={account} /></Link> <IconButton iconComponent={CloseIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} /> - <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} secondary={following} onClick={handleFollow} /> + <FollowButton accountId={account.get('id')} /> </div> </div> </div> diff --git a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx index c39b43bad..1b8040e55 100644 --- a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx +++ b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.jsx @@ -12,12 +12,11 @@ import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import InfoIcon from '@/material-icons/400-24px/info.svg?react'; -import { followAccount, unfollowAccount } from 'mastodon/actions/accounts'; import { changeSetting } from 'mastodon/actions/settings'; import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions'; import { Avatar } from 'mastodon/components/avatar'; -import { Button } from 'mastodon/components/button'; import { DisplayName } from 'mastodon/components/display_name'; +import { FollowButton } from 'mastodon/components/follow_button'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from 'mastodon/components/icon_button'; import { VerifiedBadge } from 'mastodon/components/verified_badge'; @@ -79,18 +78,8 @@ Source.propTypes = { const Card = ({ id, sources }) => { const intl = useIntl(); const account = useSelector(state => state.getIn(['accounts', id])); - const relationship = useSelector(state => state.getIn(['relationships', id])); const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at')); const dispatch = useDispatch(); - const following = relationship?.get('following') ?? relationship?.get('requested'); - - const handleFollow = useCallback(() => { - if (following) { - dispatch(unfollowAccount(id)); - } else { - dispatch(followAccount(id)); - } - }, [id, following, dispatch]); const handleDismiss = useCallback(() => { dispatch(dismissSuggestion(id)); @@ -109,7 +98,7 @@ const Card = ({ id, sources }) => { {firstVerifiedField ? <VerifiedBadge link={firstVerifiedField.get('value')} /> : <Source id={sources.get(0)} />} </div> - <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} secondary={following} onClick={handleFollow} /> + <FollowButton accountId={id} /> </div> ); }; diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx index 69084c211..272893042 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -435,7 +435,7 @@ class Notification extends ImmutablePureComponent { const targetAccount = report.get('target_account'); const targetDisplayNameHtml = { __html: targetAccount.get('display_name_html') }; - const targetLink = <bdi><Link className='notification__display-name' title={targetAccount.get('acct')} to={`/@${targetAccount.get('acct')}`} dangerouslySetInnerHTML={targetDisplayNameHtml} /></bdi>; + const targetLink = <bdi><Link className='notification__display-name' data-hover-card-account={targetAccount.get('id')} to={`/@${targetAccount.get('acct')}`} dangerouslySetInnerHTML={targetDisplayNameHtml} /></bdi>; return ( <HotKeys handlers={this.getHandlers()}> @@ -458,7 +458,7 @@ class Notification extends ImmutablePureComponent { const { notification } = this.props; const account = notification.get('account'); const displayNameHtml = { __html: account.get('display_name_html') }; - const link = <bdi><Link className='notification__display-name' href={`/@${account.get('acct')}`} title={account.get('acct')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>; + const link = <bdi><Link className='notification__display-name' href={`/@${account.get('acct')}`} data-hover-card-account={account.get('id')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>; switch(notification.get('type')) { case 'follow': diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index 8843619bc..bc81fd2df 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -272,7 +272,7 @@ class DetailedStatus extends ImmutablePureComponent { <FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' /> </div> )} - <a href={`/@${status.getIn(['account', 'acct'])}`} onClick={this.handleAccountClick} className='detailed-status__display-name'> + <a href={`/@${status.getIn(['account', 'acct'])}`} data-hover-card-account={status.getIn(['account', 'id'])} onClick={this.handleAccountClick} className='detailed-status__display-name'> <div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={46} /></div> <DisplayName account={status.get('account')} localDomain={this.props.domain} /> </a> diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 7742f6486..b58e191ed 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -14,6 +14,7 @@ import { HotKeys } from 'react-hotkeys'; import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers'; import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; +import { HoverCardController } from 'mastodon/components/hover_card_controller'; import { PictureInPicture } from 'mastodon/features/picture_in_picture'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { layoutFromWindow } from 'mastodon/is_mobile'; @@ -585,6 +586,7 @@ class UI extends PureComponent { {layout !== 'mobile' && <PictureInPicture />} <NotificationsContainer /> + <HoverCardController /> <LoadingBarContainer className='loading-bar' /> <ModalContainer /> <UploadArea active={draggingOver} onClose={this.closeUploadModal} /> diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index f0c27ad70..13296e1d2 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -35,9 +35,9 @@ "account.follow_back": "Follow back", "account.followers": "Followers", "account.followers.empty": "No one follows this user yet.", - "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}", + "account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}", "account.following": "Following", - "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}", + "account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}", "account.follows.empty": "This user doesn't follow anyone yet.", "account.go_to_profile": "Go to profile", "account.hide_reblogs": "Hide boosts from @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} has requested to follow you", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Show boosts from @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}", + "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}", "account.unblock": "Unblock @{name}", "account.unblock_domain": "Unblock domain {domain}", "account.unblock_short": "Unblock", diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss index 3cdbd9bf6..9f571b3f2 100644 --- a/app/javascript/styles/mastodon-light/variables.scss +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -59,6 +59,8 @@ $emojis-requiring-inversion: 'chains'; body { --dropdown-border-color: #d9e1e8; --dropdown-background-color: #fff; + --modal-border-color: #d9e1e8; + --modal-background-color: var(--background-color-tint); --background-border-color: #d9e1e8; --background-color: #fff; --background-color-tint: rgba(255, 255, 255, 80%); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 73d0e6220..cbf9314ff 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -120,8 +120,27 @@ text-decoration: none; } - &:disabled { - opacity: 0.5; + &.button--destructive { + &:active, + &:focus, + &:hover { + border-color: $ui-button-destructive-focus-background-color; + color: $ui-button-destructive-focus-background-color; + } + } + + &:disabled, + &.disabled { + opacity: 0.7; + border-color: $ui-primary-color; + color: $ui-primary-color; + + &:active, + &:focus, + &:hover { + border-color: $ui-primary-color; + color: $ui-primary-color; + } } } @@ -2420,7 +2439,7 @@ a.account__display-name { } .dropdown-animation { - animation: dropdown 150ms cubic-bezier(0.1, 0.7, 0.1, 1); + animation: dropdown 250ms cubic-bezier(0.1, 0.7, 0.1, 1); @keyframes dropdown { from { @@ -10325,3 +10344,156 @@ noscript { } } } + +.hover-card-controller[data-popper-reference-hidden='true'] { + opacity: 0; + pointer-events: none; +} + +.hover-card { + box-shadow: var(--dropdown-shadow); + background: var(--modal-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--modal-border-color); + border-radius: 8px; + padding: 16px; + width: 270px; + display: flex; + flex-direction: column; + gap: 12px; + + &--loading { + position: relative; + min-height: 100px; + } + + &__name { + display: flex; + gap: 12px; + text-decoration: none; + color: inherit; + } + + &__number { + font-size: 15px; + line-height: 22px; + color: $secondary-text-color; + + strong { + font-weight: 700; + } + } + + &__text-row { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__bio { + color: $secondary-text-color; + font-size: 14px; + line-height: 20px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + max-height: 2 * 20px; + overflow: hidden; + + p { + margin-bottom: 0; + } + + a { + color: inherit; + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + } + } + + .display-name { + font-size: 15px; + line-height: 22px; + + bdi { + font-weight: 500; + color: $primary-text-color; + } + + &__account { + display: block; + color: $dark-text-color; + } + } + + .account-fields { + color: $secondary-text-color; + font-size: 14px; + line-height: 20px; + + a { + color: inherit; + text-decoration: none; + + &:focus, + &:hover, + &:active { + text-decoration: underline; + } + } + + dl { + display: flex; + align-items: center; + gap: 4px; + + dt { + flex: 0 0 auto; + color: $dark-text-color; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + dd { + flex: 1 1 auto; + font-weight: 500; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + &.verified { + dd { + display: flex; + align-items: center; + gap: 4px; + overflow: hidden; + white-space: nowrap; + color: $valid-value-color; + + & > span { + overflow: hidden; + text-overflow: ellipsis; + } + + a { + font-weight: 500; + } + + .icon { + width: 16px; + height: 16px; + } + } + } + } + } +} From 3939352e92f4be13b773ee243bbb6ad54d6b5bd1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Wed, 26 Jun 2024 21:46:28 +0200 Subject: [PATCH 27/84] Convert `<ColumnHeader>` to Typescript (#30849) --- .../mastodon/components/column_header.jsx | 233 -------------- .../mastodon/components/column_header.tsx | 301 ++++++++++++++++++ .../mastodon/features/directory/index.tsx | 5 +- .../features/ui/components/column_loading.tsx | 11 +- 4 files changed, 307 insertions(+), 243 deletions(-) delete mode 100644 app/javascript/mastodon/components/column_header.jsx create mode 100644 app/javascript/mastodon/components/column_header.tsx diff --git a/app/javascript/mastodon/components/column_header.jsx b/app/javascript/mastodon/components/column_header.jsx deleted file mode 100644 index 42183f336..000000000 --- a/app/javascript/mastodon/components/column_header.jsx +++ /dev/null @@ -1,233 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent, useCallback } from 'react'; - -import { FormattedMessage, injectIntl, defineMessages, useIntl } from 'react-intl'; - -import classNames from 'classnames'; -import { withRouter } from 'react-router-dom'; - -import AddIcon from '@/material-icons/400-24px/add.svg?react'; -import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; -import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; -import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; -import CloseIcon from '@/material-icons/400-24px/close.svg?react'; -import SettingsIcon from '@/material-icons/400-24px/settings.svg?react'; -import { Icon } from 'mastodon/components/icon'; -import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context'; -import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; -import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - - -import { useAppHistory } from './router'; - -const messages = defineMessages({ - show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, - hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, - moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' }, - moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' }, - back: { id: 'column_back_button.label', defaultMessage: 'Back' }, -}); - -const BackButton = ({ onlyIcon }) => { - const history = useAppHistory(); - const intl = useIntl(); - - const handleBackClick = useCallback(() => { - if (history.location?.state?.fromMastodon) { - history.goBack(); - } else { - history.push('/'); - } - }, [history]); - - return ( - <button onClick={handleBackClick} className={classNames('column-header__back-button', { 'compact': onlyIcon })} aria-label={intl.formatMessage(messages.back)}> - <Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' /> - {!onlyIcon && <FormattedMessage id='column_back_button.label' defaultMessage='Back' />} - </button> - ); -}; - -BackButton.propTypes = { - onlyIcon: PropTypes.bool, -}; - -class ColumnHeader extends PureComponent { - static propTypes = { - identity: identityContextPropShape, - intl: PropTypes.object.isRequired, - title: PropTypes.node, - icon: PropTypes.string, - iconComponent: PropTypes.func, - active: PropTypes.bool, - multiColumn: PropTypes.bool, - extraButton: PropTypes.node, - showBackButton: PropTypes.bool, - children: PropTypes.node, - pinned: PropTypes.bool, - placeholder: PropTypes.bool, - onPin: PropTypes.func, - onMove: PropTypes.func, - onClick: PropTypes.func, - appendContent: PropTypes.node, - collapseIssues: PropTypes.bool, - ...WithRouterPropTypes, - }; - - state = { - collapsed: true, - animating: false, - }; - - handleToggleClick = (e) => { - e.stopPropagation(); - this.setState({ collapsed: !this.state.collapsed, animating: true }); - }; - - handleTitleClick = () => { - this.props.onClick?.(); - }; - - handleMoveLeft = () => { - this.props.onMove(-1); - }; - - handleMoveRight = () => { - this.props.onMove(1); - }; - - handleTransitionEnd = () => { - this.setState({ animating: false }); - }; - - handlePin = () => { - if (!this.props.pinned) { - this.props.history.replace('/'); - } - - this.props.onPin(); - }; - - render () { - const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props; - const { collapsed, animating } = this.state; - - const wrapperClassName = classNames('column-header__wrapper', { - 'active': active, - }); - - const buttonClassName = classNames('column-header', { - 'active': active, - }); - - const collapsibleClassName = classNames('column-header__collapsible', { - 'collapsed': collapsed, - 'animating': animating, - }); - - const collapsibleButtonClassName = classNames('column-header__button', { - 'active': !collapsed, - }); - - let extraContent, pinButton, moveButtons, backButton, collapseButton; - - if (children) { - extraContent = ( - <div key='extra-content' className='column-header__collapsible__extra'> - {children} - </div> - ); - } - - if (multiColumn && pinned) { - pinButton = <button className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' icon={CloseIcon} /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>; - - moveButtons = ( - <div className='column-header__setting-arrows'> - <button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='icon-button column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' icon={ChevronLeftIcon} /></button> - <button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='icon-button column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' icon={ChevronRightIcon} /></button> - </div> - ); - } else if (multiColumn && this.props.onPin) { - pinButton = <button className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' icon={AddIcon} /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; - } - - if (history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) { - backButton = <BackButton onlyIcon={!!title} />; - } - - const collapsedContent = [ - extraContent, - ]; - - if (multiColumn) { - collapsedContent.push( - <div key='buttons' className='column-header__advanced-buttons'> - {pinButton} - {moveButtons} - </div> - ); - } - - if (this.props.identity.signedIn && (children || (multiColumn && this.props.onPin))) { - collapseButton = ( - <button - className={collapsibleButtonClassName} - title={formatMessage(collapsed ? messages.show : messages.hide)} - aria-label={formatMessage(collapsed ? messages.show : messages.hide)} - onClick={this.handleToggleClick} - > - <i className='icon-with-badge'> - <Icon id='sliders' icon={SettingsIcon} /> - {collapseIssues && <i className='icon-with-badge__issue-badge' />} - </i> - </button> - ); - } - - const hasTitle = (icon || iconComponent) && title; - - const component = ( - <div className={wrapperClassName}> - <h1 className={buttonClassName}> - {hasTitle && ( - <> - {backButton} - - <button onClick={this.handleTitleClick} className='column-header__title'> - {!backButton && <Icon id={icon} icon={iconComponent} className='column-header__icon' />} - {title} - </button> - </> - )} - - {!hasTitle && backButton} - - <div className='column-header__buttons'> - {extraButton} - {collapseButton} - </div> - </h1> - - <div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}> - <div className='column-header__collapsible-inner'> - {(!collapsed || animating) && collapsedContent} - </div> - </div> - - {appendContent} - </div> - ); - - if (placeholder) { - return component; - } else { - return (<ButtonInTabsBar> - {component} - </ButtonInTabsBar>); - } - } - -} - -export default injectIntl(withIdentity(withRouter(ColumnHeader))); diff --git a/app/javascript/mastodon/components/column_header.tsx b/app/javascript/mastodon/components/column_header.tsx new file mode 100644 index 000000000..ec946cab3 --- /dev/null +++ b/app/javascript/mastodon/components/column_header.tsx @@ -0,0 +1,301 @@ +import { useCallback, useState } from 'react'; + +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings.svg?react'; +import type { IconProp } from 'mastodon/components/icon'; +import { Icon } from 'mastodon/components/icon'; +import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context'; +import { useIdentity } from 'mastodon/identity_context'; + +import { useAppHistory } from './router'; + +const messages = defineMessages({ + show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, + hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, + moveLeft: { + id: 'column_header.moveLeft_settings', + defaultMessage: 'Move column to the left', + }, + moveRight: { + id: 'column_header.moveRight_settings', + defaultMessage: 'Move column to the right', + }, + back: { id: 'column_back_button.label', defaultMessage: 'Back' }, +}); + +const BackButton: React.FC<{ + onlyIcon: boolean; +}> = ({ onlyIcon }) => { + const history = useAppHistory(); + const intl = useIntl(); + + const handleBackClick = useCallback(() => { + if (history.location.state?.fromMastodon) { + history.goBack(); + } else { + history.push('/'); + } + }, [history]); + + return ( + <button + onClick={handleBackClick} + className={classNames('column-header__back-button', { + compact: onlyIcon, + })} + aria-label={intl.formatMessage(messages.back)} + > + <Icon + id='chevron-left' + icon={ArrowBackIcon} + className='column-back-button__icon' + /> + {!onlyIcon && ( + <FormattedMessage id='column_back_button.label' defaultMessage='Back' /> + )} + </button> + ); +}; + +export interface Props { + title?: string; + icon?: string; + iconComponent?: IconProp; + active?: boolean; + children?: React.ReactNode; + pinned?: boolean; + multiColumn?: boolean; + extraButton?: React.ReactNode; + showBackButton?: boolean; + placeholder?: boolean; + appendContent?: React.ReactNode; + collapseIssues?: boolean; + onClick?: () => void; + onMove?: (arg0: number) => void; + onPin?: () => void; +} + +export const ColumnHeader: React.FC<Props> = ({ + title, + icon, + iconComponent, + active, + children, + pinned, + multiColumn, + extraButton, + showBackButton, + placeholder, + appendContent, + collapseIssues, + onClick, + onMove, + onPin, +}) => { + const intl = useIntl(); + const { signedIn } = useIdentity(); + const history = useAppHistory(); + const [collapsed, setCollapsed] = useState(true); + const [animating, setAnimating] = useState(false); + + const handleToggleClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + setCollapsed((value) => !value); + setAnimating(true); + }, + [setCollapsed, setAnimating], + ); + + const handleTitleClick = useCallback(() => { + onClick?.(); + }, [onClick]); + + const handleMoveLeft = useCallback(() => { + onMove?.(-1); + }, [onMove]); + + const handleMoveRight = useCallback(() => { + onMove?.(1); + }, [onMove]); + + const handleTransitionEnd = useCallback(() => { + setAnimating(false); + }, [setAnimating]); + + const handlePin = useCallback(() => { + if (!pinned) { + history.replace('/'); + } + + onPin?.(); + }, [history, pinned, onPin]); + + const wrapperClassName = classNames('column-header__wrapper', { + active, + }); + + const buttonClassName = classNames('column-header', { + active, + }); + + const collapsibleClassName = classNames('column-header__collapsible', { + collapsed, + animating, + }); + + const collapsibleButtonClassName = classNames('column-header__button', { + active: !collapsed, + }); + + let extraContent, pinButton, moveButtons, backButton, collapseButton; + + if (children) { + extraContent = ( + <div key='extra-content' className='column-header__collapsible__extra'> + {children} + </div> + ); + } + + if (multiColumn && pinned) { + pinButton = ( + <button + className='text-btn column-header__setting-btn' + onClick={handlePin} + > + <Icon id='times' icon={CloseIcon} />{' '} + <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /> + </button> + ); + + moveButtons = ( + <div className='column-header__setting-arrows'> + <button + title={intl.formatMessage(messages.moveLeft)} + aria-label={intl.formatMessage(messages.moveLeft)} + className='icon-button column-header__setting-btn' + onClick={handleMoveLeft} + > + <Icon id='chevron-left' icon={ChevronLeftIcon} /> + </button> + <button + title={intl.formatMessage(messages.moveRight)} + aria-label={intl.formatMessage(messages.moveRight)} + className='icon-button column-header__setting-btn' + onClick={handleMoveRight} + > + <Icon id='chevron-right' icon={ChevronRightIcon} /> + </button> + </div> + ); + } else if (multiColumn && onPin) { + pinButton = ( + <button + className='text-btn column-header__setting-btn' + onClick={handlePin} + > + <Icon id='plus' icon={AddIcon} />{' '} + <FormattedMessage id='column_header.pin' defaultMessage='Pin' /> + </button> + ); + } + + if ( + !pinned && + ((multiColumn && history.location.state?.fromMastodon) || showBackButton) + ) { + backButton = <BackButton onlyIcon={!!title} />; + } + + const collapsedContent = [extraContent]; + + if (multiColumn) { + collapsedContent.push( + <div key='buttons' className='column-header__advanced-buttons'> + {pinButton} + {moveButtons} + </div>, + ); + } + + if (signedIn && (children || (multiColumn && onPin))) { + collapseButton = ( + <button + className={collapsibleButtonClassName} + title={intl.formatMessage(collapsed ? messages.show : messages.hide)} + aria-label={intl.formatMessage( + collapsed ? messages.show : messages.hide, + )} + onClick={handleToggleClick} + > + <i className='icon-with-badge'> + <Icon id='sliders' icon={SettingsIcon} /> + {collapseIssues && <i className='icon-with-badge__issue-badge' />} + </i> + </button> + ); + } + + const hasIcon = icon && iconComponent; + const hasTitle = hasIcon && title; + + const component = ( + <div className={wrapperClassName}> + <h1 className={buttonClassName}> + {hasTitle && ( + <> + {backButton} + + <button onClick={handleTitleClick} className='column-header__title'> + {!backButton && ( + <Icon + id={icon} + icon={iconComponent} + className='column-header__icon' + /> + )} + {title} + </button> + </> + )} + + {!hasTitle && backButton} + + <div className='column-header__buttons'> + {extraButton} + {collapseButton} + </div> + </h1> + + <div + className={collapsibleClassName} + tabIndex={collapsed ? -1 : undefined} + onTransitionEnd={handleTransitionEnd} + > + <div className='column-header__collapsible-inner'> + {(!collapsed || animating) && collapsedContent} + </div> + </div> + + {appendContent} + </div> + ); + + if (placeholder) { + return component; + } else { + return <ButtonInTabsBar>{component}</ButtonInTabsBar>; + } +}; + +// eslint-disable-next-line import/no-default-export +export default ColumnHeader; diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx index 482c6858f..51d283a48 100644 --- a/app/javascript/mastodon/features/directory/index.tsx +++ b/app/javascript/mastodon/features/directory/index.tsx @@ -16,7 +16,7 @@ import { } from 'mastodon/actions/columns'; import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory'; import Column from 'mastodon/components/column'; -import ColumnHeader from 'mastodon/components/column_header'; +import { ColumnHeader } from 'mastodon/components/column_header'; import { LoadMore } from 'mastodon/components/load_more'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { RadioButton } from 'mastodon/components/radio_button'; @@ -86,7 +86,7 @@ export const Directory: React.FC<{ }, [dispatch, order, local]); const handleMove = useCallback( - (dir: string) => { + (dir: number) => { dispatch(moveColumn(columnId, dir)); }, [dispatch, columnId], @@ -185,7 +185,6 @@ export const Directory: React.FC<{ label={intl.formatMessage(messages.title)} > <ColumnHeader - // @ts-expect-error ColumnHeader is not properly typed yet icon='address-book-o' iconComponent={PeopleIcon} title={intl.formatMessage(messages.title)} diff --git a/app/javascript/mastodon/features/ui/components/column_loading.tsx b/app/javascript/mastodon/features/ui/components/column_loading.tsx index 42174838c..d9563dda7 100644 --- a/app/javascript/mastodon/features/ui/components/column_loading.tsx +++ b/app/javascript/mastodon/features/ui/components/column_loading.tsx @@ -1,11 +1,8 @@ -import Column from '../../../components/column'; -import ColumnHeader from '../../../components/column_header'; +import Column from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import type { Props as ColumnHeaderProps } from 'mastodon/components/column_header'; -interface Props { - multiColumn?: boolean; -} - -export const ColumnLoading: React.FC<Props> = (otherProps) => ( +export const ColumnLoading: React.FC<ColumnHeaderProps> = (otherProps) => ( <Column> <ColumnHeader {...otherProps} /> <div className='scrollable' /> From ad53b0ab65cd9eba6d3078a3980c467428e79371 Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Thu, 27 Jun 2024 03:16:59 -0400 Subject: [PATCH 28/84] Rely on built-in ruby private IP detection (#30848) --- Gemfile | 2 -- Gemfile.lock | 2 -- app/lib/private_address_check.rb | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 app/lib/private_address_check.rb diff --git a/Gemfile b/Gemfile index f2d7d098d..aa3ca8f79 100644 --- a/Gemfile +++ b/Gemfile @@ -100,8 +100,6 @@ gem 'json-ld' gem 'json-ld-preloaded', '~> 3.2' gem 'rdf-normalize', '~> 0.5' -gem 'private_address_check', '~> 0.5' - gem 'opentelemetry-api', '~> 1.2.5' group :opentelemetry do diff --git a/Gemfile.lock b/Gemfile.lock index e8b54ed56..3b1b4b112 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -595,7 +595,6 @@ GEM actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) - private_address_check (0.5.0) propshaft (0.9.0) actionpack (>= 7.0.0) activesupport (>= 7.0.0) @@ -994,7 +993,6 @@ DEPENDENCIES pg (~> 1.5) pghero premailer-rails - private_address_check (~> 0.5) propshaft public_suffix (~> 6.0) puma (~> 6.3) diff --git a/app/lib/private_address_check.rb b/app/lib/private_address_check.rb new file mode 100644 index 000000000..d00b16e66 --- /dev/null +++ b/app/lib/private_address_check.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module PrivateAddressCheck + module_function + + CIDR_LIST = [ + IPAddr.new('0.0.0.0/8'), # Current network (only valid as source address) + IPAddr.new('100.64.0.0/10'), # Shared Address Space + IPAddr.new('172.16.0.0/12'), # Private network + IPAddr.new('192.0.0.0/24'), # IETF Protocol Assignments + IPAddr.new('192.0.2.0/24'), # TEST-NET-1, documentation and examples + IPAddr.new('192.88.99.0/24'), # IPv6 to IPv4 relay (includes 2002::/16) + IPAddr.new('198.18.0.0/15'), # Network benchmark tests + IPAddr.new('198.51.100.0/24'), # TEST-NET-2, documentation and examples + IPAddr.new('203.0.113.0/24'), # TEST-NET-3, documentation and examples + IPAddr.new('224.0.0.0/4'), # IP multicast (former Class D network) + IPAddr.new('240.0.0.0/4'), # Reserved (former Class E network) + IPAddr.new('255.255.255.255'), # Broadcast + IPAddr.new('64:ff9b::/96'), # IPv4/IPv6 translation (RFC 6052) + IPAddr.new('100::/64'), # Discard prefix (RFC 6666) + IPAddr.new('2001::/32'), # Teredo tunneling + IPAddr.new('2001:10::/28'), # Deprecated (previously ORCHID) + IPAddr.new('2001:20::/28'), # ORCHIDv2 + IPAddr.new('2001:db8::/32'), # Addresses used in documentation and example source code + IPAddr.new('2002::/16'), # 6to4 + IPAddr.new('fc00::/7'), # Unique local address + IPAddr.new('ff00::/8'), # Multicast + ].freeze + + def private_address?(address) + address.private? || address.loopback? || address.link_local? || CIDR_LIST.any? { |cidr| cidr.include?(address) } + end +end From f6390c3326b016e1a52057573839a5608ba317ea Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Thu, 27 Jun 2024 03:42:57 -0400 Subject: [PATCH 29/84] Use flatware to parallelize CI specs (#30284) --- .github/workflows/test-ruby.yml | 8 +++++--- Gemfile | 3 +++ Gemfile.lock | 10 ++++++++++ bin/flatware | 27 +++++++++++++++++++++++++++ spec/flatware_helper.rb | 12 ++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) create mode 100755 bin/flatware create mode 100644 spec/flatware_helper.rb diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index dd71fd253..ef898968d 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -132,15 +132,17 @@ jobs: additional-system-dependencies: ffmpeg libpam-dev - name: Load database schema - run: './bin/rails db:create db:schema:load db:seed' + run: | + bin/rails db:setup + bin/flatware fan bin/rails db:test:prepare - - run: bin/rspec + - run: bin/flatware rspec -r ./spec/flatware_helper.rb - name: Upload coverage reports to Codecov if: matrix.ruby-version == '.ruby-version' uses: codecov/codecov-action@v4 with: - files: coverage/lcov/mastodon.lcov + files: coverage/lcov/*.lcov env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/Gemfile b/Gemfile index aa3ca8f79..be3f9e6f9 100644 --- a/Gemfile +++ b/Gemfile @@ -121,6 +121,9 @@ group :opentelemetry do end group :test do + # Enable usage of all available CPUs/cores during spec runs + gem 'flatware-rspec' + # Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab gem 'rspec-github', '~> 2.4', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 3b1b4b112..dd112d018 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -264,6 +264,11 @@ GEM ffi-compiler (1.3.2) ffi (>= 1.15.5) rake + flatware (2.3.2) + thor (< 2.0) + flatware-rspec (2.3.2) + flatware (= 2.3.2) + rspec (>= 3.6) fog-core (2.4.0) builder excon (~> 0.71) @@ -700,6 +705,10 @@ GEM chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) rspec-core (3.13.0) rspec-support (~> 3.13.0) rspec-expectations (3.13.1) @@ -932,6 +941,7 @@ DEPENDENCIES faker (~> 3.2) fast_blank (~> 1.0) fastimage + flatware-rspec fog-core (<= 2.4.0) fog-openstack (~> 1.0) fuubar (~> 2.5) diff --git a/bin/flatware b/bin/flatware new file mode 100755 index 000000000..337ce9277 --- /dev/null +++ b/bin/flatware @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'flatware' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("flatware", "flatware") diff --git a/spec/flatware_helper.rb b/spec/flatware_helper.rb new file mode 100644 index 000000000..57a7c1f56 --- /dev/null +++ b/spec/flatware_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +if defined?(Flatware) + Flatware.configure do |config| + config.after_fork do |test_env_number| + unless ENV.fetch('DISABLE_SIMPLECOV', nil) == 'true' + require 'simplecov' + SimpleCov.at_fork.call(test_env_number) # Combines parallel coverage results + end + end + end +end From 6d1c1fd684d0f20691cba552b209b15d0107dfe4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:00:04 +0200 Subject: [PATCH 30/84] New Crowdin Translations (automated) (#30851) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/af.json | 1 - app/javascript/mastodon/locales/an.json | 3 --- app/javascript/mastodon/locales/ar.json | 3 --- app/javascript/mastodon/locales/ast.json | 2 -- app/javascript/mastodon/locales/be.json | 3 --- app/javascript/mastodon/locales/bg.json | 3 --- app/javascript/mastodon/locales/bn.json | 3 --- app/javascript/mastodon/locales/br.json | 3 --- app/javascript/mastodon/locales/ca.json | 6 +++--- app/javascript/mastodon/locales/ckb.json | 3 --- app/javascript/mastodon/locales/co.json | 3 --- app/javascript/mastodon/locales/cs.json | 3 --- app/javascript/mastodon/locales/cy.json | 3 --- app/javascript/mastodon/locales/da.json | 4 +--- app/javascript/mastodon/locales/el.json | 3 --- app/javascript/mastodon/locales/en-GB.json | 3 --- app/javascript/mastodon/locales/eo.json | 3 --- app/javascript/mastodon/locales/es-AR.json | 2 +- app/javascript/mastodon/locales/es-MX.json | 6 +++--- app/javascript/mastodon/locales/es.json | 6 +++--- app/javascript/mastodon/locales/et.json | 3 --- app/javascript/mastodon/locales/eu.json | 3 --- app/javascript/mastodon/locales/fa.json | 3 --- app/javascript/mastodon/locales/fi.json | 2 +- app/javascript/mastodon/locales/fo.json | 3 --- app/javascript/mastodon/locales/fr-CA.json | 3 --- app/javascript/mastodon/locales/fr.json | 3 --- app/javascript/mastodon/locales/fy.json | 3 --- app/javascript/mastodon/locales/ga.json | 3 --- app/javascript/mastodon/locales/gd.json | 3 --- app/javascript/mastodon/locales/gl.json | 3 --- app/javascript/mastodon/locales/he.json | 3 --- app/javascript/mastodon/locales/hi.json | 3 --- app/javascript/mastodon/locales/hr.json | 3 --- app/javascript/mastodon/locales/hu.json | 3 --- app/javascript/mastodon/locales/hy.json | 3 --- app/javascript/mastodon/locales/ia.json | 1 + app/javascript/mastodon/locales/id.json | 3 --- app/javascript/mastodon/locales/ie.json | 3 --- app/javascript/mastodon/locales/io.json | 3 --- app/javascript/mastodon/locales/is.json | 2 +- app/javascript/mastodon/locales/it.json | 6 +++--- app/javascript/mastodon/locales/ja.json | 3 --- app/javascript/mastodon/locales/ka.json | 1 - app/javascript/mastodon/locales/kab.json | 3 --- app/javascript/mastodon/locales/kk.json | 3 --- app/javascript/mastodon/locales/kn.json | 1 - app/javascript/mastodon/locales/ko.json | 3 --- app/javascript/mastodon/locales/ku.json | 3 --- app/javascript/mastodon/locales/kw.json | 3 --- app/javascript/mastodon/locales/la.json | 3 --- app/javascript/mastodon/locales/lad.json | 3 --- app/javascript/mastodon/locales/lt.json | 3 --- app/javascript/mastodon/locales/lv.json | 3 --- app/javascript/mastodon/locales/mk.json | 1 - app/javascript/mastodon/locales/ml.json | 3 --- app/javascript/mastodon/locales/mr.json | 3 --- app/javascript/mastodon/locales/ms.json | 3 --- app/javascript/mastodon/locales/my.json | 3 --- app/javascript/mastodon/locales/ne.json | 1 - app/javascript/mastodon/locales/nn.json | 3 --- app/javascript/mastodon/locales/no.json | 3 --- app/javascript/mastodon/locales/oc.json | 3 --- app/javascript/mastodon/locales/pa.json | 3 --- app/javascript/mastodon/locales/pl.json | 3 --- app/javascript/mastodon/locales/pt-BR.json | 3 --- app/javascript/mastodon/locales/pt-PT.json | 3 --- app/javascript/mastodon/locales/ro.json | 3 --- app/javascript/mastodon/locales/ru.json | 3 --- app/javascript/mastodon/locales/sa.json | 3 --- app/javascript/mastodon/locales/sc.json | 3 --- app/javascript/mastodon/locales/sco.json | 3 --- app/javascript/mastodon/locales/si.json | 3 --- app/javascript/mastodon/locales/sk.json | 3 --- app/javascript/mastodon/locales/sl.json | 6 +++--- app/javascript/mastodon/locales/sq.json | 3 --- app/javascript/mastodon/locales/sr-Latn.json | 6 +++--- app/javascript/mastodon/locales/sr.json | 6 +++--- app/javascript/mastodon/locales/sv.json | 3 --- app/javascript/mastodon/locales/szl.json | 1 - app/javascript/mastodon/locales/ta.json | 3 --- app/javascript/mastodon/locales/tai.json | 1 - app/javascript/mastodon/locales/te.json | 1 - app/javascript/mastodon/locales/th.json | 3 --- app/javascript/mastodon/locales/tr.json | 6 +++--- app/javascript/mastodon/locales/tt.json | 3 --- app/javascript/mastodon/locales/ug.json | 1 - app/javascript/mastodon/locales/uk.json | 3 --- app/javascript/mastodon/locales/ur.json | 3 --- app/javascript/mastodon/locales/uz.json | 3 --- app/javascript/mastodon/locales/vi.json | 3 --- app/javascript/mastodon/locales/zgh.json | 1 - app/javascript/mastodon/locales/zh-CN.json | 7 ++++--- app/javascript/mastodon/locales/zh-HK.json | 3 --- app/javascript/mastodon/locales/zh-TW.json | 6 +++--- config/locales/fi.yml | 2 +- config/locales/sr-Latn.yml | 2 +- config/locales/sr.yml | 2 +- config/locales/zh-TW.yml | 2 +- 99 files changed, 40 insertions(+), 259 deletions(-) diff --git a/app/javascript/mastodon/locales/af.json b/app/javascript/mastodon/locales/af.json index e4f7f12b0..77e15eb2c 100644 --- a/app/javascript/mastodon/locales/af.json +++ b/app/javascript/mastodon/locales/af.json @@ -50,7 +50,6 @@ "account.requested_follow": "{name} het versoek om jou te volg", "account.share": "Deel @{name} se profiel", "account.show_reblogs": "Wys aangestuurde plasings van @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Plaas} other {{counter} Plasings}}", "account.unblock": "Deblokkeer @{name}", "account.unblock_domain": "Deblokkeer domein {domain}", "account.unblock_short": "Deblokkeer", diff --git a/app/javascript/mastodon/locales/an.json b/app/javascript/mastodon/locales/an.json index af5f8426d..752b6c356 100644 --- a/app/javascript/mastodon/locales/an.json +++ b/app/javascript/mastodon/locales/an.json @@ -31,9 +31,7 @@ "account.follow": "Seguir", "account.followers": "Seguidores", "account.followers.empty": "Encara no sigue dengún a este usuario.", - "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}", "account.following": "Seguindo", - "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Seguindo}}", "account.follows.empty": "Este usuario encara no sigue a dengún.", "account.go_to_profile": "Ir ta lo perfil", "account.hide_reblogs": "Amagar retutz de @{name}", @@ -54,7 +52,6 @@ "account.requested_follow": "{name} ha demandau seguir-te", "account.share": "Compartir lo perfil de @{name}", "account.show_reblogs": "Amostrar retutz de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Publicación} other {{counter} Publicaciones}}", "account.unblock": "Desblocar a @{name}", "account.unblock_domain": "Amostrar a {domain}", "account.unblock_short": "Desblocar", diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index b5ce0ae86..a9e1bdb27 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -35,9 +35,7 @@ "account.follow_back": "رد المتابعة", "account.followers": "مُتابِعون", "account.followers.empty": "لا أحدَ يُتابع هذا المُستخدم إلى حد الآن.", - "account.followers_counter": "{count, plural, zero{لا مُتابع} one {مُتابعٌ واحِد} two {مُتابعانِ اِثنان} few {{counter} مُتابِعين} many {{counter} مُتابِعًا} other {{counter} مُتابع}}", "account.following": "الاشتراكات", - "account.following_counter": "{count, plural, zero{لا يُتابِع أحدًا} one {يُتابِعُ واحد} two{يُتابِعُ اِثنان} few{يُتابِعُ {counter}} many{يُتابِعُ {counter}} other {يُتابِعُ {counter}}}", "account.follows.empty": "لا يُتابع هذا المُستخدمُ أيَّ أحدٍ حتى الآن.", "account.go_to_profile": "اذهب إلى الملف الشخصي", "account.hide_reblogs": "إخفاء المعاد نشرها مِن @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "لقد طلب {name} متابعتك", "account.share": "شارِك الملف التعريفي لـ @{name}", "account.show_reblogs": "اعرض إعادات نشر @{name}", - "account.statuses_counter": "{count, plural, zero {لَا منشورات} one {منشور واحد} two {منشوران إثنان} few {{counter} منشورات} many {{counter} منشورًا} other {{counter} منشور}}", "account.unblock": "إلغاء الحَظر عن @{name}", "account.unblock_domain": "إلغاء الحَظر عن النِّطاق {domain}", "account.unblock_short": "ألغ الحجب", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index 80e0aa6cb..3f32a8bf1 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -32,7 +32,6 @@ "account.followers": "Siguidores", "account.followers.empty": "Naide sigue a esti perfil.", "account.following": "Siguiendo", - "account.following_counter": "{count, plural,one {Sigue a {counter}} other {Sigue a {counter}}}", "account.follows.empty": "Esti perfil nun sigue a naide.", "account.go_to_profile": "Dir al perfil", "account.hide_reblogs": "Anubrir los artículos compartíos de @{name}", @@ -49,7 +48,6 @@ "account.report": "Informar de: @{name}", "account.requested_follow": "{name} solicitó siguite", "account.show_reblogs": "Amosar los artículos compartíos de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} artículu} other {{counter} artículos}}", "account.unblock": "Desbloquiar a @{name}", "account.unblock_domain": "Desbloquiar el dominiu «{domain}»", "account.unblock_short": "Desbloquiar", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 03164c429..643725270 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -35,9 +35,7 @@ "account.follow_back": "Падпісацца ў адказ", "account.followers": "Падпісчыкі", "account.followers.empty": "Ніхто пакуль не падпісаны на гэтага карыстальніка.", - "account.followers_counter": "{count, plural, one {{counter} падпісчык} few {{counter} падпісчыкі} many {{counter} падпісчыкаў} other {{counter} падпісчыка}}", "account.following": "Падпіскі", - "account.following_counter": "{count, plural, one {{counter} падпіска} few {{counter} падпіскі} many {{counter} падпісак} other {{counter} падпіскі}}", "account.follows.empty": "Карыстальнік ні на каго не падпісаны.", "account.go_to_profile": "Перайсці да профілю", "account.hide_reblogs": "Схаваць пашырэнні ад @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} адправіў запыт на падпіску", "account.share": "Абагуліць профіль @{name}", "account.show_reblogs": "Паказаць падштурхоўванні ад @{name}", - "account.statuses_counter": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}", "account.unblock": "Разблакіраваць @{name}", "account.unblock_domain": "Разблакіраваць дамен {domain}", "account.unblock_short": "Разблакіраваць", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 98e84c45d..323890ba2 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -35,9 +35,7 @@ "account.follow_back": "Последване взаимно", "account.followers": "Последователи", "account.followers.empty": "Още никой не следва потребителя.", - "account.followers_counter": "{count, plural, one {{counter} последовател} other {{counter} последователи}}", "account.following": "Последвано", - "account.following_counter": "{count, plural, one {{counter} последван} other {{counter} последвани}}", "account.follows.empty": "Потребителят още никого не следва.", "account.go_to_profile": "Към профила", "account.hide_reblogs": "Скриване на подсилвания от @{name}", @@ -63,7 +61,6 @@ "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": "Отблокиране", diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json index 4c4138bcf..a203c43f0 100644 --- a/app/javascript/mastodon/locales/bn.json +++ b/app/javascript/mastodon/locales/bn.json @@ -33,9 +33,7 @@ "account.follow": "অনুসরণ", "account.followers": "অনুসরণকারী", "account.followers.empty": "এই ব্যক্তিকে এখনো কেউ অনুসরণ করে না.", - "account.followers_counter": "{count, plural,one {{counter} জন অনুসরণকারী } other {{counter} জন অনুসরণকারী}}", "account.following": "অনুসরণ করা হচ্ছে", - "account.following_counter": "{count, plural,one {{counter} জনকে অনুসরণ} other {{counter} জনকে অনুসরণ}}", "account.follows.empty": "এই সদস্য কাউকে এখনো ফলো করেন না.", "account.go_to_profile": "প্রোফাইলে যান", "account.hide_reblogs": "@{name}'র সমর্থনগুলি লুকিয়ে ফেলুন", @@ -60,7 +58,6 @@ "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": "আনব্লক করুন", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index 7cd49ba59..c919d2e9d 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -35,9 +35,7 @@ "account.follow_back": "Heuliañ d'ho tro", "account.followers": "Tud koumanantet", "account.followers.empty": "Den na heul an implijer·ez-mañ c'hoazh.", - "account.followers_counter": "{count, plural, other{{counter} Heulier·ez}}", "account.following": "Koumanantoù", - "account.following_counter": "{count, plural, one{{counter} C'houmanant} two{{counter} Goumanant} other {{counter} a Goumanant}}", "account.follows.empty": "An implijer·ez-mañ na heul den ebet.", "account.go_to_profile": "Gwelet ar profil", "account.hide_reblogs": "Kuzh skignadennoù gant @{name}", @@ -62,7 +60,6 @@ "account.requested_follow": "Gant {name} eo bet goulennet ho heuliañ", "account.share": "Skignañ profil @{name}", "account.show_reblogs": "Diskouez skignadennoù @{name}", - "account.statuses_counter": "{count, plural, one {{counter} C'hannad} two {{counter} Gannad} other {{counter} a Gannadoù}}", "account.unblock": "Diverzañ @{name}", "account.unblock_domain": "Diverzañ an domani {domain}", "account.unblock_short": "Distankañ", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 88dd34aff..3123e29d8 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -35,9 +35,9 @@ "account.follow_back": "Segueix tu també", "account.followers": "Seguidors", "account.followers.empty": "A aquest usuari encara no el segueix ningú.", - "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} Seguidors}}", + "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidors}}", "account.following": "Seguint", - "account.following_counter": "{count, plural, other {{counter} Seguint-ne}}", + "account.following_counter": "{count, plural, other {Seguint-ne {counter}}}", "account.follows.empty": "Aquest usuari encara no segueix ningú.", "account.go_to_profile": "Vés al perfil", "account.hide_reblogs": "Amaga els impulsos de @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ha demanat de seguir-te", "account.share": "Comparteix el perfil de @{name}", "account.show_reblogs": "Mostra els impulsos de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Tut} other {{counter} Tuts}}", + "account.statuses_counter": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}", "account.unblock": "Desbloca @{name}", "account.unblock_domain": "Desbloca el domini {domain}", "account.unblock_short": "Desbloca", diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json index c212b53a8..3ebf9391d 100644 --- a/app/javascript/mastodon/locales/ckb.json +++ b/app/javascript/mastodon/locales/ckb.json @@ -35,9 +35,7 @@ "account.follow_back": "فۆڵۆو بکەنەوە", "account.followers": "شوێنکەوتووان", "account.followers.empty": "کەسێک شوێن ئەم بەکارهێنەرە نەکەوتووە", - "account.followers_counter": "{count, plural, one {{counter} شوێنکەوتوو} other {{counter} شوێنکەوتوو}}", "account.following": "بەدوادا", - "account.following_counter": "{count, plural, one {{counter} شوێنکەوتوو} other {{counter} شوێنکەوتوو}}", "account.follows.empty": "ئەم بەکارهێنەرە تا ئێستا شوێن کەس نەکەوتووە.", "account.go_to_profile": "بڕۆ بۆ پڕۆفایلی", "account.hide_reblogs": "داشاردنی بووستەکان لە @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} داوای کردووە شوێنت بکەوێت", "account.share": "پرۆفایلی @{name} هاوبەش بکە", "account.show_reblogs": "پیشاندانی بەرزکردنەوەکان لە @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}", "account.unblock": "@{name} لاببە", "account.unblock_domain": "کردنەوەی دۆمەینی {domain}", "account.unblock_short": "لابردنی بەربەست", diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json index be4cce269..78f8e6fd7 100644 --- a/app/javascript/mastodon/locales/co.json +++ b/app/javascript/mastodon/locales/co.json @@ -16,8 +16,6 @@ "account.follow": "Siguità", "account.followers": "Abbunati", "account.followers.empty": "Nisunu hè abbunatu à st'utilizatore.", - "account.followers_counter": "{count, plural, one {{counter} Abbunatu} other {{counter} Abbunati}}", - "account.following_counter": "{count, plural, one {{counter} Abbunamentu} other {{counter} Abbunamenti}}", "account.follows.empty": "St'utilizatore ùn seguita nisunu.", "account.hide_reblogs": "Piattà spartere da @{name}", "account.link_verified_on": "A prupietà di stu ligame hè stata verificata u {date}", @@ -32,7 +30,6 @@ "account.requested": "In attesa d'apprubazione. Cliccate per annullà a dumanda", "account.share": "Sparte u prufile di @{name}", "account.show_reblogs": "Vede spartere da @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Statutu} other {{counter} Statuti}}", "account.unblock": "Sbluccà @{name}", "account.unblock_domain": "Ùn piattà più {domain}", "account.unendorse": "Ùn fà figurà nant'à u prufilu", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index d8d83ae5f..66aa1fe0a 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -35,9 +35,7 @@ "account.follow_back": "Také sledovat", "account.followers": "Sledující", "account.followers.empty": "Tohoto uživatele zatím nikdo nesleduje.", - "account.followers_counter": "{count, plural, one {{counter} Sledující} few {{counter} Sledující} many {{counter} Sledujících} other {{counter} Sledujících}}", "account.following": "Sledujete", - "account.following_counter": "{count, plural, one {{counter} Sledovaný} few {{counter} Sledovaní} many {{counter} Sledovaných} other {{counter} Sledovaných}}", "account.follows.empty": "Tento uživatel zatím nikoho nesleduje.", "account.go_to_profile": "Přejít na profil", "account.hide_reblogs": "Skrýt boosty od @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} tě požádal o sledování", "account.share": "Sdílet profil @{name}", "account.show_reblogs": "Zobrazit boosty od @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Příspěvek} few {{counter} Příspěvky} many {{counter} Příspěvků} other {{counter} Příspěvků}}", "account.unblock": "Odblokovat @{name}", "account.unblock_domain": "Odblokovat doménu {domain}", "account.unblock_short": "Odblokovat", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 96476b143..1c7e61832 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -35,9 +35,7 @@ "account.follow_back": "Dilyn yn ôl", "account.followers": "Dilynwyr", "account.followers.empty": "Does neb yn dilyn y defnyddiwr hwn eto.", - "account.followers_counter": "{count, plural, one {Dilynwr: {counter}} other {Dilynwyr: {counter}}}", "account.following": "Yn dilyn", - "account.following_counter": "{count, plural, one {Yn dilyn: {counter}} other {Yn dilyn: {counter}}}", "account.follows.empty": "Nid yw'r defnyddiwr hwn yn dilyn unrhyw un eto.", "account.go_to_profile": "Mynd i'r proffil", "account.hide_reblogs": "Cuddio hybiau gan @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "Mae {name} wedi gwneud cais i'ch dilyn", "account.share": "Rhannwch broffil @{name}", "account.show_reblogs": "Dangos hybiau gan @{name}", - "account.statuses_counter": "{count, plural, one {Postiad: {counter}} other {Postiad: {counter}}}", "account.unblock": "Dadflocio @{name}", "account.unblock_domain": "Dadflocio parth {domain}", "account.unblock_short": "Dadflocio", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 5ac7128a3..d8c178d29 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -35,9 +35,8 @@ "account.follow_back": "Følg tilbage", "account.followers": "Følgere", "account.followers.empty": "Ingen følger denne bruger endnu.", - "account.followers_counter": "{count, plural, one {{counter} Følger} other {{counter} Følgere}}", + "account.followers_counter": "{count, plural, one {{counter} følger} other {{counter} følgere}}", "account.following": "Følger", - "account.following_counter": "{count, plural, one {{counter} Følges} other {{counter} Følges}}", "account.follows.empty": "Denne bruger følger ikke nogen endnu.", "account.go_to_profile": "Gå til profil", "account.hide_reblogs": "Skjul boosts fra @{name}", @@ -63,7 +62,6 @@ "account.requested_follow": "{name} har anmodet om at følge dig", "account.share": "Del @{name}s profil", "account.show_reblogs": "Vis fremhævelser fra @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Indlæg} other {{counter} Indlæg}}", "account.unblock": "Afblokér @{name}", "account.unblock_domain": "Afblokér domænet {domain}", "account.unblock_short": "Afblokér", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 47a8df620..5442624b3 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -35,9 +35,7 @@ "account.follow_back": "Ακολούθησε και εσύ", "account.followers": "Ακόλουθοι", "account.followers.empty": "Κανείς δεν ακολουθεί αυτόν τον χρήστη ακόμα.", - "account.followers_counter": "{count, plural, one {{counter} Ακόλουθος} other {{counter} Ακόλουθοι}}", "account.following": "Ακολουθείτε", - "account.following_counter": "{count, plural, one {{counter} Ακολουθεί} other {{counter} Ακολουθούν}}", "account.follows.empty": "Αυτός ο χρήστης δεν ακολουθεί κανέναν ακόμα.", "account.go_to_profile": "Μετάβαση στο προφίλ", "account.hide_reblogs": "Απόκρυψη ενισχύσεων από @{name}", @@ -63,7 +61,6 @@ "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": "Άρση αποκλεισμού", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 108880cc9..c4f401d86 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -35,9 +35,7 @@ "account.follow_back": "Follow back", "account.followers": "Followers", "account.followers.empty": "No one follows this user yet.", - "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}", "account.following": "Following", - "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}", "account.follows.empty": "This user doesn't follow anyone yet.", "account.go_to_profile": "Go to profile", "account.hide_reblogs": "Hide boosts from @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} has requested to follow you", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Show boosts from @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}", "account.unblock": "Unblock @{name}", "account.unblock_domain": "Unblock domain {domain}", "account.unblock_short": "Unblock", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index bab277b48..e7cfc0346 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -35,9 +35,7 @@ "account.follow_back": "Sekvu reen", "account.followers": "Sekvantoj", "account.followers.empty": "Ankoraŭ neniu sekvas ĉi tiun uzanton.", - "account.followers_counter": "{count, plural, one{{counter} Sekvanto} other {{counter} Sekvantoj}}", "account.following": "Sekvatoj", - "account.following_counter": "{count, plural, one {{counter} Sekvato} other {{counter} Sekvatoj}}", "account.follows.empty": "La uzanto ankoraŭ ne sekvas iun ajn.", "account.go_to_profile": "Iri al profilo", "account.hide_reblogs": "Kaŝi diskonigojn de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} petis sekvi vin", "account.share": "Diskonigi la profilon de @{name}", "account.show_reblogs": "Montri diskonigojn de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Afiŝo} other {{counter} Afiŝoj}}", "account.unblock": "Malbloki @{name}", "account.unblock_domain": "Malbloki la domajnon {domain}", "account.unblock_short": "Malbloki", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 7da39b88c..28e8de923 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -37,7 +37,7 @@ "account.followers.empty": "Todavía nadie sigue a este usuario.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "Siguiendo", - "account.following_counter": "{count, plural, other {Siguiendo a {counter}}}", + "account.following_counter": "{count, plural, one {siguiendo a {counter}} other {siguiendo a {counter}}}", "account.follows.empty": "Todavía este usuario no sigue a nadie.", "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar adhesiones de @{name}", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index d3e02cd6e..c10a16101 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -35,9 +35,9 @@ "account.follow_back": "Seguir también", "account.followers": "Seguidores", "account.followers.empty": "Todavía nadie sigue a este usuario.", - "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}", + "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "Siguiendo", - "account.following_counter": "{count, plural, other {{counter} Siguiendo}}", + "account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}", "account.follows.empty": "Este usuario todavía no sigue a nadie.", "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar retoots de @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ha solicitado seguirte", "account.share": "Compartir el perfil de @{name}", "account.show_reblogs": "Mostrar retoots de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", + "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Mostrar a {domain}", "account.unblock_short": "Desbloquear", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 849e0fa27..259fc1795 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -35,9 +35,9 @@ "account.follow_back": "Seguir también", "account.followers": "Seguidores", "account.followers.empty": "Todavía nadie sigue a este usuario.", - "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}", + "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "Siguiendo", - "account.following_counter": "{count, plural, other {Siguiendo a {counter}}}", + "account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}", "account.follows.empty": "Este usuario todavía no sigue a nadie.", "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar impulsos de @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ha solicitado seguirte", "account.share": "Compartir el perfil de @{name}", "account.show_reblogs": "Mostrar impulsos de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Publicación} other {{counter} Publicaciones}}", + "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Desbloquear dominio {domain}", "account.unblock_short": "Desbloquear", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 547a0fe61..94f5ef5d9 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -35,9 +35,7 @@ "account.follow_back": "Jälgi vastu", "account.followers": "Jälgijad", "account.followers.empty": "Keegi ei jälgi veel seda kasutajat.", - "account.followers_counter": "{count, plural, one {{counter} jälgija} other {{counter} jälgijat}}", "account.following": "Jälgib", - "account.following_counter": "{count, plural, one {{counter} jälgitav} other {{counter} jälgitavat}}", "account.follows.empty": "See kasutaja ei jälgi veel kedagi.", "account.go_to_profile": "Mine profiilile", "account.hide_reblogs": "Peida @{name} jagamised", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} on taodelnud sinu jälgimist", "account.share": "Jaga @{name} profiili", "account.show_reblogs": "Näita @{name} jagamisi", - "account.statuses_counter": "{count, plural, one {{counter} postitus} other {{counter} postitust}}", "account.unblock": "Eemalda blokeering @{name}", "account.unblock_domain": "Tee {domain} nähtavaks", "account.unblock_short": "Eemalda blokeering", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 5fbac270c..97c4250d2 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -35,9 +35,7 @@ "account.follow_back": "Jarraitu bueltan", "account.followers": "Jarraitzaileak", "account.followers.empty": "Ez du inork erabiltzaile hau jarraitzen oraindik.", - "account.followers_counter": "{count, plural, one {Jarraitzaile {counter}} other {{counter} jarraitzaile}}", "account.following": "Jarraitzen", - "account.following_counter": "{count, plural, one {{counter} jarraitzen} other {{counter} jarraitzen}}", "account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.", "account.go_to_profile": "Joan profilera", "account.hide_reblogs": "Ezkutatu @{name} erabiltzailearen bultzadak", @@ -63,7 +61,6 @@ "account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du", "account.share": "Partekatu @{name} erabiltzailearen profila", "account.show_reblogs": "Erakutsi @{name} erabiltzailearen bultzadak", - "account.statuses_counter": "{count, plural, one {Bidalketa {counter}} other {{counter} bidalketa}}", "account.unblock": "Desblokeatu @{name}", "account.unblock_domain": "Berriz erakutsi {domain}", "account.unblock_short": "Desblokeatu", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 072a67421..18f6466d4 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -35,9 +35,7 @@ "account.follow_back": "دنبال کردن متقابل", "account.followers": "پیگیرندگان", "account.followers.empty": "هنوز کسی پیگیر این کاربر نیست.", - "account.followers_counter": "{count, plural, one {{counter} پیگیرنده} other {{counter} پیگیرنده}}", "account.following": "پی میگیرید", - "account.following_counter": "{count, plural, one {{counter} پیگرفته} other {{counter} پیگرفته}}", "account.follows.empty": "این کاربر هنوز پیگیر کسی نیست.", "account.go_to_profile": "رفتن به نمایه", "account.hide_reblogs": "نهفتن تقویتهای @{name}", @@ -63,7 +61,6 @@ "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": "رفع مسدودیت", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 67e2b72b8..0767dd5e3 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -699,7 +699,7 @@ "server_banner.is_one_of_many": "{domain} on yksi monista itsenäisistä Mastodon-palvelimista, joiden välityksellä voit toimia fediversumissa.", "server_banner.server_stats": "Palvelimen tilastot:", "sign_in_banner.create_account": "Luo tili", - "sign_in_banner.follow_anyone": "Seuraa kenen tahansa julkaisuja fediversumissa ja näe ne kaikki aikajärjestyksessä. Ei algoritmejä, mainoksia tai klikkikalastelua.", + "sign_in_banner.follow_anyone": "Seuraa kenen tahansa julkaisuja fediversumissa ja näe ne kaikki aikajärjestyksessä. Ei algoritmeja, mainoksia tai klikkausten kalastelua.", "sign_in_banner.mastodon_is": "Mastodon on paras tapa pysyä ajan tasalla siitä, mitä ympärillä tapahtuu.", "sign_in_banner.sign_in": "Kirjaudu", "sign_in_banner.sso_redirect": "Kirjaudu tai rekisteröidy", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 7a317820b..e7786f388 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -35,9 +35,7 @@ "account.follow_back": "Fylg aftur", "account.followers": "Fylgjarar", "account.followers.empty": "Ongar fylgjarar enn.", - "account.followers_counter": "{count, plural, one {{counter} Fylgjari} other {{counter} Fylgjarar}}", "account.following": "Fylgir", - "account.following_counter": "{count, plural, one {{counter} fylgir} other {{counter} fylgja}}", "account.follows.empty": "Hesin brúkari fylgir ongum enn.", "account.go_to_profile": "Far til vanga", "account.hide_reblogs": "Fjal lyft frá @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} hevur biðið um at fylgja tær", "account.share": "Deil vanga @{name}'s", "account.show_reblogs": "Vís lyft frá @{name}", - "account.statuses_counter": "{count, plural, one {{counter} postur} other {{counter} postar}}", "account.unblock": "Banna ikki @{name}", "account.unblock_domain": "Banna ikki økisnavnið {domain}", "account.unblock_short": "Banna ikki", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 50b7dcf90..432485500 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -35,9 +35,7 @@ "account.follow_back": "S'abonner en retour", "account.followers": "abonné·e·s", "account.followers.empty": "Personne ne suit ce compte pour l'instant.", - "account.followers_counter": "{count, plural, one {{counter} Abonné·e} other {{counter} Abonné·e·s}}", "account.following": "Abonné·e", - "account.following_counter": "{count, plural, one {{counter} Abonnement} other {{counter} Abonnements}}", "account.follows.empty": "Ce compte ne suit personne présentement.", "account.go_to_profile": "Voir ce profil", "account.hide_reblogs": "Masquer les boosts de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} a demandé à vous suivre", "account.share": "Partager le profil de @{name}", "account.show_reblogs": "Afficher les boosts de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Publication} other {{counter} Publications}}", "account.unblock": "Débloquer @{name}", "account.unblock_domain": "Débloquer le domaine {domain}", "account.unblock_short": "Débloquer", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 2e565c200..cd67cda53 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -35,9 +35,7 @@ "account.follow_back": "S'abonner en retour", "account.followers": "Abonné·e·s", "account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour l’instant.", - "account.followers_counter": "{count, plural, one {{counter} Abonné·e} other {{counter} Abonné·e·s}}", "account.following": "Abonnements", - "account.following_counter": "{count, plural, one {{counter} Abonnement} other {{counter} Abonnements}}", "account.follows.empty": "Cet·te utilisateur·rice ne suit personne pour l’instant.", "account.go_to_profile": "Aller au profil", "account.hide_reblogs": "Masquer les partages de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} a demandé à vous suivre", "account.share": "Partager le profil de @{name}", "account.show_reblogs": "Afficher les partages de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Message} other {{counter} Messages}}", "account.unblock": "Débloquer @{name}", "account.unblock_domain": "Débloquer le domaine {domain}", "account.unblock_short": "Débloquer", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 11b11ff81..d787c16bf 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -35,9 +35,7 @@ "account.follow_back": "Weromfolgje", "account.followers": "Folgers", "account.followers.empty": "Noch net ien folget dizze brûker.", - "account.followers_counter": "{count, plural, one {{counter} folger} other {{counter} folgers}}", "account.following": "Folgjend", - "account.following_counter": "{count, plural, one {{counter} folgjend} other {{counter} folgjend}}", "account.follows.empty": "Dizze brûker folget noch net ien.", "account.go_to_profile": "Gean nei profyl", "account.hide_reblogs": "Boosts fan @{name} ferstopje", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} hat dy in folchfersyk stjoerd", "account.share": "Profyl fan @{name} diele", "account.show_reblogs": "Boosts fan @{name} toane", - "account.statuses_counter": "{count, plural, one {{counter} berjocht} other {{counter} berjochten}}", "account.unblock": "@{name} deblokkearje", "account.unblock_domain": "Domein {domain} deblokkearje", "account.unblock_short": "Deblokkearje", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 97dcc752b..edf761814 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -31,9 +31,7 @@ "account.follow": "Lean", "account.followers": "Leantóirí", "account.followers.empty": "Ní leanann éinne an t-úsáideoir seo fós.", - "account.followers_counter": "{count, plural, one {Leantóir amháin} other {{counter} Leantóir}}", "account.following": "Ag leanúint", - "account.following_counter": "{count, plural, one {Ag leanúint cúntas amháin} other {Ag leanúint {counter} cúntas}}", "account.follows.empty": "Ní leanann an t-úsáideoir seo duine ar bith fós.", "account.go_to_profile": "Téigh go dtí próifíl", "account.hide_reblogs": "Folaigh moltaí ó @{name}", @@ -55,7 +53,6 @@ "account.requested_follow": "D'iarr {name} ort do chuntas a leanúint", "account.share": "Roinn próifíl @{name}", "account.show_reblogs": "Taispeáin moltaí ó @{name}", - "account.statuses_counter": "{count, plural, one {Postáil amháin} other {{counter} Postáil}}", "account.unblock": "Bain bac de @{name}", "account.unblock_domain": "Bain bac den ainm fearainn {domain}", "account.unblock_short": "Bain bac de", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 714fa6e36..fec025045 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -35,9 +35,7 @@ "account.follow_back": "Lean air ais", "account.followers": "Luchd-leantainn", "account.followers.empty": "Chan eil neach sam bith a’ leantainn air a’ chleachdaiche seo fhathast.", - "account.followers_counter": "{count, plural, one {{counter} neach-leantainn} two {{counter} neach-leantainn} few {{counter} luchd-leantainn} other {{counter} luchd-leantainn}}", "account.following": "A’ leantainn", - "account.following_counter": "{count, plural, one {A’ leantainn {counter}} two {A’ leantainn {counter}} few {A’ leantainn {counter}} other {A’ leantainn {counter}}}", "account.follows.empty": "Chan eil an cleachdaiche seo a’ leantainn neach sam bith fhathast.", "account.go_to_profile": "Tadhail air a’ phròifil", "account.hide_reblogs": "Falaich na brosnachaidhean o @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "Dh’iarr {name} ’gad leantainn", "account.share": "Co-roinn a’ phròifil aig @{name}", "account.show_reblogs": "Seall na brosnachaidhean o @{name}", - "account.statuses_counter": "{count, plural, one {{counter} phost} two {{counter} phost} few {{counter} postaichean} other {{counter} post}}", "account.unblock": "Dì-bhac @{name}", "account.unblock_domain": "Dì-bhac an àrainn {domain}", "account.unblock_short": "Dì-bhac", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 7b77f9803..156fe3ee8 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -35,9 +35,7 @@ "account.follow_back": "Seguir tamén", "account.followers": "Seguidoras", "account.followers.empty": "Aínda ninguén segue esta usuaria.", - "account.followers_counter": "{count, plural, one {{counter} Seguidora} other {{counter} Seguidoras}}", "account.following": "Seguindo", - "account.following_counter": "{count, plural, one {{counter} Seguindo} other {{counter} Seguindo}}", "account.follows.empty": "Esta usuaria aínda non segue a ninguén.", "account.go_to_profile": "Ir ao perfil", "account.hide_reblogs": "Agochar promocións de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} solicitou seguirte", "account.share": "Compartir o perfil de @{name}", "account.show_reblogs": "Amosar compartidos de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Publicación} other {{counter} Publicacións}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Amosar {domain}", "account.unblock_short": "Desbloquear", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 1c50ba8e1..e022eac11 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -35,9 +35,7 @@ "account.follow_back": "לעקוב בחזרה", "account.followers": "עוקבים", "account.followers.empty": "אף אחד לא עוקב אחר המשתמש הזה עדיין.", - "account.followers_counter": "{count, plural,one {עוקב אחד} other {{counter} עוקבים}}", "account.following": "נעקבים", - "account.following_counter": "{count, plural,one {עוקב אחרי {counter}}other {עוקב אחרי {counter}}}", "account.follows.empty": "משתמש זה עדיין לא עוקב אחרי אף אחד.", "account.go_to_profile": "מעבר לפרופיל", "account.hide_reblogs": "להסתיר הידהודים מאת @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ביקשו לעקוב אחריך", "account.share": "שתף את הפרופיל של @{name}", "account.show_reblogs": "הצג הדהודים מאת @{name}", - "account.statuses_counter": "{count, plural, one {הודעה} two {הודעותיים} many {{count} הודעות} other {{count} הודעות}}", "account.unblock": "להסיר חסימה ל- @{name}", "account.unblock_domain": "הסירי את החסימה של קהילת {domain}", "account.unblock_short": "הסר חסימה", diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json index a2da55da8..89c71207f 100644 --- a/app/javascript/mastodon/locales/hi.json +++ b/app/javascript/mastodon/locales/hi.json @@ -35,9 +35,7 @@ "account.follow_back": "फॉलो करें", "account.followers": "फॉलोवर", "account.followers.empty": "कोई भी इस यूज़र् को फ़ॉलो नहीं करता है", - "account.followers_counter": "{count, plural, one {{counter} अनुगामी} other {{counter} समर्थक}}", "account.following": "फॉलोइंग", - "account.following_counter": "{count, plural, one {{counter} निम्नलिखित} other {{counter} निम्नलिखित}}", "account.follows.empty": "यह यूज़र् अभी तक किसी को फॉलो नहीं करता है।", "account.go_to_profile": "प्रोफाइल में जाएँ", "account.hide_reblogs": "@{name} के बूस्ट छुपाएं", @@ -63,7 +61,6 @@ "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": "अनब्लॉक", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index d952945c4..c8f6f0186 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -35,9 +35,7 @@ "account.follow_back": "Slijedi natrag", "account.followers": "Pratitelji", "account.followers.empty": "Nitko još ne prati korisnika/cu.", - "account.followers_counter": "{count, plural, one {{counter} pratitelj} other {{counter} pratitelja}}", "account.following": "Pratim", - "account.following_counter": "{count, plural, one {{counter} praćeni} few{{counter} praćena} other {{counter} praćenih}}", "account.follows.empty": "Korisnik/ca još ne prati nikoga.", "account.go_to_profile": "Idi na profil", "account.hide_reblogs": "Sakrij boostove od @{name}", @@ -62,7 +60,6 @@ "account.requested_follow": "{name} zatražio/la je praćenje", "account.share": "Podijeli profil @{name}", "account.show_reblogs": "Prikaži boostove od @{name}", - "account.statuses_counter": "{count, plural, one {{counter} toot} other {{counter} toota}}", "account.unblock": "Deblokiraj @{name}", "account.unblock_domain": "Deblokiraj domenu {domain}", "account.unblock_short": "Deblokiraj", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 6164335da..627e3cab5 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -35,9 +35,7 @@ "account.follow_back": "Viszontkövetés", "account.followers": "Követő", "account.followers.empty": "Ezt a felhasználót még senki sem követi.", - "account.followers_counter": "{count, plural, one {{counter} Követő} other {{counter} Követő}}", "account.following": "Követve", - "account.following_counter": "{count, plural, one {{counter} Követett} other {{counter} Követett}}", "account.follows.empty": "Ez a felhasználó még senkit sem követ.", "account.go_to_profile": "Ugrás a profilhoz", "account.hide_reblogs": "@{name} megtolásainak elrejtése", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} kérte, hogy követhessen", "account.share": "@{name} profiljának megosztása", "account.show_reblogs": "@{name} megtolásainak mutatása", - "account.statuses_counter": "{count, plural, one {{counter} Bejegyzés} other {{counter} Bejegyzés}}", "account.unblock": "@{name} letiltásának feloldása", "account.unblock_domain": "{domain} domain tiltásának feloldása", "account.unblock_short": "Tiltás feloldása", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index cd29f441d..b4abe9bf0 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -28,9 +28,7 @@ "account.follow": "Հետեւել", "account.followers": "Հետեւողներ", "account.followers.empty": "Այս օգտատիրոջը դեռ ոչ մէկ չի հետեւում։", - "account.followers_counter": "{count, plural, one {{counter} Հետեւորդ} other {{counter} Հետեւորդ}}", "account.following": "Հետեւած", - "account.following_counter": "{count, plural, one {{counter} Հետեւած} other {{counter} Հետեւած}}", "account.follows.empty": "Այս օգտատէրը դեռ ոչ մէկի չի հետեւում։", "account.go_to_profile": "Գնալ անձնական հաշիւ", "account.hide_reblogs": "Թաքցնել @{name}֊ի տարածածները", @@ -52,7 +50,6 @@ "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": "Արգելաբացել", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index 53cc93859..a2e64c10f 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -699,6 +699,7 @@ "server_banner.is_one_of_many": "{domain} es un de multe servitores independente de Mastodon que tu pote usar pro participar in le fediverso.", "server_banner.server_stats": "Statos del servitor:", "sign_in_banner.create_account": "Crear un conto", + "sign_in_banner.follow_anyone": "Seque quicunque in le fediverso, e tu videra toto in ordine chronologic. Sin algorithmo, sin publicitate, sin titulos de esca.", "sign_in_banner.mastodon_is": "Mastodon es le melior maniera de sequer lo que passa.", "sign_in_banner.sign_in": "Aperir session", "sign_in_banner.sso_redirect": "Aperir session o crear conto", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index d86b5854f..f4e5e1bae 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -35,9 +35,7 @@ "account.follow_back": "Ikuti balik", "account.followers": "Pengikut", "account.followers.empty": "Pengguna ini belum ada pengikut.", - "account.followers_counter": "{count, plural, other {{counter} Pengikut}}", "account.following": "Mengikuti", - "account.following_counter": "{count, plural, other {{counter} Mengikuti}}", "account.follows.empty": "Pengguna ini belum mengikuti siapa pun.", "account.go_to_profile": "Buka profil", "account.hide_reblogs": "Sembunyikan boosts dari @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ingin mengikuti Anda", "account.share": "Bagikan profil @{name}", "account.show_reblogs": "Tampilkan boost dari @{name}", - "account.statuses_counter": "{count, plural, other {{counter} Kiriman}}", "account.unblock": "Buka blokir @{name}", "account.unblock_domain": "Buka blokir domain {domain}", "account.unblock_short": "Buka blokir", diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index f15b98288..c75788c43 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -35,9 +35,7 @@ "account.follow_back": "Sequer reciprocmen", "account.followers": "Sequitores", "account.followers.empty": "Ancor nequi seque ti-ci usator.", - "account.followers_counter": "{count, plural, one {{counter} Sequitor} other {{counter} Sequitor}}", "account.following": "Sequent", - "account.following_counter": "{count, plural, one {{counter} Sequent} other {{counter} Sequent}}", "account.follows.empty": "Ti-ci usator ancor ne seque quemcunc.", "account.go_to_profile": "Ear a profil", "account.hide_reblogs": "Celar boosts de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ha petit sequer te", "account.share": "Distribuer li profil de @{name}", "account.show_reblogs": "Monstrar boosts de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Posta} other {{counter} Postas}}", "account.unblock": "Desbloccar @{name}", "account.unblock_domain": "Desbloccar dominia {domain}", "account.unblock_short": "Desbloccar", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 016a111c4..6aa954ae5 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -33,9 +33,7 @@ "account.follow": "Sequar", "account.followers": "Sequanti", "account.followers.empty": "Nulu sequas ca uzanto til nun.", - "account.followers_counter": "{count, plural, one {{counter} Sequanto} other {{counter} Sequanti}}", "account.following": "Sequata", - "account.following_counter": "{count, plural, one {{counter} Sequas} other {{counter} Sequanti}}", "account.follows.empty": "Ca uzanto ne sequa irgu til nun.", "account.go_to_profile": "Irez al profilo", "account.hide_reblogs": "Celez repeti de @{name}", @@ -60,7 +58,6 @@ "account.requested_follow": "{name} demandis sequar tu", "account.share": "Partigez profilo di @{name}", "account.show_reblogs": "Montrez repeti de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Posto} other {{counter} Posti}}", "account.unblock": "Desblokusar @{name}", "account.unblock_domain": "Desblokusar {domain}", "account.unblock_short": "Desblokusar", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 08605f523..1a38591b8 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -63,7 +63,7 @@ "account.requested_follow": "{name} hefur beðið um að fylgjast með þér", "account.share": "Deila notandasniði fyrir @{name}", "account.show_reblogs": "Sýna endurbirtingar frá @{name}", - "account.statuses_counter": "{count, plural, one {Færsla: {counter}} other {Færslur: {counter}}}", + "account.statuses_counter": "{count, plural, one {{counter} færsla} other {{counter} færslur}}", "account.unblock": "Aflétta útilokun af @{name}", "account.unblock_domain": "Aflétta útilokun lénsins {domain}", "account.unblock_short": "Hætta að loka á", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 3672b5fd7..73c4f9ba6 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -35,9 +35,9 @@ "account.follow_back": "Segui a tua volta", "account.followers": "Follower", "account.followers.empty": "Ancora nessuno segue questo utente.", - "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}", + "account.followers_counter": "{count, plural, one {{counter} seguace} other {{counter} seguaci}}", "account.following": "Seguiti", - "account.following_counter": "{count, plural, one {{counter} Seguiti} other {{counter} Seguiti}}", + "account.following_counter": "{count, plural, one {{counter} segui} other {{counter} segui}}", "account.follows.empty": "Questo utente non segue ancora nessuno.", "account.go_to_profile": "Vai al profilo", "account.hide_reblogs": "Nascondi potenziamenti da @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ha richiesto di seguirti", "account.share": "Condividi il profilo di @{name}", "account.show_reblogs": "Mostra potenziamenti da @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Post}}", + "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} post}}", "account.unblock": "Sblocca @{name}", "account.unblock_domain": "Sblocca il dominio {domain}", "account.unblock_short": "Sblocca", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 90a46edd5..575c68de0 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -35,9 +35,7 @@ "account.follow_back": "フォローバック", "account.followers": "フォロワー", "account.followers.empty": "まだ誰もフォローしていません。", - "account.followers_counter": "{counter} フォロワー", "account.following": "フォロー中", - "account.following_counter": "{counter} フォロー", "account.follows.empty": "まだ誰もフォローしていません。", "account.go_to_profile": "プロフィールページへ", "account.hide_reblogs": "@{name}さんからのブーストを非表示", @@ -63,7 +61,6 @@ "account.requested_follow": "{name}さんがあなたにフォローリクエストしました", "account.share": "@{name}さんのプロフィールを共有する", "account.show_reblogs": "@{name}さんからのブーストを表示", - "account.statuses_counter": "{counter} 投稿", "account.unblock": "@{name}さんのブロックを解除", "account.unblock_domain": "{domain}のブロックを解除", "account.unblock_short": "ブロック解除", diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json index 7af4dccd8..b2e67e143 100644 --- a/app/javascript/mastodon/locales/ka.json +++ b/app/javascript/mastodon/locales/ka.json @@ -26,7 +26,6 @@ "account.requested": "დამტკიცების მოლოდინში. დააწკაპუნეთ რომ უარყოთ დადევნების მოთხონვა", "account.share": "გააზიარე @{name}-ის პროფილი", "account.show_reblogs": "აჩვენე ბუსტები @{name}-სგან", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "განბლოკე @{name}", "account.unblock_domain": "გამოაჩინე {domain}", "account.unendorse": "არ გამოირჩეს პროფილზე", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index 5aa46bafd..de866cc1b 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -26,9 +26,7 @@ "account.follow": "Ḍfer", "account.followers": "Imeḍfaren", "account.followers.empty": "Ar tura, ulac yiwen i yeṭṭafaṛen amseqdac-agi.", - "account.followers_counter": "{count, plural, one {{count} n umeḍfar} other {{count} n imeḍfaren}}", "account.following": "Yeṭṭafaṛ", - "account.following_counter": "{count, plural, one {{counter} yettwaḍfaren} other {{counter} yettwaḍfaren}}", "account.follows.empty": "Ar tura, amseqdac-agi ur yeṭṭafaṛ yiwen.", "account.go_to_profile": "Ddu ɣer umaɣnu", "account.hide_reblogs": "Ffer ayen i ibeṭṭu @{name}", @@ -51,7 +49,6 @@ "account.requested_follow": "{name} yessuter ad k·m-yeḍfer", "account.share": "Bḍu amaɣnu n @{name}", "account.show_reblogs": "Ssken-d inebḍa n @{name}", - "account.statuses_counter": "{count, plural, one {{counter} n tsuffeɣt} other {{counter} n tsuffaɣ}}", "account.unblock": "Serreḥ i @{name}", "account.unblock_domain": "Ssken-d {domain}", "account.unblock_short": "Serreḥ", diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json index bd0a806cd..efeee16c6 100644 --- a/app/javascript/mastodon/locales/kk.json +++ b/app/javascript/mastodon/locales/kk.json @@ -31,9 +31,7 @@ "account.follow": "Жазылу", "account.followers": "Жазылушы", "account.followers.empty": "Бұл қолданушыға әлі ешкім жазылмаған.", - "account.followers_counter": "{count, plural, one {{counter} жазылушы} other {{counter} жазылушы}}", "account.following": "Жазылым", - "account.following_counter": "{count, plural, one {{counter} жазылым} other {{counter} жазылым}}", "account.follows.empty": "Бұл қолданушы әлі ешкімге жазылмаған.", "account.go_to_profile": "Профиліне өту", "account.hide_reblogs": "@{name} бустарын жасыру", @@ -52,7 +50,6 @@ "account.requested": "Растауын күтіңіз. Жазылудан бас тарту үшін басыңыз", "account.share": "@{name} профилін бөлісу\"", "account.show_reblogs": "@{name} бөліскендерін көрсету", - "account.statuses_counter": "{count, plural, one {{counter} Пост} other {{counter} Пост}}", "account.unblock": "Бұғаттан шығару @{name}", "account.unblock_domain": "Бұғаттан шығару {domain}", "account.unendorse": "Профильде рекомендемеу", diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json index ceb0f8b9b..24592e37f 100644 --- a/app/javascript/mastodon/locales/kn.json +++ b/app/javascript/mastodon/locales/kn.json @@ -16,7 +16,6 @@ "account.posts": "ಟೂಟ್ಗಳು", "account.posts_with_replies": "Toots and replies", "account.requested": "Awaiting approval", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock_domain": "Unhide {domain}", "account_note.placeholder": "Click to add a note", "alert.unexpected.title": "ಅಯ್ಯೋ!", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index c4c084d98..90755666b 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -35,9 +35,7 @@ "account.follow_back": "맞팔로우 하기", "account.followers": "팔로워", "account.followers.empty": "아직 아무도 이 사용자를 팔로우하고 있지 않습니다.", - "account.followers_counter": "{counter} 팔로워", "account.following": "팔로잉", - "account.following_counter": "{counter} 팔로잉", "account.follows.empty": "이 사용자는 아직 아무도 팔로우하고 있지 않습니다.", "account.go_to_profile": "프로필로 이동", "account.hide_reblogs": "@{name}의 부스트를 숨기기", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} 님이 팔로우 요청을 보냈습니다", "account.share": "@{name}의 프로필 공유", "account.show_reblogs": "@{name}의 부스트 보기", - "account.statuses_counter": "{counter} 게시물", "account.unblock": "차단 해제", "account.unblock_domain": "도메인 {domain} 차단 해제", "account.unblock_short": "차단 해제", diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json index 83fcef26f..5248cdfa5 100644 --- a/app/javascript/mastodon/locales/ku.json +++ b/app/javascript/mastodon/locales/ku.json @@ -32,9 +32,7 @@ "account.follow": "Bişopîne", "account.followers": "Şopîner", "account.followers.empty": "Kesekî hin ev bikarhêner neşopandiye.", - "account.followers_counter": "{count, plural, one {{counter} Şopîner} other {{counter} Şopîner}}", "account.following": "Dişopîne", - "account.following_counter": "{count, plural, one {{counter} Dişopîne} other {{counter} Dişopîne}}", "account.follows.empty": "Ev bikarhêner hin kesekî heya niha neşopandiye.", "account.go_to_profile": "Biçe bo profîlê", "account.hide_reblogs": "Bilindkirinên ji @{name} veşêre", @@ -56,7 +54,6 @@ "account.requested_follow": "{name} dixwaze te bişopîne", "account.share": "Profîla @{name} parve bike", "account.show_reblogs": "Bilindkirinên ji @{name} nîşan bike", - "account.statuses_counter": "{count, plural,one {{counter} Şandî}other {{counter} Şandî}}", "account.unblock": "Astengê li ser @{name} rake", "account.unblock_domain": "Astengê li ser navperê {domain} rake", "account.unblock_short": "Astengiyê rake", diff --git a/app/javascript/mastodon/locales/kw.json b/app/javascript/mastodon/locales/kw.json index 794cbd9ed..1afcf645c 100644 --- a/app/javascript/mastodon/locales/kw.json +++ b/app/javascript/mastodon/locales/kw.json @@ -17,8 +17,6 @@ "account.follow": "Holya", "account.followers": "Holyoryon", "account.followers.empty": "Ny wra nagonan holya'n devnydhyer ma hwath.", - "account.followers_counter": "{count, plural, one {{counter} Holyer} other {{counter} Holyer}}", - "account.following_counter": "{count, plural, one {Ow holya {counter}} other {Ow holya {counter}}}", "account.follows.empty": "Ny wra'n devnydhyer ma holya nagonan hwath.", "account.hide_reblogs": "Kudha kenerthow a @{name}", "account.link_verified_on": "Perghenogeth an kolm ma a veu checkys dhe {date}", @@ -33,7 +31,6 @@ "account.requested": "Ow kortos komendyans. Klyckyewgh dhe hedhi govyn holya", "account.share": "Kevrenna profil @{name}", "account.show_reblogs": "Diskwedhes kenerthow a @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Tout} other {{counter} Tout}}", "account.unblock": "Anlettya @{name}", "account.unblock_domain": "Anlettya gorfarth {domain}", "account.unendorse": "Na wra diskwedhes yn profil", diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json index d867034f0..aa209fcc0 100644 --- a/app/javascript/mastodon/locales/la.json +++ b/app/javascript/mastodon/locales/la.json @@ -14,12 +14,9 @@ "account.edit_profile": "Recolere notionem", "account.featured_tags.last_status_never": "Nulla contributa", "account.featured_tags.title": "Hashtag notātī {name}", - "account.followers_counter": "{count, plural, one {{counter} Sectator} other {{counter} Sectatores}}", - "account.following_counter": "{count, plural, one {{counter} Sequens} other {{counter} Sequentes}}", "account.moved_to": "{name} significavit eum suam rationem novam nunc esse:", "account.muted": "Confutatus", "account.requested_follow": "{name} postulavit ut te sequeretur", - "account.statuses_counter": "{count, plural, one {{counter} Nuntius} other {{counter} Nuntii}}", "account.unblock_short": "Solvere impedimentum", "account_note.placeholder": "Click to add a note", "admin.dashboard.retention.average": "Mediocritas", diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index bf676a602..292f00818 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -35,9 +35,7 @@ "account.follow_back": "Sige tamyen", "account.followers": "Suivantes", "account.followers.empty": "Por agora dingun no sige a este utilizador.", - "account.followers_counter": "{count, plural, one {{counter} suivante} other {{counter} suivantes}}", "account.following": "Sigiendo", - "account.following_counter": "{count, plural, other {Sigiendo a {counter}}}", "account.follows.empty": "Este utilizador ainda no sige a dingun.", "account.go_to_profile": "Va al profil", "account.hide_reblogs": "Eskonde repartajasyones de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} tiene solisitado segirte", "account.share": "Partaja el profil de @{name}", "account.show_reblogs": "Amostra repartajasyones de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}", "account.unblock": "Dezbloka a @{name}", "account.unblock_domain": "Dezbloka domeno {domain}", "account.unblock_short": "Dezbloka", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index bb69b7339..6bf8f94bd 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -35,9 +35,7 @@ "account.follow_back": "Sekti atgal", "account.followers": "Sekėjai", "account.followers.empty": "Šio naudotojo dar niekas neseka.", - "account.followers_counter": "{count, plural, one {{counter} sekėjas} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}", "account.following": "Sekama", - "account.following_counter": "{count, plural, one {{counter} sekimas} few {{counter} sekimai} many {{counter} sekimo} other {{counter} sekimų}}", "account.follows.empty": "Šis naudotojas dar nieko neseka.", "account.go_to_profile": "Eiti į profilį", "account.hide_reblogs": "Slėpti pakėlimus iš @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} paprašė tave sekti", "account.share": "Bendrinti @{name} profilį", "account.show_reblogs": "Rodyti pakėlimus iš @{name}", - "account.statuses_counter": "{count, plural, one {{counter} įrašas} few {{counter} įrašai} many {{counter} įrašo} other {{counter} įrašų}}", "account.unblock": "Atblokuoti @{name}", "account.unblock_domain": "Atblokuoti domeną {domain}", "account.unblock_short": "Atblokuoti", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 13ceec21c..041072c6a 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -35,9 +35,7 @@ "account.follow_back": "Sekot atpakaļ", "account.followers": "Sekotāji", "account.followers.empty": "Šim lietotājam vēl nav sekotāju.", - "account.followers_counter": "{count, plural, zero {{counter} sekotāju} one {{counter} sekotājs} other {{counter} sekotāji}}", "account.following": "Seko", - "account.following_counter": "{count, plural, zero{{counter} sekojamo} one {{counter} sekojamais} other {{counter} sekojamie}}", "account.follows.empty": "Šis lietotājs pagaidām nevienam neseko.", "account.go_to_profile": "Doties uz profilu", "account.hide_reblogs": "Paslēpt @{name} pastiprinātos ierakstus", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} nosūtīja Tev sekošanas pieprasījumu", "account.share": "Dalīties ar @{name} profilu", "account.show_reblogs": "Parādīt @{name} pastiprinātos ierakstus", - "account.statuses_counter": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}}", "account.unblock": "Atbloķēt @{name}", "account.unblock_domain": "Atbloķēt domēnu {domain}", "account.unblock_short": "Atbloķēt", diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json index d8a470ed4..a09ad98eb 100644 --- a/app/javascript/mastodon/locales/mk.json +++ b/app/javascript/mastodon/locales/mk.json @@ -38,7 +38,6 @@ "account.requested": "Се чека одобрување. Кликни за да одкажиш барање за следење", "account.share": "Сподели @{name} профил", "account.show_reblogs": "Прикажи бустови од @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "Одблокирај @{name}", "account.unblock_domain": "Прикажи {domain}", "account.unendorse": "Не прикажувај на профил", diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json index 8fb4e818d..d9caccef3 100644 --- a/app/javascript/mastodon/locales/ml.json +++ b/app/javascript/mastodon/locales/ml.json @@ -22,9 +22,7 @@ "account.follow": "പിന്തുടരുക", "account.followers": "പിന്തുടരുന്നവർ", "account.followers.empty": "ഈ ഉപയോക്താവിനെ ആരും ഇതുവരെ പിന്തുടരുന്നില്ല.", - "account.followers_counter": "{count, plural, one {{counter} പിന്തുടരുന്നവർ} other {{counter} പിന്തുടരുന്നവർ}}", "account.following": "പിന്തുടരുന്നു", - "account.following_counter": "{count, plural, one {{counter} പിന്തുടരുന്നു} other {{counter} പിന്തുടരുന്നു}}", "account.follows.empty": "ഈ ഉപയോക്താവ് ആരേയും ഇതുവരെ പിന്തുടരുന്നില്ല.", "account.go_to_profile": "പ്രൊഫൈലിലേക്ക് പോകാം", "account.hide_reblogs": "@{name} ബൂസ്റ്റ് ചെയ്തവ മറയ്കുക", @@ -42,7 +40,6 @@ "account.requested": "അനുവാദത്തിനായി കാത്തിരിക്കുന്നു. പിന്തുടരാനുള്ള അപേക്ഷ റദ്ദാക്കുവാൻ ഞെക്കുക", "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": "അൺബ്ലോക്കു ചെയ്യുക", diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json index c07294d90..2757b96f9 100644 --- a/app/javascript/mastodon/locales/mr.json +++ b/app/javascript/mastodon/locales/mr.json @@ -35,9 +35,7 @@ "account.follow_back": "आपणही अनुसरण करा", "account.followers": "अनुयायी", "account.followers.empty": "ह्या वापरकर्त्याचा आतापर्यंत कोणी अनुयायी नाही.", - "account.followers_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.following": "अनुसरण", - "account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}", "account.follows.empty": "हा वापरकर्ता अजूनपर्यंत कोणाचा अनुयायी नाही.", "account.go_to_profile": "प्रोफाइल वर जा", "account.hide_reblogs": "@{name} पासून सर्व बूस्ट लपवा", @@ -59,7 +57,6 @@ "account.requested_follow": "{name} ने आपल्याला फॉलो करण्याची रिक्वेस्ट केली आहे", "account.share": "@{name} चे प्रोफाइल शेअर करा", "account.show_reblogs": "{name}चे सर्व बुस्ट्स दाखवा", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name} ला ब्लॉक करा", "account.unblock_domain": "उघड करा {domain}", "account.unblock_short": "अनब्लॉक करा", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index 3d7992faf..88c093bde 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -35,9 +35,7 @@ "account.follow_back": "Ikut balik", "account.followers": "Pengikut", "account.followers.empty": "Belum ada yang mengikuti pengguna ini.", - "account.followers_counter": "{count, plural, one {{counter} Pengikut} other {{counter} Pengikut}}", "account.following": "Mengikuti", - "account.following_counter": "{count, plural, one {{counter} Diikuti} other {{counter} Diikuti}}", "account.follows.empty": "Pengguna ini belum mengikuti sesiapa.", "account.go_to_profile": "Pergi ke profil", "account.hide_reblogs": "Sembunyikan galakan daripada @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} has requested to follow you", "account.share": "Kongsi profil @{name}", "account.show_reblogs": "Tunjukkan galakan daripada @{name}", - "account.statuses_counter": "{count, plural, other {{counter} kiriman}}", "account.unblock": "Nyahsekat @{name}", "account.unblock_domain": "Nyahsekat domain {domain}", "account.unblock_short": "Nyahsekat", diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json index e3287f3f3..46c8d1806 100644 --- a/app/javascript/mastodon/locales/my.json +++ b/app/javascript/mastodon/locales/my.json @@ -34,9 +34,7 @@ "account.follow": "စောင့်ကြည့်", "account.followers": "စောင့်ကြည့်သူများ", "account.followers.empty": "ဤသူကို စောင့်ကြည့်သူ မရှိသေးပါ။", - "account.followers_counter": "{count, plural, one {စောင့်ကြည့်သူ {counter}} other {စောင့်ကြည့်သူများ {counter}}}", "account.following": "စောင့်ကြည့်နေသည်", - "account.following_counter": "{count, plural, one {စောင့်ကြည့်ထားသူ {counter}} other {စောင့်ကြည့်ထားသူများ {counter}}}", "account.follows.empty": "ဤသူသည် မည်သူ့ကိုမျှ စောင့်ကြည့်ခြင်း မရှိသေးပါ။", "account.go_to_profile": "ပရိုဖိုင်းသို့ သွားရန်", "account.hide_reblogs": "@{name} ၏ မျှဝေမှုကို ဝှက်ထားရန်", @@ -61,7 +59,6 @@ "account.requested_follow": "{name} က သင့်ကို စောင့်ကြည့်ရန် တောင်းဆိုထားသည်", "account.share": "{name}၏ပရိုဖိုင်ကိုမျှဝေပါ", "account.show_reblogs": "@{name} မှ မျှ၀ေမှုများကို ပြပါ\n", - "account.statuses_counter": "{count, plural, one {{counter} ပိုစ့်များ} other {{counter} ပိုစ့်များ}}", "account.unblock": "{name} ကို ဘလော့ဖြုတ်မည်", "account.unblock_domain": " {domain} ဒိုမိန်းကိုပြန်ဖွင့်မည်", "account.unblock_short": "ဘလော့ဖြုတ်ရန်", diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json index 500261a34..ca23a1f78 100644 --- a/app/javascript/mastodon/locales/ne.json +++ b/app/javascript/mastodon/locales/ne.json @@ -39,7 +39,6 @@ "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": "अनब्लक गर्नुहोस्", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 93b44f29a..7eca29659 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -35,9 +35,7 @@ "account.follow_back": "Fylg tilbake", "account.followers": "Fylgjarar", "account.followers.empty": "Ingen fylgjer denne brukaren enno.", - "account.followers_counter": "{count, plural, one {{counter} fylgjar} other {{counter} fylgjarar}}", "account.following": "Fylgjer", - "account.following_counter": "{count, plural, one {Fylgjer {counter}} other {Fylgjer {counter}}}", "account.follows.empty": "Denne brukaren fylgjer ikkje nokon enno.", "account.go_to_profile": "Gå til profil", "account.hide_reblogs": "Gøym framhevingar frå @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} har bedt om å få fylgja deg", "account.share": "Del @{name} sin profil", "account.show_reblogs": "Vis framhevingar frå @{name}", - "account.statuses_counter": "{count, plural, one {{counter} tut} other {{counter} tut}}", "account.unblock": "Stopp blokkering av @{name}", "account.unblock_domain": "Stopp blokkering av domenet {domain}", "account.unblock_short": "Stopp blokkering", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 213ba8af1..2bda37340 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -35,9 +35,7 @@ "account.follow_back": "Følg tilbake", "account.followers": "Følgere", "account.followers.empty": "Ingen følger denne brukeren ennå.", - "account.followers_counter": "{count, plural, one {{counter} følger} other {{counter} følgere}}", "account.following": "Følger", - "account.following_counter": "{count, plural, one {{counter} som følges} other {{counter} som følges}}", "account.follows.empty": "Denne brukeren følger ikke noen enda.", "account.go_to_profile": "Gå til profil", "account.hide_reblogs": "Skjul fremhevinger fra @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} har bedt om å få følge deg", "account.share": "Del @{name} sin profil", "account.show_reblogs": "Vis fremhevinger fra @{name}", - "account.statuses_counter": "{count, plural, one {{counter} innlegg} other {{counter} innlegg}}", "account.unblock": "Opphev blokkering av @{name}", "account.unblock_domain": "Opphev blokkering av {domain}", "account.unblock_short": "Opphev blokkering", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index d8e114158..d977eed4a 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -32,9 +32,7 @@ "account.follow_back": "Sègre en retorn", "account.followers": "Seguidors", "account.followers.empty": "Degun sèc pas aqueste utilizaire pel moment.", - "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidors}}", "account.following": "Abonat", - "account.following_counter": "{count, plural, one {{counter} Abonaments} other {{counter} Abonaments}}", "account.follows.empty": "Aqueste utilizaire sèc pas degun pel moment.", "account.go_to_profile": "Anar al perfil", "account.hide_reblogs": "Rescondre los partatges de @{name}", @@ -60,7 +58,6 @@ "account.requested_follow": "{name} a demandat a vos sègre", "account.share": "Partejar lo perfil a @{name}", "account.show_reblogs": "Mostrar los partatges de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Tut} other {{counter} Tuts}}", "account.unblock": "Desblocar @{name}", "account.unblock_domain": "Desblocar {domain}", "account.unblock_short": "Desblocat", diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index 46924d737..3828ff887 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -25,9 +25,7 @@ "account.follow_back": "ਵਾਪਸ ਫਾਲ਼ੋ ਕਰੋ", "account.followers": "ਫ਼ਾਲੋਅਰ", "account.followers.empty": "ਇਸ ਵਰਤੋਂਕਾਰ ਨੂੰ ਹਾਲੇ ਕੋਈ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।", - "account.followers_counter": "{count, plural, one {{counter} ਫ਼ਾਲੋਅਰ} other {{counter} ਫ਼ਾਲੋਅਰ}}", "account.following": "ਫ਼ਾਲੋ ਕੀਤਾ", - "account.following_counter": "{count, plural, one {{counter} ਨੂੰ ਫ਼ਾਲੋ} other {{counter} ਨੂੰ ਫ਼ਾਲੋ}}", "account.follows.empty": "ਇਹ ਵਰਤੋਂਕਾਰ ਹਾਲੇ ਕਿਸੇ ਨੂੰ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।", "account.go_to_profile": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਜਾਓ", "account.media": "ਮੀਡੀਆ", @@ -41,7 +39,6 @@ "account.requested": "ਮਨਜ਼ੂਰੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਫ਼ਾਲੋ ਬੇਨਤੀਆਂ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ", "account.requested_follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ", "account.share": "{name} ਦਾ ਪਰੋਫ਼ਾਇਲ ਸਾਂਝਾ ਕਰੋ", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name} ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ", "account.unblock_domain": "{domain} ਡੋਮੇਨ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ", "account.unblock_short": "ਪਾਬੰਦੀ ਹਟਾਓ", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index ddfe1d4fb..a3690e734 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -35,9 +35,7 @@ "account.follow_back": "Obserwuj wzajemnie", "account.followers": "Obserwujący", "account.followers.empty": "Nikt jeszcze nie obserwuje tego użytkownika.", - "account.followers_counter": "{count, plural, one {{counter} obserwujący} few {{counter} obserwujących} many {{counter} obserwujących} other {{counter} obserwujących}}", "account.following": "Obserwowani", - "account.following_counter": "{count, plural, one {{counter} obserwowany} few {{counter} obserwowanych} many {{counter} obserwowanych} other {{counter} obserwowanych}}", "account.follows.empty": "Ten użytkownik nie obserwuje jeszcze nikogo.", "account.go_to_profile": "Przejdź do profilu", "account.hide_reblogs": "Ukryj podbicia od @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} chce zaobserwować twój profil", "account.share": "Udostępnij profil @{name}", "account.show_reblogs": "Pokazuj podbicia od @{name}", - "account.statuses_counter": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}}", "account.unblock": "Odblokuj @{name}", "account.unblock_domain": "Odblokuj domenę {domain}", "account.unblock_short": "Odblokuj", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 4d3bd2d28..34d0ba36e 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -35,9 +35,7 @@ "account.follow_back": "Seguir de volta", "account.followers": "Seguidores", "account.followers.empty": "Nada aqui.", - "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "Seguindo", - "account.following_counter": "{count, plural, one {segue {counter}} other {segue {counter}}}", "account.follows.empty": "Nada aqui.", "account.go_to_profile": "Ir ao perfil", "account.hide_reblogs": "Ocultar boosts de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} quer te seguir", "account.share": "Compartilhar perfil de @{name}", "account.show_reblogs": "Mostrar boosts de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear domínio {domain}", "account.unblock_short": "Desbloquear", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 9446d5ee2..41112c2ca 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -35,9 +35,7 @@ "account.follow_back": "Seguir de volta", "account.followers": "Seguidores", "account.followers.empty": "Ainda ninguém segue este utilizador.", - "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "A seguir", - "account.following_counter": "{count, plural, other {A seguir {counter}}}", "account.follows.empty": "Este utilizador ainda não segue ninguém.", "account.go_to_profile": "Ir para o perfil", "account.hide_reblogs": "Esconder partilhas de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} pediu para segui-lo", "account.share": "Partilhar o perfil @{name}", "account.show_reblogs": "Mostrar partilhas de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear o domínio {domain}", "account.unblock_short": "Desbloquear", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index 3a2fab905..35abf1b02 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -35,9 +35,7 @@ "account.follow_back": "Urmăreşte înapoi", "account.followers": "Urmăritori", "account.followers.empty": "Acest utilizator nu are încă urmăritori.", - "account.followers_counter": "{count, plural, one {Un abonat} few {{counter} abonați} other {{counter} de abonați}}", "account.following": "Urmăriți", - "account.following_counter": "{count, plural, one {Un abonament} few {{counter} abonamente} other {{counter} de abonamente}}", "account.follows.empty": "Momentan acest utilizator nu are niciun abonament.", "account.go_to_profile": "Mergi la profil", "account.hide_reblogs": "Ascunde distribuirile de la @{name}", @@ -62,7 +60,6 @@ "account.requested_follow": "{name} A cerut să vă urmărească", "account.share": "Distribuie profilul lui @{name}", "account.show_reblogs": "Afișează distribuirile de la @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "Deblochează pe @{name}", "account.unblock_domain": "Deblochează domeniul {domain}", "account.unblock_short": "Deblochează", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 40ca84814..97a1f0b09 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -35,9 +35,7 @@ "account.follow_back": "Подписаться в ответ", "account.followers": "Подписчики", "account.followers.empty": "На этого пользователя пока никто не подписан.", - "account.followers_counter": "{count, plural, one {{counter} подписчик} many {{counter} подписчиков} other {{counter} подписчика}}", "account.following": "Подписки", - "account.following_counter": "{count, plural, one {{counter} подписка} many {{counter} подписок} other {{counter} подписки}}", "account.follows.empty": "Этот пользователь пока ни на кого не подписался.", "account.go_to_profile": "Перейти к профилю", "account.hide_reblogs": "Скрыть продвижения от @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} отправил(а) вам запрос на подписку", "account.share": "Поделиться профилем @{name}", "account.show_reblogs": "Показывать продвижения от @{name}", - "account.statuses_counter": "{count, plural, one {{counter} пост} many {{counter} постов} other {{counter} поста}}", "account.unblock": "Разблокировать @{name}", "account.unblock_domain": "Разблокировать {domain}", "account.unblock_short": "Разблокировать", diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json index 58654deb0..c3880a6b0 100644 --- a/app/javascript/mastodon/locales/sa.json +++ b/app/javascript/mastodon/locales/sa.json @@ -32,9 +32,7 @@ "account.follow": "अनुस्रियताम्", "account.followers": "अनुसर्तारः", "account.followers.empty": "नाऽनुसर्तारो वर्तन्ते", - "account.followers_counter": "{count, plural, one {{counter} अनुसर्ता} two {{counter} अनुसर्तारौ} other {{counter} अनुसर्तारः}}", "account.following": "अनुसरति", - "account.following_counter": "{count, plural, one {{counter} अनुसृतः} two {{counter} अनुसृतौ} other {{counter} अनुसृताः}}", "account.follows.empty": "न कोऽप्यनुसृतो वर्तते", "account.go_to_profile": "प्रोफायिलं गच्छ", "account.hide_reblogs": "@{name} मित्रस्य प्रकाशनानि छिद्यन्ताम्", @@ -56,7 +54,6 @@ "account.requested_follow": "{name} त्वामनुसर्तुमयाचीत्", "account.share": "@{name} मित्रस्य विवरणं विभाज्यताम्", "account.show_reblogs": "@{name} मित्रस्य प्रकाशनानि दृश्यन्ताम्", - "account.statuses_counter": "{count, plural, one {{counter} पत्रम्} two{{counter} पत्रे} other {{counter} पत्राणि}}", "account.unblock": "निषेधता नश्यताम् @{name}", "account.unblock_domain": "प्रदेशनिषेधता नश्यताम् {domain}", "account.unblock_short": "अनवरुन्धि", diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json index a0b5b3271..895557373 100644 --- a/app/javascript/mastodon/locales/sc.json +++ b/app/javascript/mastodon/locales/sc.json @@ -26,9 +26,7 @@ "account.follow": "Sighi", "account.followers": "Sighiduras", "account.followers.empty": "Nemos sighit ancora custa persone.", - "account.followers_counter": "{count, plural, one {{counter} sighidura} other {{counter} sighiduras}}", "account.following": "Sighende", - "account.following_counter": "{count, plural, one {Sighende a {counter}} other {Sighende a {counter}}}", "account.follows.empty": "Custa persone non sighit ancora a nemos.", "account.hide_reblogs": "Cua is cumpartziduras de @{name}", "account.in_memoriam": "In memoriam.", @@ -47,7 +45,6 @@ "account.requested_follow": "{name} at dimandadu de ti sighire", "account.share": "Cumpartzi su profilu de @{name}", "account.show_reblogs": "Ammustra is cumpartziduras de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}}", "account.unblock": "Isbloca a @{name}", "account.unblock_domain": "Isbloca su domìniu {domain}", "account.unendorse": "Non cussiges in su profilu", diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json index 53501a593..397f63fed 100644 --- a/app/javascript/mastodon/locales/sco.json +++ b/app/javascript/mastodon/locales/sco.json @@ -31,9 +31,7 @@ "account.follow": "Follae", "account.followers": "Follaers", "account.followers.empty": "Naebody follaes this uiser yit.", - "account.followers_counter": "{count, plural, one {{counter} Follaer} other {{counter} Follaers}}", "account.following": "Follaein", - "account.following_counter": "{count, plural, one {{counter} Follaein} other {{counter} Follaein}}", "account.follows.empty": "This uiser disnae follae oniebody yit.", "account.go_to_profile": "Gang tae profile", "account.hide_reblogs": "Dinnae shaw heezes fae @{name}", @@ -53,7 +51,6 @@ "account.requested": "Haudin fir approval. Chap tae cancel follae request", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Shaw heezes frae @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}", "account.unblock": "Undingie @{name}", "account.unblock_domain": "Undingie domain {domain}", "account.unblock_short": "Undingie", diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index 22320daef..fbfdfaa65 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -23,9 +23,7 @@ "account.follow": "අනුගමනය", "account.followers": "අනුගාමිකයින්", "account.followers.empty": "කිසිවෙක් අනුගමනය කර නැත.", - "account.followers_counter": "{count, plural, one {අනුගාමිකයින් {counter}} other {අනුගාමිකයින් {counter}}}", "account.following": "අනුගමන", - "account.following_counter": "{count, plural, one {අනුගමන {counter}} other {අනුගමන {counter}}}", "account.follows.empty": "තවමත් කිසිවෙක් අනුගමනය නොකරයි.", "account.go_to_profile": "පැතිකඩට යන්න", "account.joined_short": "එක් වූ දිනය", @@ -39,7 +37,6 @@ "account.posts_with_replies": "ලිපි සහ පිළිතුරු", "account.report": "@{name} වාර්තා කරන්න", "account.share": "@{name} ගේ පැතිකඩ බෙදාගන්න", - "account.statuses_counter": "{count, plural, one {ලිපි {counter}} other {ලිපි {counter}}}", "account.unblock": "@{name} අනවහිර කරන්න", "account.unblock_domain": "{domain} වසම අනවහිර කරන්න", "account.unblock_short": "අනවහිර", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 4c152a214..ed9c0de60 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -35,9 +35,7 @@ "account.follow_back": "Sledovať späť", "account.followers": "Sledovatelia", "account.followers.empty": "Tento účet ešte nikto nesleduje.", - "account.followers_counter": "{count, plural, one {{counter} sledujúci účet} few {{counter} sledujúce účty} many {{counter} sledujúcich účtov} other {{counter} sledujúcich účtov}}", "account.following": "Sledovaný účet", - "account.following_counter": "{count, plural, one {{counter} sledovaný účet} few {{counter} sledované účty} many {{counter} sledovaných účtov} other {{counter} sledovaných účtov}}", "account.follows.empty": "Tento účet ešte nikoho nesleduje.", "account.go_to_profile": "Prejsť na profil", "account.hide_reblogs": "Skryť zdieľania od @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} vás chce sledovať", "account.share": "Zdieľaj profil @{name}", "account.show_reblogs": "Zobrazovať zdieľania od @{name}", - "account.statuses_counter": "{count, plural, one {{counter} príspevok} few {{counter} príspevky} many {{counter} príspevkov} other {{counter} príspevkov}}", "account.unblock": "Odblokovať @{name}", "account.unblock_domain": "Odblokovať doménu {domain}", "account.unblock_short": "Odblokovať", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 195797143..2a3d74a80 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -35,9 +35,9 @@ "account.follow_back": "Sledi nazaj", "account.followers": "Sledilci", "account.followers.empty": "Nihče ne sledi temu uporabniku.", - "account.followers_counter": "{count, plural, one {ima {counter} sledilca} two {ima {counter} sledilca} few {ima {counter} sledilce} other {ima {counter} sledilcev}}", + "account.followers_counter": "{count, plural, one {{counter} sledilec} two {{counter} sledilca} few {{counter} sledilci} other {{counter} sledilcev}}", "account.following": "Sledim", - "account.following_counter": "{count, plural, one {sledi {count} osebi} two {sledi {count} osebama} few {sledi {count} osebam} other {sledi {count} osebam}}", + "account.following_counter": "{count, plural, one {{counter} sleden} two {{counter} sledena} few {{counter} sledeni} other {{counter} sledenih}}", "account.follows.empty": "Ta uporabnik še ne sledi nikomur.", "account.go_to_profile": "Pojdi na profil", "account.hide_reblogs": "Skrij izpostavitve od @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} vam želi slediti", "account.share": "Deli profil osebe @{name}", "account.show_reblogs": "Pokaži izpostavitve osebe @{name}", - "account.statuses_counter": "{count, plural, one {{count} objava} two {{count} objavi} few {{count} objave} other {{count} objav}}", + "account.statuses_counter": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objave} other {{counter} objav}}", "account.unblock": "Odblokiraj @{name}", "account.unblock_domain": "Odblokiraj domeno {domain}", "account.unblock_short": "Odblokiraj", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 6903bceff..6ac038c9f 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -35,9 +35,7 @@ "account.follow_back": "Ndiqe gjithashtu", "account.followers": "Ndjekës", "account.followers.empty": "Këtë përdorues ende s’e ndjek kush.", - "account.followers_counter": "{count, plural, one {{counter} Ndjekës} other {{counter} Ndjekës}}", "account.following": "Ndjekje", - "account.following_counter": "{count, plural, one {{counter} i Ndjekur} other {{counter} të Ndjekur}}", "account.follows.empty": "Ky përdorues ende s’ndjek kënd.", "account.go_to_profile": "Kalo te profili", "account.hide_reblogs": "Fshih përforcime nga @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ka kërkuar t’ju ndjekë", "account.share": "Ndajeni profilin e @{name} me të tjerët", "account.show_reblogs": "Shfaq përforcime nga @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Mesazh} other {{counter} Mesazhe}}", "account.unblock": "Zhbllokoje @{name}", "account.unblock_domain": "Zhblloko përkatësinë {domain}", "account.unblock_short": "Zhbllokoje", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 63b2e03c9..93c3b8fe2 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -35,9 +35,7 @@ "account.follow_back": "Uzvrati praćenje", "account.followers": "Pratioci", "account.followers.empty": "Još uvek niko ne prati ovog korisnika.", - "account.followers_counter": "{count, plural, one {{counter} pratilac} few {{counter} pratioca} other {{counter} pratilaca}}", "account.following": "Prati", - "account.following_counter": "{count, plural, one {{counter} prati} few {{counter} prati} other {{counter} prati}}", "account.follows.empty": "Ovaj korisnik još uvek nikog ne prati.", "account.go_to_profile": "Idi na profil", "account.hide_reblogs": "Sakrij podržavanja @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} je zatražio da vas prati", "account.share": "Podeli profil korisnika @{name}", "account.show_reblogs": "Prikaži podržavanja od korisnika @{name}", - "account.statuses_counter": "{count, plural, one {{counter} objavio} few {{counter} objavio} other {{counter} objavio}}", "account.unblock": "Odblokiraj korisnika @{name}", "account.unblock_domain": "Odblokiraj domen {domain}", "account.unblock_short": "Odblokiraj", @@ -696,8 +693,11 @@ "server_banner.about_active_users": "Ljudi koji su koristili ovaj server u prethodnih 30 dana (mesečno aktivnih korisnika)", "server_banner.active_users": "aktivnih korisnika", "server_banner.administered_by": "Administrira:", + "server_banner.is_one_of_many": "{domain} je jedan od mnogih nezavisnih Mastodon servera koje možete koristiti za učešće u fediverzumu.", "server_banner.server_stats": "Statistike servera:", "sign_in_banner.create_account": "Napravite nalog", + "sign_in_banner.follow_anyone": "Pratite bilo koga širom fediverzuma i pogledajte sve hronološkim redom. Nema algoritama, reklama ili mamaca za klikove na vidiku.", + "sign_in_banner.mastodon_is": "Mastodon je najbolji način da budete u toku sa onim što se dešava.", "sign_in_banner.sign_in": "Prijavite se", "sign_in_banner.sso_redirect": "Prijavite se ili se registrujte", "status.admin_account": "Otvori moderatorsko okruženje za @{name}", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index c6b969e98..0273002b3 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -35,9 +35,7 @@ "account.follow_back": "Узврати праћење", "account.followers": "Пратиоци", "account.followers.empty": "Још увек нико не прати овог корисника.", - "account.followers_counter": "{count, plural, one {{counter} пратилац} few {{counter} пратиоца} other {{counter} пратилаца}}", "account.following": "Прати", - "account.following_counter": "{count, plural, one {{counter} прати} few {{counter} прати} other {{counter} прати}}", "account.follows.empty": "Овај корисник још увек никог не прати.", "account.go_to_profile": "Иди на профил", "account.hide_reblogs": "Сакриј подржавања од @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} је затражио да вас прати", "account.share": "Подели профил корисника @{name}", "account.show_reblogs": "Прикажи подржавања од корисника @{name}", - "account.statuses_counter": "{count, plural, one {{counter} објавио} few {{counter} објавио} other {{counter} објавио}}", "account.unblock": "Одблокирај корисника @{name}", "account.unblock_domain": "Одблокирај домен {domain}", "account.unblock_short": "Одблокирај", @@ -696,8 +693,11 @@ "server_banner.about_active_users": "Људи који су користили овај сервер у претходних 30 дана (месечно активних корисника)", "server_banner.active_users": "активних корисника", "server_banner.administered_by": "Администрира:", + "server_banner.is_one_of_many": "{domain} је један од многих независних Mastodon сервера које можете користити за учешће у федиверзуму.", "server_banner.server_stats": "Статистике сервера:", "sign_in_banner.create_account": "Направите налог", + "sign_in_banner.follow_anyone": "Пратите било кога широм федиверзума и погледајте све хронолошким редом. Нема алгоритама, реклама или мамаца за кликове на видику.", + "sign_in_banner.mastodon_is": "Mastodon је најбољи начин да будете у току са оним што се дешава.", "sign_in_banner.sign_in": "Пријавите се", "sign_in_banner.sso_redirect": "Пријавите се или се региструјте", "status.admin_account": "Отвори модераторско окружење за @{name}", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index ced6c3605..7445b77ba 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -35,9 +35,7 @@ "account.follow_back": "Följ tillbaka", "account.followers": "Följare", "account.followers.empty": "Ingen följer denna användare än.", - "account.followers_counter": "{count, plural, one {{counter} följare} other {{counter} följare}}", "account.following": "Följer", - "account.following_counter": "{count, plural, one {{counter} följd} other {{counter} följda}}", "account.follows.empty": "Denna användare följer inte någon än.", "account.go_to_profile": "Gå till profilen", "account.hide_reblogs": "Dölj boostar från @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} har begärt att följa dig", "account.share": "Dela @{name}s profil", "account.show_reblogs": "Visa boostar från @{name}", - "account.statuses_counter": "{count, plural, one {{counter} inlägg} other {{counter} inlägg}}", "account.unblock": "Avblockera @{name}", "account.unblock_domain": "Avblockera {domain}", "account.unblock_short": "Avblockera", diff --git a/app/javascript/mastodon/locales/szl.json b/app/javascript/mastodon/locales/szl.json index 43cfc78d5..34d086eb4 100644 --- a/app/javascript/mastodon/locales/szl.json +++ b/app/javascript/mastodon/locales/szl.json @@ -23,7 +23,6 @@ "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", "account.requested": "Awaiting approval", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account_note.placeholder": "Click to add a note", "column.pins": "Pinned toot", "community.column_settings.media_only": "Media only", diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json index ac0984293..d44ac424f 100644 --- a/app/javascript/mastodon/locales/ta.json +++ b/app/javascript/mastodon/locales/ta.json @@ -24,9 +24,7 @@ "account.follow_back": "பின்தொடரு", "account.followers": "பின்தொடர்பவர்கள்", "account.followers.empty": "இதுவரை யாரும் இந்த பயனரைப் பின்தொடரவில்லை.", - "account.followers_counter": "{count, plural, one {{counter} வாசகர்} other {{counter} வாசகர்கள்}}", "account.following": "பின்தொடரும்", - "account.following_counter": "{count, plural,one {{counter} சந்தா} other {{counter} சந்தாக்கள்}}", "account.follows.empty": "இந்த பயனர் இதுவரை யாரையும் பின்தொடரவில்லை.", "account.go_to_profile": "சுயவிவரத்திற்குச் செல்லவும்", "account.hide_reblogs": "இருந்து ஊக்கியாக மறை @{name}", @@ -45,7 +43,6 @@ "account.requested": "ஒப்புதலுக்காகக் காத்திருக்கிறது. பின்தொடரும் கோரிக்கையை நீக்க அழுத்தவும்", "account.share": "@{name} உடைய விவரத்தை பகிர்", "account.show_reblogs": "காட்டு boosts இருந்து @{name}", - "account.statuses_counter": "{count, plural, one {{counter} டூட்} other {{counter} டூட்டுகள்}}", "account.unblock": "@{name} மீது தடை நீக்குக", "account.unblock_domain": "{domain} ஐ காண்பி", "account.unblock_short": "தடையை நீக்கு", diff --git a/app/javascript/mastodon/locales/tai.json b/app/javascript/mastodon/locales/tai.json index 825cfb93b..cad6e8eaa 100644 --- a/app/javascript/mastodon/locales/tai.json +++ b/app/javascript/mastodon/locales/tai.json @@ -9,7 +9,6 @@ "account.posts": "Huah-siann", "account.posts_with_replies": "Huah-siann kah huê-ìng", "account.requested": "Tán-thāi phue-tsún", - "account.statuses_counter": "{count, plural, one {{counter} Huah-siann} other {{counter} Huah-siann}}", "account_note.placeholder": "Tiám tsi̍t-ē ka-thiam pī-tsù", "column.pins": "Tah thâu-tsîng ê huah-siann", "community.column_settings.media_only": "Kan-na muî-thé", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json index 284102c38..c06472561 100644 --- a/app/javascript/mastodon/locales/te.json +++ b/app/javascript/mastodon/locales/te.json @@ -25,7 +25,6 @@ "account.requested": "ఆమోదం కోసం వేచి ఉంది. అభ్యర్థనను రద్దు చేయడానికి క్లిక్ చేయండి", "account.share": "@{name} యొక్క ప్రొఫైల్ను పంచుకోండి", "account.show_reblogs": "@{name}నుంచి బూస్ట్ లను చూపించు", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name}పై బ్లాక్ ను తొలగించు", "account.unblock_domain": "{domain}ను దాచవద్దు", "account.unendorse": "ప్రొఫైల్లో చూపించవద్దు", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 64abb394b..e1d556ebf 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -35,9 +35,7 @@ "account.follow_back": "ติดตามกลับ", "account.followers": "ผู้ติดตาม", "account.followers.empty": "ยังไม่มีใครติดตามผู้ใช้นี้", - "account.followers_counter": "{count, plural, other {{counter} ผู้ติดตาม}}", "account.following": "กำลังติดตาม", - "account.following_counter": "{count, plural, other {{counter} กำลังติดตาม}}", "account.follows.empty": "ผู้ใช้นี้ยังไม่ได้ติดตามใคร", "account.go_to_profile": "ไปยังโปรไฟล์", "account.hide_reblogs": "ซ่อนการดันจาก @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ได้ขอติดตามคุณ", "account.share": "แชร์โปรไฟล์ของ @{name}", "account.show_reblogs": "แสดงการดันจาก @{name}", - "account.statuses_counter": "{count, plural, other {{counter} โพสต์}}", "account.unblock": "เลิกปิดกั้น @{name}", "account.unblock_domain": "เลิกปิดกั้นโดเมน {domain}", "account.unblock_short": "เลิกปิดกั้น", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 0bb2a0e4a..ac39a3fd7 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -35,9 +35,9 @@ "account.follow_back": "Geri takip et", "account.followers": "Takipçi", "account.followers.empty": "Henüz kimse bu kullanıcıyı takip etmiyor.", - "account.followers_counter": "{count, plural, one {{counter} Takipçi} other {{counter} Takipçi}}", + "account.followers_counter": "{count, plural, one {{counter} takipçi} other {{counter} takipçi}}", "account.following": "Takip Ediliyor", - "account.following_counter": "{count, plural, one {{counter} Takip Edilen} other {{counter} Takip Edilen}}", + "account.following_counter": "{count, plural, one {{counter} takip edilen} other {{counter} takip edilen}}", "account.follows.empty": "Bu kullanıcı henüz kimseyi takip etmiyor.", "account.go_to_profile": "Profile git", "account.hide_reblogs": "@{name} kişisinin boostlarını gizle", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} size takip isteği gönderdi", "account.share": "@{name} adlı kişinin profilini paylaş", "account.show_reblogs": "@{name} kişisinin yeniden paylaşımlarını göster", - "account.statuses_counter": "{count, plural, one {{counter} Gönderi} other {{counter} Gönderi}}", + "account.statuses_counter": "{count, plural, one {{counter} gönderi} other {{counter} gönderi}}", "account.unblock": "@{name} adlı kişinin engelini kaldır", "account.unblock_domain": "{domain} alan adının engelini kaldır", "account.unblock_short": "Engeli kaldır", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 273c1a6de..baba3190d 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -31,9 +31,7 @@ "account.follow": "Язылу", "account.followers": "Язылучы", "account.followers.empty": "Әле беркем дә язылмаган.", - "account.followers_counter": "{count, plural,one {{counter} язылучы} other {{counter} язылучы}}", "account.following": "Язылулар", - "account.following_counter": "{count, plural, one {{counter} язылу} other {{counter} язылу}}", "account.follows.empty": "Беркемгә дә язылмаган әле.", "account.go_to_profile": "Профильгә күчү", "account.hide_reblogs": "Скрывать көчен нче @{name}", @@ -55,7 +53,6 @@ "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": "Бикләүне чыгу", diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json index 4120d4483..e3dd0e6b1 100644 --- a/app/javascript/mastodon/locales/ug.json +++ b/app/javascript/mastodon/locales/ug.json @@ -6,7 +6,6 @@ "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", "account.requested": "Awaiting approval", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account_note.placeholder": "Click to add a note", "column.pins": "Pinned toot", "community.column_settings.media_only": "Media only", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 22cd15bd2..338b65061 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -35,9 +35,7 @@ "account.follow_back": "Підписатися взаємно", "account.followers": "Підписники", "account.followers.empty": "Ніхто ще не підписаний на цього користувача.", - "account.followers_counter": "{count, plural, one {{counter} підписник} few {{counter} підписники} many {{counter} підписників} other {{counter} підписники}}", "account.following": "Ви стежите", - "account.following_counter": "{count, plural, one {{counter} підписка} few {{counter} підписки} many {{counter} підписок} other {{counter} підписки}}", "account.follows.empty": "Цей користувач ще ні на кого не підписався.", "account.go_to_profile": "Перейти до профілю", "account.hide_reblogs": "Сховати поширення від @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} надсилає запит на стеження", "account.share": "Поділитися профілем @{name}", "account.show_reblogs": "Показати поширення від @{name}", - "account.statuses_counter": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} дописи}}", "account.unblock": "Розблокувати @{name}", "account.unblock_domain": "Розблокувати {domain}", "account.unblock_short": "Розблокувати", diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json index 1b9f8d969..cf53eb6fe 100644 --- a/app/javascript/mastodon/locales/ur.json +++ b/app/javascript/mastodon/locales/ur.json @@ -29,9 +29,7 @@ "account.follow_back": "اکاؤنٹ کو فالو بیک ", "account.followers": "پیروکار", "account.followers.empty": "ہنوز اس صارف کی کوئی پیروی نہیں کرتا.", - "account.followers_counter": "{count, plural,one {{counter} پیروکار} other {{counter} پیروکار}}", "account.following": "فالو کر رہے ہیں", - "account.following_counter": "{count, plural, one {{counter} پیروی کر رہے ہیں} other {{counter} پیروی کر رہے ہیں}}", "account.follows.empty": "\"یہ صارف ہنوز کسی کی پیروی نہیں کرتا ہے\".", "account.go_to_profile": "پروفائل پر جائیں", "account.hide_reblogs": "@{name} سے فروغ چھپائیں", @@ -57,7 +55,6 @@ "account.requested_follow": "{name} آپ کو فالو کرنا چھاتا ہے۔", "account.share": "@{name} کے مشخص کو بانٹیں", "account.show_reblogs": "@{name} کی افزائشات کو دکھائیں", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name} کو بحال کریں", "account.unblock_domain": "{domain} کو نہ چھپائیں", "account.unblock_short": "بلاک ختم کریں", diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json index 77892914a..4824b1d33 100644 --- a/app/javascript/mastodon/locales/uz.json +++ b/app/javascript/mastodon/locales/uz.json @@ -31,9 +31,7 @@ "account.follow": "Obuna bo‘lish", "account.followers": "Obunachilar", "account.followers.empty": "Bu foydalanuvchini hali hech kim kuzatmaydi.", - "account.followers_counter": "{count, plural, one {{counter} Muxlis} other {{counter} Muxlislar}}", "account.following": "Kuzatish", - "account.following_counter": "{count, plural, one {{counter} ga Muxlis} other {{counter} larga muxlis}}", "account.follows.empty": "Bu foydalanuvchi hali hech kimni kuzatmagan.", "account.go_to_profile": "Profilga o'tish", "account.hide_reblogs": "@{name} dan boostlarni yashirish", @@ -54,7 +52,6 @@ "account.requested_follow": "{name} sizni kuzatishni soʻradi", "account.share": "@{name} profilini ulashing", "account.show_reblogs": "@{name} dan bootlarni ko'rsatish", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Postlar}}", "account.unblock": "@{name} ni blokdan chiqarish", "account.unblock_domain": "{domain} domenini blokdan chiqarish", "account.unblock_short": "Blokdan chiqarish", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 18f0fec3c..bbfecf2c8 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -35,9 +35,7 @@ "account.follow_back": "Theo dõi lại", "account.followers": "Người theo dõi", "account.followers.empty": "Chưa có người theo dõi nào.", - "account.followers_counter": "{count, plural, one {{counter} Người theo dõi} other {{counter} Người theo dõi}}", "account.following": "Đang theo dõi", - "account.following_counter": "{count, plural, one {{counter} Theo dõi} other {{counter} Theo dõi}}", "account.follows.empty": "Người này chưa theo dõi ai.", "account.go_to_profile": "Xem hồ sơ", "account.hide_reblogs": "Ẩn tút @{name} đăng lại", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} yêu cầu theo dõi bạn", "account.share": "Chia sẻ @{name}", "account.show_reblogs": "Hiện tút do @{name} đăng lại", - "account.statuses_counter": "{count, plural, one {{counter} Tút} other {{counter} Tút}}", "account.unblock": "Bỏ chặn @{name}", "account.unblock_domain": "Bỏ ẩn {domain}", "account.unblock_short": "Bỏ chặn", diff --git a/app/javascript/mastodon/locales/zgh.json b/app/javascript/mastodon/locales/zgh.json index 1d3a22108..b42bb7589 100644 --- a/app/javascript/mastodon/locales/zgh.json +++ b/app/javascript/mastodon/locales/zgh.json @@ -19,7 +19,6 @@ "account.posts_with_replies": "Toots and replies", "account.requested": "Awaiting approval", "account.share": "ⴱⴹⵓ ⵉⴼⵔⵙ ⵏ @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unfollow": "ⴽⴽⵙ ⴰⴹⴼⴼⵓⵕ", "account_note.placeholder": "Click to add a note", "bundle_column_error.retry": "ⴰⵍⵙ ⴰⵔⵎ", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 3456f99d2..f2accae0d 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -35,9 +35,9 @@ "account.follow_back": "回关", "account.followers": "关注者", "account.followers.empty": "目前无人关注此用户。", - "account.followers_counter": "被 {counter} 人关注", + "account.followers_counter": "{count, plural, other {{counter} 关注者}}", "account.following": "正在关注", - "account.following_counter": "正在关注 {counter} 人", + "account.following_counter": "{count, plural, other {{counter} 关注}}", "account.follows.empty": "此用户目前未关注任何人。", "account.go_to_profile": "前往个人资料页", "account.hide_reblogs": "隐藏来自 @{name} 的转嘟", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} 已经向你发送了关注请求", "account.share": "分享 @{name} 的个人资料页", "account.show_reblogs": "显示来自 @{name} 的转嘟", - "account.statuses_counter": "{counter} 条嘟文", + "account.statuses_counter": "{count, plural, other {{counter} 嘟文}}", "account.unblock": "取消屏蔽 @{name}", "account.unblock_domain": "取消屏蔽 {domain} 域名", "account.unblock_short": "取消屏蔽", @@ -699,6 +699,7 @@ "server_banner.is_one_of_many": "{domain} 是可用于参与联邦宇宙的众多独立 Mastodon 服务器之一。", "server_banner.server_stats": "服务器统计数据:", "sign_in_banner.create_account": "创建账户", + "sign_in_banner.follow_anyone": "关注联邦宇宙中的任何人,并按时间顺序查看所有内容。没有算法、广告或诱导链接。", "sign_in_banner.mastodon_is": "Mastodon 是了解最新动态的最佳途径。", "sign_in_banner.sign_in": "登录", "sign_in_banner.sso_redirect": "登录或注册", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 5dff46620..09a497e88 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -35,9 +35,7 @@ "account.follow_back": "追蹤對方", "account.followers": "追蹤者", "account.followers.empty": "尚未有人追蹤這位使用者。", - "account.followers_counter": "有 {count, plural,one {{counter} 個} other {{counter} 個}}追蹤者", "account.following": "正在追蹤", - "account.following_counter": "正在追蹤 {count, plural,one {{counter}}other {{counter} 人}}", "account.follows.empty": "這位使用者尚未追蹤任何人。", "account.go_to_profile": "前往個人檔案", "account.hide_reblogs": "隱藏 @{name} 的轉推", @@ -63,7 +61,6 @@ "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": "解除封鎖", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 4ab22daba..04469a971 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -35,9 +35,9 @@ "account.follow_back": "跟隨回去", "account.followers": "跟隨者", "account.followers.empty": "尚未有人跟隨這位使用者。", - "account.followers_counter": "被 {count, plural, other {{counter} 人}}跟隨", + "account.followers_counter": "被 {count, plural, other {{count} 人}}跟隨", "account.following": "跟隨中", - "account.following_counter": "正在跟隨 {count,plural,other {{counter} 人}}", + "account.following_counter": "正在跟隨 {count,plural,other {{count} 人}}", "account.follows.empty": "這位使用者尚未跟隨任何人。", "account.go_to_profile": "前往個人檔案", "account.hide_reblogs": "隱藏來自 @{name} 的轉嘟", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} 要求跟隨您", "account.share": "分享 @{name} 的個人檔案", "account.show_reblogs": "顯示來自 @{name} 的轉嘟", - "account.statuses_counter": "{count, plural,one {{counter} 則}other {{counter} 則}}嘟文", + "account.statuses_counter": "{count, plural, other {{count} 則嘟文}}", "account.unblock": "解除封鎖 @{name}", "account.unblock_domain": "解除封鎖網域 {domain}", "account.unblock_short": "解除封鎖", diff --git a/config/locales/fi.yml b/config/locales/fi.yml index f108718e5..be87258da 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -293,7 +293,7 @@ fi: filter_by_action: Suodata tapahtuman mukaan filter_by_user: Suodata käyttäjän mukaan title: Auditointiloki - unavailable_instance: "(verkkotunnus ei ole saatavilla)" + unavailable_instance: "(verkkotunnus ei saatavilla)" announcements: destroyed_msg: Tiedote poistettu onnistuneesti! edit: diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 776e473ee..f6e6d4d2a 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -577,7 +577,7 @@ sr-Latn: relays: add_new: Dodaj novi relej delete: Obriši - description_html: "<strong>Federalni relej</strong> je posrednički server koji razmenjuje velike količine javnih truba između servera na koji je pretplaćen i na koji objavljuje.<strong>Može pomoći malim i srednjim serverima da otkriju sadržaj iz fediversa</strong>, koji inače zahteva od lokalnih korisnika da ručno pratiti ostale ljude na udaljenim serverima." + description_html: "<strong>Federalni relej</strong> je posrednički server koji razmenjuje velike količine javnih objava između servera na koji je pretplaćen i na koji objavljuje.<strong>Može pomoći malim i srednjim serverima da otkriju sadržaj iz fediverzuma</strong>, koji inače zahteva od lokalnih korisnika da ručno pratiti ostale ljude na udaljenim serverima." disable: Isključi disabled: Isključen enable: Uključi diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 365b358d5..9bfefde83 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -577,7 +577,7 @@ sr: relays: add_new: Додај нови релеј delete: Обриши - description_html: "<strong>Федерални релеј</strong> је посреднички сервер који размењује велике количине јавних труба између сервера на који је претплаћен и на који објављује.<strong>Може помоћи малим и средњим серверима да открију садржај из федиверса</strong>, који иначе захтева од локалних корисника да ручно пратити остале људе на удаљеним серверима." + description_html: "<strong>Федерални релеј</strong> је посреднички сервер који размењује велике количине јавних објава између сервера на који је претплаћен и на који објављује.<strong>Може помоћи малим и средњим серверима да открију садржај из федиверзума</strong>, који иначе захтева од локалних корисника да ручно пратити остале људе на удаљеним серверима." disable: Искључи disabled: Искључен enable: Укључи diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 93de27c0b..1317d5f70 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -525,7 +525,7 @@ zh-TW: total_followed_by_us: 被我們跟隨 total_reported: 關於他們的檢舉報告 total_storage: 多媒體附加檔案 - totals_time_period_hint_html: 以下顯示之總和包含所有時間的資料。 + totals_time_period_hint_html: 以下顯示之統計包含所有時間的資料。 unknown_instance: 此伺服器目前沒有這個網域的紀錄。 invites: deactivate_all: 全部停用 From 096057b845f27fd6090915b22c8688a6eeb22e28 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Thu, 27 Jun 2024 15:17:18 +0200 Subject: [PATCH 31/84] Change `author_account` to be `authors` in REST API (#30846) --- .../mastodon/actions/importer/index.js | 4 ++-- .../mastodon/actions/importer/normalizer.js | 11 +++++++++-- app/javascript/mastodon/actions/trends.js | 2 +- app/javascript/mastodon/api_types/statuses.ts | 7 +++++++ .../features/explore/components/author_link.jsx | 4 ++++ .../mastodon/features/explore/links.jsx | 2 +- .../mastodon/features/status/components/card.jsx | 4 ++-- app/models/preview_card.rb | 16 ++++++++++++++++ app/serializers/rest/preview_card_serializer.rb | 7 ++++++- 9 files changed, 48 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index d906bdfb1..516a7a797 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -76,8 +76,8 @@ export function importFetchedStatuses(statuses) { pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id]))); } - if (status.card?.author_account) { - pushUnique(accounts, status.card.author_account); + if (status.card) { + status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account)); } } diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index be76b0f39..c09a3f442 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -36,8 +36,15 @@ export function normalizeStatus(status, normalOldStatus) { normalStatus.poll = status.poll.id; } - if (status.card?.author_account) { - normalStatus.card = { ...status.card, author_account: status.card.author_account.id }; + if (status.card) { + normalStatus.card = { + ...status.card, + authors: status.card.authors.map(author => ({ + ...author, + accountId: author.account?.id, + account: undefined, + })), + }; } if (status.filtered) { diff --git a/app/javascript/mastodon/actions/trends.js b/app/javascript/mastodon/actions/trends.js index 01089fccb..0bdf17a5d 100644 --- a/app/javascript/mastodon/actions/trends.js +++ b/app/javascript/mastodon/actions/trends.js @@ -51,7 +51,7 @@ export const fetchTrendingLinks = () => (dispatch) => { api() .get('/api/v1/trends/links', { params: { limit: 20 } }) .then(({ data }) => { - dispatch(importFetchedAccounts(data.map(link => link.author_account).filter(account => !!account))); + dispatch(importFetchedAccounts(data.flatMap(link => link.authors.map(author => author.account)).filter(account => !!account))); dispatch(fetchTrendingLinksSuccess(data)); }) .catch(err => dispatch(fetchTrendingLinksFail(err))); diff --git a/app/javascript/mastodon/api_types/statuses.ts b/app/javascript/mastodon/api_types/statuses.ts index c7dd33b5d..db4e20506 100644 --- a/app/javascript/mastodon/api_types/statuses.ts +++ b/app/javascript/mastodon/api_types/statuses.ts @@ -30,6 +30,12 @@ export interface ApiMentionJSON { acct: string; } +export interface ApiPreviewCardAuthorJSON { + name: string; + url: string; + account?: ApiAccountJSON; +} + export interface ApiPreviewCardJSON { url: string; title: string; @@ -48,6 +54,7 @@ export interface ApiPreviewCardJSON { embed_url: string; blurhash: string; published_at: string; + authors: ApiPreviewCardAuthorJSON[]; } export interface ApiStatusJSON { diff --git a/app/javascript/mastodon/features/explore/components/author_link.jsx b/app/javascript/mastodon/features/explore/components/author_link.jsx index 8dd9b0dab..764ae7534 100644 --- a/app/javascript/mastodon/features/explore/components/author_link.jsx +++ b/app/javascript/mastodon/features/explore/components/author_link.jsx @@ -8,6 +8,10 @@ import { useAppSelector } from 'mastodon/store'; export const AuthorLink = ({ accountId }) => { const account = useAppSelector(state => state.getIn(['accounts', accountId])); + if (!account) { + return null; + } + return ( <Link to={`/@${account.get('acct')}`} className='story__details__shared__author-link' data-hover-card-account={accountId}> <Avatar account={account} size={16} /> diff --git a/app/javascript/mastodon/features/explore/links.jsx b/app/javascript/mastodon/features/explore/links.jsx index 93fd1fb6d..035e5aaad 100644 --- a/app/javascript/mastodon/features/explore/links.jsx +++ b/app/javascript/mastodon/features/explore/links.jsx @@ -75,7 +75,7 @@ class Links extends PureComponent { publisher={link.get('provider_name')} publishedAt={link.get('published_at')} author={link.get('author_name')} - authorAccount={link.getIn(['author_account', 'id'])} + authorAccount={link.getIn(['authors', 0, 'account', 'id'])} sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1} thumbnail={link.get('image')} thumbnailDescription={link.get('image_description')} diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index f562e53f0..f0ae40cbc 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -138,7 +138,7 @@ export default class Card extends PureComponent { const interactive = card.get('type') === 'video'; const language = card.get('language') || ''; const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive; - const showAuthor = !!card.get('author_account'); + const showAuthor = !!card.getIn(['authors', 0, 'accountId']); const description = ( <div className='status-card__content'> @@ -244,7 +244,7 @@ export default class Card extends PureComponent { {description} </a> - {showAuthor && <MoreFromAuthor accountId={card.get('author_account')} />} + {showAuthor && <MoreFromAuthor accountId={card.getIn(['authors', 0, 'accountId'])} />} </> ); } diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index cbfc39378..eac02ac14 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -128,6 +128,22 @@ class PreviewCard < ApplicationRecord @history ||= Trends::History.new('links', id) end + def authors + @authors ||= [PreviewCard::Author.new(self)] + end + + class Author < ActiveModelSerializers::Model + attributes :name, :url, :account + + def initialize(preview_card) + super( + name: preview_card.author_name, + url: preview_card.author_url, + account: preview_card.author_account, + ) + end + end + class << self private diff --git a/app/serializers/rest/preview_card_serializer.rb b/app/serializers/rest/preview_card_serializer.rb index 7d4c99c2d..f73a051ac 100644 --- a/app/serializers/rest/preview_card_serializer.rb +++ b/app/serializers/rest/preview_card_serializer.rb @@ -1,6 +1,11 @@ # frozen_string_literal: true class REST::PreviewCardSerializer < ActiveModel::Serializer + class AuthorSerializer < ActiveModel::Serializer + attributes :name, :url + has_one :account, serializer: REST::AccountSerializer + end + include RoutingHelper attributes :url, :title, :description, :language, :type, @@ -8,7 +13,7 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer :provider_url, :html, :width, :height, :image, :image_description, :embed_url, :blurhash, :published_at - has_one :author_account, serializer: REST::AccountSerializer, if: -> { object.author_account.present? } + has_many :authors, serializer: AuthorSerializer def url object.original_url.presence || object.url From 42adb6eaee3250e3557403ce52513e3d31b3ab80 Mon Sep 17 00:00:00 2001 From: David Roetzel <david@roetzel.de> Date: Thu, 27 Jun 2024 16:40:19 +0200 Subject: [PATCH 32/84] Add size limit for link preview URLs (#30854) --- app/services/fetch_link_card_service.rb | 5 ++++- spec/services/fetch_link_card_service_spec.rb | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 900cb9863..9692dd21b 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -15,6 +15,9 @@ class FetchLinkCardService < BaseService ) }iox + # URL size limit to safely store in PosgreSQL's unique indexes + BYTESIZE_LIMIT = 2692 + def call(status) @status = status @original_url = parse_urls @@ -85,7 +88,7 @@ class FetchLinkCardService < BaseService def bad_url?(uri) # Avoid local instance URLs and invalid URLs - uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme) + uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme) || uri.to_s.bytesize > BYTESIZE_LIMIT end def mention_link?(anchor) diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index 239f84fde..4f02aba54 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -193,6 +193,19 @@ RSpec.describe FetchLinkCardService do end end + context 'with an URL too long for PostgreSQL unique indexes' do + let(:url) { "http://example.com/#{'a' * 2674}" } + let(:status) { Fabricate(:status, text: url) } + + it 'does not fetch the URL' do + expect(a_request(:get, url)).to_not have_been_made + end + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end + context 'with a URL of a page with oEmbed support' do let(:html) { '<!doctype html><title>Hello world</title><link rel="alternate" type="application/json+oembed" href="http://example.com/oembed?url=http://example.com/html">' } let(:status) { Fabricate(:status, text: 'http://example.com/html') } From ff08d99d4dceebd720e16e4a40118cbd6941b0ca Mon Sep 17 00:00:00 2001 From: David Roetzel <david@roetzel.de> Date: Thu, 27 Jun 2024 16:41:03 +0200 Subject: [PATCH 33/84] Catch encoding errors when creating link previews. (#30853) --- app/services/fetch_link_card_service.rb | 2 +- spec/fixtures/requests/redirect_with_utf8_url.txt | 5 +++++ spec/services/fetch_link_card_service_spec.rb | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/requests/redirect_with_utf8_url.txt diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 9692dd21b..8bc9f912c 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -32,7 +32,7 @@ class FetchLinkCardService < BaseService end attach_card if @card&.persisted? - rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e + rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Encoding::UndefinedConversionError => e Rails.logger.debug { "Error fetching link #{@original_url}: #{e}" } nil end diff --git a/spec/fixtures/requests/redirect_with_utf8_url.txt b/spec/fixtures/requests/redirect_with_utf8_url.txt new file mode 100644 index 000000000..08f99ee2a --- /dev/null +++ b/spec/fixtures/requests/redirect_with_utf8_url.txt @@ -0,0 +1,5 @@ +HTTP/1.1 301 Moved Permanently +server: nginx +date: Thu, 27 Jun 2024 11:04:53 GMT +content-type: text/html; charset=UTF-8 +location: http://example.com/ärgerliche-umlaute.html diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index 4f02aba54..d83a52751 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -27,6 +27,7 @@ RSpec.describe FetchLinkCardService do stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt')) stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt')) stub_request(:get, 'http://example.com/low_confidence_latin1').to_return(request_fixture('low_confidence_latin1.txt')) + stub_request(:get, 'http://example.com/aergerliche-umlaute').to_return(request_fixture('redirect_with_utf8_url.txt')) Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache @@ -101,6 +102,14 @@ RSpec.describe FetchLinkCardService do end end + context 'with a redirect URL with faulty encoding' do + let(:status) { Fabricate(:status, text: 'http://example.com/aergerliche-umlaute') } + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end + context 'with a 404 URL' do let(:status) { Fabricate(:status, text: 'http://example.com/not-found') } From b15a3614dc7466e983e1394a12a16874a812e672 Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Thu, 27 Jun 2024 17:25:27 +0200 Subject: [PATCH 34/84] Stub `Vips::Error` when not using libvips (#30857) --- config/initializers/vips.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config/initializers/vips.rb b/config/initializers/vips.rb index 25a17b2a1..8d5d5cdc9 100644 --- a/config/initializers/vips.rb +++ b/config/initializers/vips.rb @@ -25,3 +25,11 @@ if Rails.configuration.x.use_vips Vips.block_untrusted(true) end + +# In some places of the code, we rescue this exception, but we don't always +# load libvips, so it may be an undefined constant: +unless defined?(Vips) + module Vips + class Error < StandardError; end + end +end From 836c0477ac63ea61b955bfb900cf52ec29554e40 Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Thu, 27 Jun 2024 12:03:26 -0400 Subject: [PATCH 35/84] Use vips setting instead of env var in media processing spec (#30859) --- spec/models/media_attachment_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index 221645ac5..a8f1ce774 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -210,10 +210,14 @@ RSpec.describe MediaAttachment, :paperclip_processing do expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102) expect(media.thumbnail.present?).to be true - # NOTE: Our libvips and ImageMagick implementations currently have different results - expect(media.file.meta['colors']['background']).to eq(ENV['MASTODON_USE_LIBVIPS'] ? '#268cd9' : '#3088d4') + expect(media.file.meta['colors']['background']).to eq(expected_background_color) expect(media.file_file_name).to_not eq 'boop.ogg' end + + def expected_background_color + # The libvips and ImageMagick implementations produce different results + Rails.configuration.x.use_vips ? '#268cd9' : '#3088d4' + end end describe 'mp3 with large cover art' do From 03bbb74b0ccc5f56bb3856efb1892ae8e0872a38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:27:32 +0000 Subject: [PATCH 36/84] fix(deps): update dependency prom-client to v15.1.3 (#30852) 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 dc73fb93b..f9eaef458 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14099,12 +14099,12 @@ __metadata: linkType: hard "prom-client@npm:^15.0.0": - version: 15.1.2 - resolution: "prom-client@npm:15.1.2" + version: 15.1.3 + resolution: "prom-client@npm:15.1.3" dependencies: "@opentelemetry/api": "npm:^1.4.0" tdigest: "npm:^0.1.1" - checksum: 10c0/a221db148fa64e29dfd4c6cdcaaae14635495a4272b68917e2b44fcfd988bc57027d275b04489ceeea4d0c4d64d058af842c1300966d2c1ffa255f1fa6af1277 + checksum: 10c0/816525572e5799a2d1d45af78512fb47d073c842dc899c446e94d17cfc343d04282a1627c488c7ca1bcd47f766446d3e49365ab7249f6d9c22c7664a5bce7021 languageName: node linkType: hard From bc3737f0c3c4cc0af500413db53fb5443ad02b69 Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Thu, 27 Jun 2024 12:27:42 -0400 Subject: [PATCH 37/84] Add detail about running version on vips error failure (#30858) --- config/initializers/vips.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/initializers/vips.rb b/config/initializers/vips.rb index 8d5d5cdc9..a539d7035 100644 --- a/config/initializers/vips.rb +++ b/config/initializers/vips.rb @@ -5,7 +5,11 @@ if Rails.configuration.x.use_vips require 'vips' - abort('Incompatible libvips version, please install libvips >= 8.13') unless Vips.at_least_libvips?(8, 13) + unless Vips.at_least_libvips?(8, 13) + abort <<~ERROR.squish + Incompatible libvips version (#{Vips.version_string}), please install libvips >= 8.13 + ERROR + end Vips.block('VipsForeign', true) From 3225954865c49ce7a336b73b2bc0c5ff2b707746 Mon Sep 17 00:00:00 2001 From: Michael Stanclift <mx@vmstan.com> Date: Thu, 27 Jun 2024 11:46:20 -0500 Subject: [PATCH 38/84] Fix browser window color on light theme (#30861) --- app/lib/themes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/themes.rb b/app/lib/themes.rb index b6da98073..183258d62 100644 --- a/app/lib/themes.rb +++ b/app/lib/themes.rb @@ -8,7 +8,7 @@ class Themes THEME_COLORS = { dark: '#191b22', - light: '#f3f5f7', + light: '#ffffff', }.freeze def initialize From 0f3fef6fda5819824457b9c1cdc41cb3d5ca976e Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Thu, 27 Jun 2024 23:34:34 +0200 Subject: [PATCH 39/84] Change search modifiers to be case-insensitive (#30865) --- app/lib/search_query_transformer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index 927495eac..606819ed4 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -225,7 +225,7 @@ class SearchQueryTransformer < Parslet::Transform end rule(clause: subtree(:clause)) do - prefix = clause[:prefix][:term].to_s if clause[:prefix] + prefix = clause[:prefix][:term].to_s.downcase if clause[:prefix] operator = clause[:operator]&.to_s term = clause[:phrase] ? clause[:phrase].map { |term| term[:term].to_s }.join(' ') : clause[:term].to_s From ea6c455e81f9f01a64adf123d0a4f820c6e67f97 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Fri, 28 Jun 2024 00:01:40 +0200 Subject: [PATCH 40/84] Fix follow button in hover cards not working when signed out in web UI (#30864) --- .../mastodon/components/follow_button.tsx | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index 4b4d27831..db5994288 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -2,11 +2,13 @@ import { useCallback, useEffect } from 'react'; import { useIntl, defineMessages } from 'react-intl'; +import { useIdentity } from '@/mastodon/identity_context'; import { fetchRelationships, followAccount, unfollowAccount, } from 'mastodon/actions/accounts'; +import { openModal } from 'mastodon/actions/modal'; import { Button } from 'mastodon/components/button'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { me } from 'mastodon/initial_state'; @@ -29,17 +31,37 @@ export const FollowButton: React.FC<{ }> = ({ accountId }) => { const intl = useIntl(); const dispatch = useAppDispatch(); + const { signedIn } = useIdentity(); + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); const relationship = useAppSelector((state) => state.relationships.get(accountId), ); const following = relationship?.following || relationship?.requested; useEffect(() => { - dispatch(fetchRelationships([accountId])); - }, [dispatch, accountId]); + if (accountId && signedIn) { + dispatch(fetchRelationships([accountId])); + } + }, [dispatch, accountId, signedIn]); const handleClick = useCallback(() => { + if (!signedIn) { + dispatch( + openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'follow', + accountId: accountId, + url: account?.url, + }, + }), + ); + } + if (!relationship) return; + if (accountId === me) { return; } else if (relationship.following || relationship.requested) { @@ -47,11 +69,13 @@ export const FollowButton: React.FC<{ } else { dispatch(followAccount(accountId)); } - }, [dispatch, accountId, relationship]); + }, [dispatch, accountId, relationship, account, signedIn]); let label; - if (accountId === me) { + if (!signedIn) { + label = intl.formatMessage(messages.follow); + } else if (accountId === me) { label = intl.formatMessage(messages.edit_profile); } else if (!relationship) { label = <LoadingIndicator />; From a5134f2695659ccb1c40b28b1f691e2dc932a5fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:33:57 +0200 Subject: [PATCH 41/84] New Crowdin Translations (automated) (#30867) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/de.json | 2 +- app/javascript/mastodon/locales/gl.json | 3 +++ app/javascript/mastodon/locales/hu.json | 3 +++ app/javascript/mastodon/locales/lt.json | 3 +++ app/javascript/mastodon/locales/nn.json | 6 ++++++ app/javascript/mastodon/locales/pl.json | 3 +++ app/javascript/mastodon/locales/pt-PT.json | 3 +++ app/javascript/mastodon/locales/sq.json | 3 +++ app/javascript/mastodon/locales/sv.json | 2 ++ config/locales/nn.yml | 1 + 10 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 86438757a..4a5b666d3 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -37,7 +37,7 @@ "account.followers.empty": "Diesem Profil folgt noch niemand.", "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}", "account.following": "Folge ich", - "account.following_counter": "{count, plural, one {{counter} Folge ich} other {{counter} Folge ich}}", + "account.following_counter": "{count, plural, one {{counter} folge ich} other {{counter} folge ich}}", "account.follows.empty": "Dieses Profil folgt noch niemandem.", "account.go_to_profile": "Profil aufrufen", "account.hide_reblogs": "Geteilte Beiträge von @{name} ausblenden", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 156fe3ee8..fae48ed06 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -35,7 +35,9 @@ "account.follow_back": "Seguir tamén", "account.followers": "Seguidoras", "account.followers.empty": "Aínda ninguén segue esta usuaria.", + "account.followers_counter": "{count, plural, one {{counter} seguidora} other {{counter} seguidoras}}", "account.following": "Seguindo", + "account.following_counter": "{count, plural, one {{counter} seguimento} other {{counter} seguimentos}}", "account.follows.empty": "Esta usuaria aínda non segue a ninguén.", "account.go_to_profile": "Ir ao perfil", "account.hide_reblogs": "Agochar promocións de @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} solicitou seguirte", "account.share": "Compartir o perfil de @{name}", "account.show_reblogs": "Amosar compartidos de @{name}", + "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Amosar {domain}", "account.unblock_short": "Desbloquear", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 627e3cab5..1fcadc8f9 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -35,7 +35,9 @@ "account.follow_back": "Viszontkövetés", "account.followers": "Követő", "account.followers.empty": "Ezt a felhasználót még senki sem követi.", + "account.followers_counter": "{count, plural, one {{counter} követő} other {{counter} követő}}", "account.following": "Követve", + "account.following_counter": "{count, plural, one {{counter} követett} other {{counter} követett}}", "account.follows.empty": "Ez a felhasználó még senkit sem követ.", "account.go_to_profile": "Ugrás a profilhoz", "account.hide_reblogs": "@{name} megtolásainak elrejtése", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} kérte, hogy követhessen", "account.share": "@{name} profiljának megosztása", "account.show_reblogs": "@{name} megtolásainak mutatása", + "account.statuses_counter": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}}", "account.unblock": "@{name} letiltásának feloldása", "account.unblock_domain": "{domain} domain tiltásának feloldása", "account.unblock_short": "Tiltás feloldása", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 6bf8f94bd..bb69b7339 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -35,7 +35,9 @@ "account.follow_back": "Sekti atgal", "account.followers": "Sekėjai", "account.followers.empty": "Šio naudotojo dar niekas neseka.", + "account.followers_counter": "{count, plural, one {{counter} sekėjas} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}", "account.following": "Sekama", + "account.following_counter": "{count, plural, one {{counter} sekimas} few {{counter} sekimai} many {{counter} sekimo} other {{counter} sekimų}}", "account.follows.empty": "Šis naudotojas dar nieko neseka.", "account.go_to_profile": "Eiti į profilį", "account.hide_reblogs": "Slėpti pakėlimus iš @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} paprašė tave sekti", "account.share": "Bendrinti @{name} profilį", "account.show_reblogs": "Rodyti pakėlimus iš @{name}", + "account.statuses_counter": "{count, plural, one {{counter} įrašas} few {{counter} įrašai} many {{counter} įrašo} other {{counter} įrašų}}", "account.unblock": "Atblokuoti @{name}", "account.unblock_domain": "Atblokuoti domeną {domain}", "account.unblock_short": "Atblokuoti", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 7eca29659..0fb0edf0a 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -35,7 +35,9 @@ "account.follow_back": "Fylg tilbake", "account.followers": "Fylgjarar", "account.followers.empty": "Ingen fylgjer denne brukaren enno.", + "account.followers_counter": "{count, plural, one {{counter} følgjar} other {{counter} følgjarar}}", "account.following": "Fylgjer", + "account.following_counter": "{count, plural, one {{counter} følgjer} other {{counter} følgjer}}", "account.follows.empty": "Denne brukaren fylgjer ikkje nokon enno.", "account.go_to_profile": "Gå til profil", "account.hide_reblogs": "Gøym framhevingar frå @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} har bedt om å få fylgja deg", "account.share": "Del @{name} sin profil", "account.show_reblogs": "Vis framhevingar frå @{name}", + "account.statuses_counter": "{count, plural, one {{counter} innlegg} other {{counter} innlegg}}", "account.unblock": "Stopp blokkering av @{name}", "account.unblock_domain": "Stopp blokkering av domenet {domain}", "account.unblock_short": "Stopp blokkering", @@ -693,8 +696,11 @@ "server_banner.about_active_users": "Personar som har brukt denne tenaren dei siste 30 dagane (Månadlege Aktive Brukarar)", "server_banner.active_users": "aktive brukarar", "server_banner.administered_by": "Administrert av:", + "server_banner.is_one_of_many": "{domain} er ein av dei mange uavhengige Mastodon-serverane du kan bruke til å delta i Fødiverset.", "server_banner.server_stats": "Tenarstatistikk:", "sign_in_banner.create_account": "Opprett konto", + "sign_in_banner.follow_anyone": "Følg kven som helst på tvers av Fødiverset og sjå alt i kronologisk rekkjefølgje. Ingen algoritmar, reklamar eller clickbait i sikte.", + "sign_in_banner.mastodon_is": "Mastodon er den beste måten å følgje med på det som skjer.", "sign_in_banner.sign_in": "Logg inn", "sign_in_banner.sso_redirect": "Logg inn eller registrer deg", "status.admin_account": "Opne moderasjonsgrensesnitt for @{name}", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index a3690e734..ddfe1d4fb 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -35,7 +35,9 @@ "account.follow_back": "Obserwuj wzajemnie", "account.followers": "Obserwujący", "account.followers.empty": "Nikt jeszcze nie obserwuje tego użytkownika.", + "account.followers_counter": "{count, plural, one {{counter} obserwujący} few {{counter} obserwujących} many {{counter} obserwujących} other {{counter} obserwujących}}", "account.following": "Obserwowani", + "account.following_counter": "{count, plural, one {{counter} obserwowany} few {{counter} obserwowanych} many {{counter} obserwowanych} other {{counter} obserwowanych}}", "account.follows.empty": "Ten użytkownik nie obserwuje jeszcze nikogo.", "account.go_to_profile": "Przejdź do profilu", "account.hide_reblogs": "Ukryj podbicia od @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} chce zaobserwować twój profil", "account.share": "Udostępnij profil @{name}", "account.show_reblogs": "Pokazuj podbicia od @{name}", + "account.statuses_counter": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}}", "account.unblock": "Odblokuj @{name}", "account.unblock_domain": "Odblokuj domenę {domain}", "account.unblock_short": "Odblokuj", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 41112c2ca..6a6feca30 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -35,7 +35,9 @@ "account.follow_back": "Seguir de volta", "account.followers": "Seguidores", "account.followers.empty": "Ainda ninguém segue este utilizador.", + "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "A seguir", + "account.following_counter": "{count, plural, one {A seguir {counter}} other {A seguir {counter}}}", "account.follows.empty": "Este utilizador ainda não segue ninguém.", "account.go_to_profile": "Ir para o perfil", "account.hide_reblogs": "Esconder partilhas de @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} pediu para segui-lo", "account.share": "Partilhar o perfil @{name}", "account.show_reblogs": "Mostrar partilhas de @{name}", + "account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear o domínio {domain}", "account.unblock_short": "Desbloquear", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 6ac038c9f..96b7b3fef 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -35,7 +35,9 @@ "account.follow_back": "Ndiqe gjithashtu", "account.followers": "Ndjekës", "account.followers.empty": "Këtë përdorues ende s’e ndjek kush.", + "account.followers_counter": "{count, plural, one {{counter} ndjekës} other {{counter} ndjekës}}", "account.following": "Ndjekje", + "account.following_counter": "{count, plural, one {{counter} i ndjekur} other {{counter} të ndjekur}}", "account.follows.empty": "Ky përdorues ende s’ndjek kënd.", "account.go_to_profile": "Kalo te profili", "account.hide_reblogs": "Fshih përforcime nga @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} ka kërkuar t’ju ndjekë", "account.share": "Ndajeni profilin e @{name} me të tjerët", "account.show_reblogs": "Shfaq përforcime nga @{name}", + "account.statuses_counter": "{count, plural, one {{counter} postim} other {{counter} postime}}", "account.unblock": "Zhbllokoje @{name}", "account.unblock_domain": "Zhblloko përkatësinë {domain}", "account.unblock_short": "Zhbllokoje", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 7445b77ba..1833a2cfd 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -35,6 +35,7 @@ "account.follow_back": "Följ tillbaka", "account.followers": "Följare", "account.followers.empty": "Ingen följer denna användare än.", + "account.followers_counter": "{count, plural, one {{counter} följare} other {{counter} följare}}", "account.following": "Följer", "account.follows.empty": "Denna användare följer inte någon än.", "account.go_to_profile": "Gå till profilen", @@ -61,6 +62,7 @@ "account.requested_follow": "{name} har begärt att följa dig", "account.share": "Dela @{name}s profil", "account.show_reblogs": "Visa boostar från @{name}", + "account.statuses_counter": "{count, plural, one {{counter} inlägg} other {{counter} inlägg}}", "account.unblock": "Avblockera @{name}", "account.unblock_domain": "Avblockera {domain}", "account.unblock_short": "Avblockera", diff --git a/config/locales/nn.yml b/config/locales/nn.yml index d82c92c26..2da30e662 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -293,6 +293,7 @@ nn: filter_by_action: Sorter etter handling filter_by_user: Sorter etter brukar title: Revisionslogg + unavailable_instance: "(domenenamn er utilgjengeleg)" announcements: destroyed_msg: Kunngjøringen er slettet! edit: From 1bccba14082f380232a363d036b6b2e92104ffc6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:39:32 +0200 Subject: [PATCH 42/84] chore(deps): update dependency @testing-library/react to v16 (#30533) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Renaud Chaput <renchap@gmail.com> --- app/javascript/mastodon/test_helpers.tsx | 7 ++++- package.json | 3 ++- yarn.lock | 33 +++++++++++++----------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/app/javascript/mastodon/test_helpers.tsx b/app/javascript/mastodon/test_helpers.tsx index 93b5a8453..f40509073 100644 --- a/app/javascript/mastodon/test_helpers.tsx +++ b/app/javascript/mastodon/test_helpers.tsx @@ -2,6 +2,7 @@ import { IntlProvider } from 'react-intl'; import { MemoryRouter } from 'react-router'; +import type { RenderOptions } from '@testing-library/react'; // eslint-disable-next-line import/no-extraneous-dependencies import { render as rtlRender } from '@testing-library/react'; @@ -9,7 +10,11 @@ import { IdentityContext } from './identity_context'; function render( ui: React.ReactElement, - { locale = 'en', signedIn = true, ...renderOptions } = {}, + { + locale = 'en', + signedIn = true, + ...renderOptions + }: RenderOptions & { locale?: string; signedIn?: boolean } = {}, ) { const fakeIdentity = { signedIn: signedIn, diff --git a/package.json b/package.json index 729482f85..3eb1ebad7 100644 --- a/package.json +++ b/package.json @@ -138,8 +138,9 @@ }, "devDependencies": { "@formatjs/cli": "^6.1.1", + "@testing-library/dom": "^10.2.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@types/babel__core": "^7.20.1", "@types/emoji-mart": "^3.0.9", "@types/escape-html": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index f9eaef458..0e7337328 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2768,8 +2768,9 @@ __metadata: "@rails/ujs": "npm:7.1.3" "@reduxjs/toolkit": "npm:^2.0.1" "@svgr/webpack": "npm:^5.5.0" + "@testing-library/dom": "npm:^10.2.0" "@testing-library/jest-dom": "npm:^6.0.0" - "@testing-library/react": "npm:^15.0.0" + "@testing-library/react": "npm:^16.0.0" "@types/babel__core": "npm:^7.20.1" "@types/emoji-mart": "npm:^3.0.9" "@types/escape-html": "npm:^1.0.2" @@ -3348,9 +3349,9 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:^10.0.0": - version: 10.0.0 - resolution: "@testing-library/dom@npm:10.0.0" +"@testing-library/dom@npm:^10.2.0": + version: 10.2.0 + resolution: "@testing-library/dom@npm:10.2.0" dependencies: "@babel/code-frame": "npm:^7.10.4" "@babel/runtime": "npm:^7.12.5" @@ -3360,7 +3361,7 @@ __metadata: dom-accessibility-api: "npm:^0.5.9" lz-string: "npm:^1.5.0" pretty-format: "npm:^27.0.2" - checksum: 10c0/2d12d2a6018a6f1d15e91834180bc068932c699ff1fcbfb80aa21aba519a4f5329c861dfa852e06ee5615bcb92ef2a0f0e755e32684ea3dada63bc34248382ab + checksum: 10c0/de582dfbeb632436547a0ca5851b5a714a4a17f8e96ab3dc4fb4e454eef52c912b648b7cb6e9fdf477f3eeef97e698f3250f0ce50846f39d04677a44169209f2 languageName: node linkType: hard @@ -3397,21 +3398,23 @@ __metadata: languageName: node linkType: hard -"@testing-library/react@npm:^15.0.0": - version: 15.0.7 - resolution: "@testing-library/react@npm:15.0.7" +"@testing-library/react@npm:^16.0.0": + version: 16.0.0 + resolution: "@testing-library/react@npm:16.0.0" dependencies: "@babel/runtime": "npm:^7.12.5" - "@testing-library/dom": "npm:^10.0.0" - "@types/react-dom": "npm:^18.0.0" peerDependencies: + "@testing-library/dom": ^10.0.0 "@types/react": ^18.0.0 + "@types/react-dom": ^18.0.0 react: ^18.0.0 react-dom: ^18.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/ac8ee8968e81949ecb35f7ee34741c2c043f73dd7fee2247d56f6de6a30de4742af94f25264356863974e54387485b46c9448ecf3f6ca41cf4339011c369f2d4 + "@types/react-dom": + optional: true + checksum: 10c0/297f97bf4722dad05f11d9cafd47d387dbdb096fea4b79b876c7466460f0f2e345b55b81b3e37fc81ed8185c528cb53dd8455ca1b6b019b229edf6c796f11c9f languageName: node linkType: hard @@ -3430,9 +3433,9 @@ __metadata: linkType: hard "@types/aria-query@npm:^5.0.1": - version: 5.0.1 - resolution: "@types/aria-query@npm:5.0.1" - checksum: 10c0/bc9e40ce37bd3a1654948778c7829bd55aea1bc5f2cd06fcf6cd650b07bb388995799e9aab6e2d93a6cf55dcba3b85c155f7ba93adefcc7c2e152fc6057061b5 + version: 5.0.4 + resolution: "@types/aria-query@npm:5.0.4" + checksum: 10c0/dc667bc6a3acc7bba2bccf8c23d56cb1f2f4defaa704cfef595437107efaa972d3b3db9ec1d66bc2711bfc35086821edd32c302bffab36f2e79b97f312069f08 languageName: node linkType: hard @@ -3829,7 +3832,7 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.2.4": +"@types/react-dom@npm:^18.2.4": version: 18.3.0 resolution: "@types/react-dom@npm:18.3.0" dependencies: From ba6a558a7088b811b63a90d20b82069eee7dfaa1 Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Sat, 29 Jun 2024 00:41:27 +0200 Subject: [PATCH 43/84] Simplify color extraction code using `bandunfold` (#30869) --- lib/paperclip/color_extractor.rb | 35 +++++++++++--------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/lib/paperclip/color_extractor.rb b/lib/paperclip/color_extractor.rb index 378af0961..fba32ba4c 100644 --- a/lib/paperclip/color_extractor.rb +++ b/lib/paperclip/color_extractor.rb @@ -116,34 +116,23 @@ module Paperclip # The number of occurrences of a color (r, g, b) is thus encoded in band `b` at pixel position `(r, g)` histogram = image.hist_find_ndim(bins: BINS) - # `histogram.max` returns an array of maxima with their pixel positions, but we don't know in which - # band they are + # With `bandunfold`, we get back to a (BINS*BINS)×BINS 2D image with a single band. + # The number of occurrences of a color (r, g, b) is thus encoded at pixel position `(r * BINS + b, g)` + histogram = histogram.bandunfold + _, colors = histogram.max(size: 10, out_array: true, x_array: true, y_array: true) - colors['out_array'].zip(colors['x_array'], colors['y_array']).map do |v, x, y| - rgb_from_xyv(histogram, x, y, v) - end.flatten.reverse.uniq + colors['x_array'].zip(colors['y_array']).map do |x, y| + rgb_from_hist_xy(x, y) + end.flatten.reverse end # rubocop:disable Naming/MethodParameterName - def rgb_from_xyv(image, x, y, v) - pixel = image.getpoint(x, y) - - # As we only have the first 2 dimensions for this maximum, we - # can't distinguish with different maxima with the same `r` and `g` - # values but different `b` values. - # - # Therefore, we return an array of maxima, which is always non-empty, - # but may contain multiple colors with the same values. - - pixel.filter_map.with_index do |pv, z| - next if pv != v - - r = (x + 0.5) * 256 / BINS - g = (y + 0.5) * 256 / BINS - b = (z + 0.5) * 256 / BINS - ColorDiff::Color::RGB.new(r, g, b) - end + def rgb_from_hist_xy(x, y) + r = ((x / BINS) + 0.5) * 256 / BINS + g = (y + 0.5) * 256 / BINS + b = ((x % BINS) + 0.5) * 256 / BINS + ColorDiff::Color::RGB.new(r, g, b) end def w3c_contrast(color1, color2) From 5d4dbbcc67c98007d417cbe67b5a2261889304dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 22:51:46 +0200 Subject: [PATCH 44/84] chore(deps): update dependency charlock_holmes to v0.7.8 (#30870) 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 dd112d018..02437eab6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -159,7 +159,7 @@ GEM case_transform (0.2) activesupport cbor (0.5.9.8) - charlock_holmes (0.7.7) + charlock_holmes (0.7.8) chewy (7.6.0) activesupport (>= 5.2) elasticsearch (>= 7.14.0, < 8) From 8142c7aa3eb2e0705c32e862fd7db000b898bc38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:21:15 +0200 Subject: [PATCH 45/84] chore(deps): update definitelytyped types (non-major) (#30882) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0e7337328..30141a6cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3625,11 +3625,11 @@ __metadata: linkType: hard "@types/http-link-header@npm:^1.0.3": - version: 1.0.6 - resolution: "@types/http-link-header@npm:1.0.6" + version: 1.0.7 + resolution: "@types/http-link-header@npm:1.0.7" dependencies: "@types/node": "npm:*" - checksum: 10c0/63f3f7ab5ff6312280727ba8cf836abf5d1b76f9dc5eefc8cd4389db29d57a72fb0e028db99735ada5ccfd3c2cc6607e096b5cc142fc53c2bb5688b6295f61af + checksum: 10c0/ffde4514a286ee62fab86bd5164958f5c9ad9d8012eaeb4f5536efa6157e6cf9f93121d5a39bf160c3712554d945300a223a9f8b1f3fb08cc70b73f539767338 languageName: node linkType: hard @@ -3715,9 +3715,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.195": - version: 4.17.5 - resolution: "@types/lodash@npm:4.17.5" - checksum: 10c0/55924803ed853e72261512bd3eaf2c5b16558c3817feb0a3125ef757afe46e54b86f33d1960e40b7606c0ddab91a96f47966bf5e6006b7abfd8994c13b04b19b + version: 4.17.6 + resolution: "@types/lodash@npm:4.17.6" + checksum: 10c0/3b197ac47af9443fee8c4719c5ffde527d7febc018b827d44a6bc2523c728c7adfdd25196fdcfe3eed827993e0c41a917d0da6e78938b18b2be94164789f1117 languageName: node linkType: hard From 9c7d56db9a30e35d54974ea9257c2b48545b5217 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:21:40 +0200 Subject: [PATCH 46/84] fix(deps): update dependency postcss-preset-env to v9.5.15 (#30878) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 404 +++++++++++++++++++++++++++--------------------------- 1 file changed, 202 insertions(+), 202 deletions(-) diff --git a/yarn.lock b/yarn.lock index 30141a6cc..b650d5f49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1558,69 +1558,69 @@ __metadata: languageName: node linkType: hard -"@csstools/cascade-layer-name-parser@npm:^1.0.11": - version: 1.0.11 - resolution: "@csstools/cascade-layer-name-parser@npm:1.0.11" +"@csstools/cascade-layer-name-parser@npm:^1.0.12": + version: 1.0.12 + resolution: "@csstools/cascade-layer-name-parser@npm:1.0.12" peerDependencies: - "@csstools/css-parser-algorithms": ^2.6.3 - "@csstools/css-tokenizer": ^2.3.1 - checksum: 10c0/52ac8369877c8072ff5c111f656bd87e9a2a4b9e44e48fe005c26faeb6cffd83bfe2f463f4f385a2ae5cfe1f82bbf95d26ddaabca18b66c6b657c4fe1520fb43 + "@csstools/css-parser-algorithms": ^2.7.0 + "@csstools/css-tokenizer": ^2.3.2 + checksum: 10c0/5f92aefcbb3f4b660cf7b0db54f6a4ba21a32fa1b64ea4f050a6370233152d4f561ecf5c8e98ca231e73c16e0d9f75b20b0a65153e18b14957658c81e0f68213 languageName: node linkType: hard -"@csstools/color-helpers@npm:^4.2.0": - version: 4.2.0 - resolution: "@csstools/color-helpers@npm:4.2.0" - checksum: 10c0/3f1feac43c2ef35f38b3b06fe74e0acc130283d7efb6874f6624e45e178c1a7b3c7e39816c7421cddbffc2666430906aa6f0d3dd7c7209db1369c0afd4a29b1b +"@csstools/color-helpers@npm:^4.2.1": + version: 4.2.1 + resolution: "@csstools/color-helpers@npm:4.2.1" + checksum: 10c0/72e11b186ad0f6019a9b4b3752e620fa798c2a40cf47e8cad565dff46e572c9342eb8cf804542d7886344a1e540555d77f20119ace6b2d8a45b6e5ef8a41685c languageName: node linkType: hard -"@csstools/css-calc@npm:^1.2.2": - version: 1.2.2 - resolution: "@csstools/css-calc@npm:1.2.2" +"@csstools/css-calc@npm:^1.2.3": + version: 1.2.3 + resolution: "@csstools/css-calc@npm:1.2.3" peerDependencies: - "@csstools/css-parser-algorithms": ^2.6.3 - "@csstools/css-tokenizer": ^2.3.1 - checksum: 10c0/6032b482764a11c1b882d7502928950ab11760044fa7a2c23ecee802002902f6ea8fca045ee2919302af5a5c399e7baa9f68dff001ac6246ac7fef48fb3f6df7 + "@csstools/css-parser-algorithms": ^2.7.0 + "@csstools/css-tokenizer": ^2.3.2 + checksum: 10c0/fb34767ea9638b837167bcecaf945bcc0c5e8f0d811067c4e8c7a57bc8f0955f61107b1ed5e017b95c54acacc8088473e5497a9986bee95b37ec92999e792871 languageName: node linkType: hard -"@csstools/css-color-parser@npm:^2.0.2": - version: 2.0.2 - resolution: "@csstools/css-color-parser@npm:2.0.2" +"@csstools/css-color-parser@npm:^2.0.3": + version: 2.0.3 + resolution: "@csstools/css-color-parser@npm:2.0.3" dependencies: - "@csstools/color-helpers": "npm:^4.2.0" - "@csstools/css-calc": "npm:^1.2.2" + "@csstools/color-helpers": "npm:^4.2.1" + "@csstools/css-calc": "npm:^1.2.3" peerDependencies: - "@csstools/css-parser-algorithms": ^2.6.3 - "@csstools/css-tokenizer": ^2.3.1 - checksum: 10c0/c5ae4ad78745e425dce56da9f1ab053fb4f7963399735df3303305b32123bed0b2237689c2e7e99da2c62387e3226c12ea85e70e275c4027c7507e4ac929bffa + "@csstools/css-parser-algorithms": ^2.7.0 + "@csstools/css-tokenizer": ^2.3.2 + checksum: 10c0/d8860e6b9c65de4f90d4c21e4d66471fd858434cf63af80f812a900371343b753b86a256627e8bd024cb8903a6a0181d2d9c0c65ab5d78cf29d084a761e2adba languageName: node linkType: hard -"@csstools/css-parser-algorithms@npm:^2.6.3": - version: 2.6.3 - resolution: "@csstools/css-parser-algorithms@npm:2.6.3" +"@csstools/css-parser-algorithms@npm:^2.6.3, @csstools/css-parser-algorithms@npm:^2.7.0": + version: 2.7.0 + resolution: "@csstools/css-parser-algorithms@npm:2.7.0" peerDependencies: - "@csstools/css-tokenizer": ^2.3.1 - checksum: 10c0/6648fda75a1c08096320fb5c04fd13656a0168de13584d2795547fecfb26c2c7d8b3b1fb79ba7aa758714851e98bfbec20d89e28697f999f41f91133eafe4207 + "@csstools/css-tokenizer": ^2.3.2 + checksum: 10c0/fb84fefdf37c41d170f81b687bf1ee1847a970e51cc1fe3a320e3eaf225383ae9a3c4eb6208b83357dfe18c5114353d780e0c65f05d86d6435e5a9ad9334c834 languageName: node linkType: hard -"@csstools/css-tokenizer@npm:^2.3.1": - version: 2.3.1 - resolution: "@csstools/css-tokenizer@npm:2.3.1" - checksum: 10c0/fed6619fb5108e109d4dd10b0e967035a92793bae8fb84544e1342058b6df4e306d9d075623e2201fe88831b1ada797aea3546a8d12229d2d81cd7a5dfee4444 +"@csstools/css-tokenizer@npm:^2.3.1, @csstools/css-tokenizer@npm:^2.3.2": + version: 2.3.2 + resolution: "@csstools/css-tokenizer@npm:2.3.2" + checksum: 10c0/f7d0d8b3e9e2dcdc6547a387253a09dbbacaaffb5c8718bcd7f15dddeefdd441b73fc5f9fad3f03fabef3b37ec4b62be7ff79caab366427fa90eaf54cd8fc452 languageName: node linkType: hard -"@csstools/media-query-list-parser@npm:^2.1.11": - version: 2.1.11 - resolution: "@csstools/media-query-list-parser@npm:2.1.11" +"@csstools/media-query-list-parser@npm:^2.1.11, @csstools/media-query-list-parser@npm:^2.1.12": + version: 2.1.12 + resolution: "@csstools/media-query-list-parser@npm:2.1.12" peerDependencies: - "@csstools/css-parser-algorithms": ^2.6.3 - "@csstools/css-tokenizer": ^2.3.1 - checksum: 10c0/9bcd99f7d28ae3cdaba73fbbfef571b0393dd4e841f522cc796fe5161744f17e327ba1713dad3c481626fade1357c55890e3d365177abed50e857b69130a9be5 + "@csstools/css-parser-algorithms": ^2.7.0 + "@csstools/css-tokenizer": ^2.3.2 + checksum: 10c0/7395cc710d8f54670c1e7a418a88dcf1ae726316272294ec645f6d79a8e931f5d390ba7ed5d0141d29ad7280cd447b8773143dc7676659413de79228130e1a65 languageName: node linkType: hard @@ -1636,46 +1636,46 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-color-function@npm:^3.0.16": - version: 3.0.16 - resolution: "@csstools/postcss-color-function@npm:3.0.16" +"@csstools/postcss-color-function@npm:^3.0.17": + version: 3.0.17 + resolution: "@csstools/postcss-color-function@npm:3.0.17" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/41756a4601a3f1086290dab6ca92b54e201bd94637b54b439c66a04fd628a14e2a0bd1452ad294d2981e2f4bb306758fa5f44639b1c4332320435050749aa487 + checksum: 10c0/6d347fc9fe65cb897c275c129103576e551b74a7c47a1a4dc8160da2fad7752bf51e3cfbff339f86b39c723efac33643168d2dfaac4d3624d072875d18a65a4b languageName: node linkType: hard -"@csstools/postcss-color-mix-function@npm:^2.0.16": - version: 2.0.16 - resolution: "@csstools/postcss-color-mix-function@npm:2.0.16" +"@csstools/postcss-color-mix-function@npm:^2.0.17": + version: 2.0.17 + resolution: "@csstools/postcss-color-mix-function@npm:2.0.17" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/70cd5b291dd615e20e4475517bf0027c90c433241397a66866f89acedb12cb91f45552a162bdd1000636ec56f7d6a099b65e44fe100fd03228fc65f17cfae285 + checksum: 10c0/ba9a406ebe4caba6709878ee26debb06780be5cbf4e6ab7e902d79ca6e21ec6a8409b9dc0a5ef36fc4bf54bf2bd8f9642b72da8d7939145f99dc40fedd2be9d2 languageName: node linkType: hard -"@csstools/postcss-exponential-functions@npm:^1.0.7": - version: 1.0.7 - resolution: "@csstools/postcss-exponential-functions@npm:1.0.7" +"@csstools/postcss-exponential-functions@npm:^1.0.8": + version: 1.0.8 + resolution: "@csstools/postcss-exponential-functions@npm:1.0.8" dependencies: - "@csstools/css-calc": "npm:^1.2.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-calc": "npm:^1.2.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" peerDependencies: postcss: ^8.4 - checksum: 10c0/2079c81c3437686ef432d88502fa3a13bf8a27b7af105b4c6c2eb8e779f14adc8967a5a3ed03271ab919eeaf999fc4489fe4b37d32a8f61ab3212439517bddcc + checksum: 10c0/6b049801fc1275b34f43ffbb915f447a54cbff7bf48ab0705c3ad1ffde055cb876c4dc24e7a9162cd65e219457328e298a673f6176446493db17cf7af6f90dc0 languageName: node linkType: hard @@ -1691,46 +1691,46 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-gamut-mapping@npm:^1.0.9": - version: 1.0.9 - resolution: "@csstools/postcss-gamut-mapping@npm:1.0.9" +"@csstools/postcss-gamut-mapping@npm:^1.0.10": + version: 1.0.10 + resolution: "@csstools/postcss-gamut-mapping@npm:1.0.10" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" peerDependencies: postcss: ^8.4 - checksum: 10c0/412ae1410f3fce240401576441637c2c4e71d1a54153ac9b7a991b3de7519c253d03e10db78b09872eb10b0776d7f960b442779efabc11332b5be6672163c836 + checksum: 10c0/6c2dab6a84f81904bed89cb584bd9bc6a904b49a4fa315b17be65c7d68baefe592561ee439660d5602b7481bac3be9a93189dc45404764524495400f34c6b6e6 languageName: node linkType: hard -"@csstools/postcss-gradients-interpolation-method@npm:^4.0.17": - version: 4.0.17 - resolution: "@csstools/postcss-gradients-interpolation-method@npm:4.0.17" +"@csstools/postcss-gradients-interpolation-method@npm:^4.0.18": + version: 4.0.18 + resolution: "@csstools/postcss-gradients-interpolation-method@npm:4.0.18" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/465ac42856ca1a57aa2b9ea41ede31d9e2bcf2fe84345dbc182ae41f463069a0cfd41041b834b5133108c702cd85ecb8636b51b0b88fff8a221628639b59f386 + checksum: 10c0/23c431068ac205392b4953dbce411e208e79e221ba8030c5e23c0b82e8fd53bc3bc4f2cdc47050f5d91a4ac69cb80f4f1853b213aa8072fa60a6cb6ff0621e04 languageName: node linkType: hard -"@csstools/postcss-hwb-function@npm:^3.0.15": - version: 3.0.15 - resolution: "@csstools/postcss-hwb-function@npm:3.0.15" +"@csstools/postcss-hwb-function@npm:^3.0.16": + version: 3.0.16 + resolution: "@csstools/postcss-hwb-function@npm:3.0.16" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/fdfaeefbab1008ab1e4a98a2b45cc3db002b2724c404fa0600954b411a68b1fa4028286250bf9898eed10fa80c44e4d6b4e55f1aca073c3dfce8198a0aaedf3f + checksum: 10c0/4deca8831a69038aff719a77df92c53578bb28e23cc61dc4ea7b1d912b1b685683a9c6232396c2616948ac2e8488ad1e2009c9c8ed30c493d97ba8ad37b6418d languageName: node linkType: hard @@ -1768,17 +1768,17 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-light-dark-function@npm:^1.0.5": - version: 1.0.5 - resolution: "@csstools/postcss-light-dark-function@npm:1.0.5" +"@csstools/postcss-light-dark-function@npm:^1.0.6": + version: 1.0.6 + resolution: "@csstools/postcss-light-dark-function@npm:1.0.6" dependencies: - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/4fbeda98372d0da25d3ed87da09903c9a0a5d0b8c13cc9de82a98acce4a8f8367e5ba33bfc25c2534d10f2b1db9d5b4278df4ebab755e27ef2b03a95e0ebe264 + checksum: 10c0/6b2c64860d789cd3e3ce875c01259333911f6e32a751a7475604de8022c13abcb578e5cb941b51bd3a2022bee883df3f6b64800c6e3559b06da283d968aeb615 languageName: node linkType: hard @@ -1820,42 +1820,42 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-logical-viewport-units@npm:^2.0.9": - version: 2.0.9 - resolution: "@csstools/postcss-logical-viewport-units@npm:2.0.9" +"@csstools/postcss-logical-viewport-units@npm:^2.0.10": + version: 2.0.10 + resolution: "@csstools/postcss-logical-viewport-units@npm:2.0.10" dependencies: - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/25b01e36b08c571806d09046be63582dbebf97a4612df59be405fa8a92e6eebcd4e768ad7fbe53b0b8739d6ab04d56957964fb04d6a3ea129fc5f72e6d0adf95 + checksum: 10c0/fe142b11e0e8ccab4667cc5db90b45e67b7d11eaf5c038e91d867e1b18a315ef0859114185aeb48fdc1ce05986be8b644d6157fe9e19da7281f7023c99eb8877 languageName: node linkType: hard -"@csstools/postcss-media-minmax@npm:^1.1.6": - version: 1.1.6 - resolution: "@csstools/postcss-media-minmax@npm:1.1.6" +"@csstools/postcss-media-minmax@npm:^1.1.7": + version: 1.1.7 + resolution: "@csstools/postcss-media-minmax@npm:1.1.7" dependencies: - "@csstools/css-calc": "npm:^1.2.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" - "@csstools/media-query-list-parser": "npm:^2.1.11" + "@csstools/css-calc": "npm:^1.2.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" + "@csstools/media-query-list-parser": "npm:^2.1.12" peerDependencies: postcss: ^8.4 - checksum: 10c0/2cbfb3728a232c655d82f63d5ac7da36876d14e5fee5d62a0738efed40c58f20ef11f600395ade24d5063d750e8e093251dd93cc361f782b5a6c0e0f80288f51 + checksum: 10c0/a02943a17b540cbd909b55bbb1f8c9331badc51b613279bbdb7127c9921a5d0675bb41675a3b4d0f15e9586120e5a96d9b9786b63b2c594fbb3a238e860c6ad8 languageName: node linkType: hard -"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^2.0.9": - version: 2.0.9 - resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:2.0.9" +"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^2.0.10": + version: 2.0.10 + resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:2.0.10" dependencies: - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" - "@csstools/media-query-list-parser": "npm:^2.1.11" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" + "@csstools/media-query-list-parser": "npm:^2.1.12" peerDependencies: postcss: ^8.4 - checksum: 10c0/d431d2900a7177c938d9dc2d5bdf3c1930758adc214cc72f94b34e6bbd02fd917c200dc81482db515519c97d4f1e766ba3200f3ec9b55081887f2f8111f68e20 + checksum: 10c0/d7879e72df98d9fe2e5d85a64837e7a73c2df1aea8659d65516f0acb070317edd353531882f0bdfd81510703d1da30d6da861052a0bda85fde1f9eab94b1e467 languageName: node linkType: hard @@ -1882,18 +1882,18 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-oklab-function@npm:^3.0.16": - version: 3.0.16 - resolution: "@csstools/postcss-oklab-function@npm:3.0.16" +"@csstools/postcss-oklab-function@npm:^3.0.17": + version: 3.0.17 + resolution: "@csstools/postcss-oklab-function@npm:3.0.17" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/9c67ee5f51116df16ab6baffa1b3c6c7aa93d53b836f421125ae8824075bd3cfaa1a93594466de0ac935c89c4fc8171e80974e1a15bafa23ea864e4cf1f1c1f2 + checksum: 10c0/ff27a4b6fd8490439aa0f3c91ffa2a42a8cf539d7306d9329cef7ca59f28317cee40253f402d19a18c196471fd39a05842d2974d92f1b131dc748074d91ac4ee languageName: node linkType: hard @@ -1908,18 +1908,18 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-relative-color-syntax@npm:^2.0.16": - version: 2.0.16 - resolution: "@csstools/postcss-relative-color-syntax@npm:2.0.16" +"@csstools/postcss-relative-color-syntax@npm:^2.0.17": + version: 2.0.17 + resolution: "@csstools/postcss-relative-color-syntax@npm:2.0.17" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/cdc965706212dcbc03394f55c79a0ad043d1e0174059c4d0d90e4267fe8e6fd9eef7cfed4f5bbc1f8e89c225c1c042ae792e115bba198eb2daae763d65f44679 + checksum: 10c0/46226351b3825323e3496dcee44ff354cd3ccc9241d837659e1311f428f0b4dc878d9bb762cbb8f63243b7af346728ab7a46c311f9dc38bb609147523c698eab languageName: node linkType: hard @@ -1934,41 +1934,41 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-stepped-value-functions@npm:^3.0.8": - version: 3.0.8 - resolution: "@csstools/postcss-stepped-value-functions@npm:3.0.8" +"@csstools/postcss-stepped-value-functions@npm:^3.0.9": + version: 3.0.9 + resolution: "@csstools/postcss-stepped-value-functions@npm:3.0.9" dependencies: - "@csstools/css-calc": "npm:^1.2.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-calc": "npm:^1.2.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" peerDependencies: postcss: ^8.4 - checksum: 10c0/2be66aa769808245137be8ff14308aa17c3a0d75433f6fd6789114966a78c365dbf173d087e7ff5bc80118c75be2ff740baab83ed39fc0671980f6217779956b + checksum: 10c0/bafe80947abc8613903f1f3f1939ece9780696774f15960aef229733e40e483dc2830145426d49c4f6d0b1dabb35f812c8a2dda0d0dcddc930321e36b5c6ca0b languageName: node linkType: hard -"@csstools/postcss-text-decoration-shorthand@npm:^3.0.6": - version: 3.0.6 - resolution: "@csstools/postcss-text-decoration-shorthand@npm:3.0.6" +"@csstools/postcss-text-decoration-shorthand@npm:^3.0.7": + version: 3.0.7 + resolution: "@csstools/postcss-text-decoration-shorthand@npm:3.0.7" dependencies: - "@csstools/color-helpers": "npm:^4.2.0" + "@csstools/color-helpers": "npm:^4.2.1" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/5abdc4fad1c3f15e9d47c7af3995dec9cdf4e6f87c5857eb2e149764779b8389f4f4b21d11e6f2509c57c554a0dc5c11f68f212acd04bbc47defa15911ac3eb9 + checksum: 10c0/072b9893ca2409aa16e53e84747d7b7e13071ce19738a0800a139bf71b535e439958d9093df2b85f83eee2e0c44bc22a14bf3a39b5a7508bca9e747a12273d02 languageName: node linkType: hard -"@csstools/postcss-trigonometric-functions@npm:^3.0.8": - version: 3.0.8 - resolution: "@csstools/postcss-trigonometric-functions@npm:3.0.8" +"@csstools/postcss-trigonometric-functions@npm:^3.0.9": + version: 3.0.9 + resolution: "@csstools/postcss-trigonometric-functions@npm:3.0.9" dependencies: - "@csstools/css-calc": "npm:^1.2.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-calc": "npm:^1.2.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" peerDependencies: postcss: ^8.4 - checksum: 10c0/aeed8d1026f4a5cb7afafbadd739af84291d5bfcbcdef2f79b77174f003d0cd0c7f9deb3fe0b9377efab37ce9bb17a2499efd4af8211f5ff9eb01b878b0b62b3 + checksum: 10c0/7a439d31a63d35986dab634d9e415f7ce7c32a2d3d382052b5b730a259a12e44c5f1b14e318d79086253e3d5d4f7d942d0e7317d92eb3421dd08824eebec45fb languageName: node linkType: hard @@ -5646,7 +5646,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.22.2, browserslist@npm:^4.22.3, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1": +"browserslist@npm:^4.0.0, browserslist@npm:^4.22.2, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1": version: 4.23.1 resolution: "browserslist@npm:4.23.1" dependencies: @@ -13208,18 +13208,18 @@ __metadata: languageName: node linkType: hard -"postcss-color-functional-notation@npm:^6.0.11": - version: 6.0.11 - resolution: "postcss-color-functional-notation@npm:6.0.11" +"postcss-color-functional-notation@npm:^6.0.12": + version: 6.0.12 + resolution: "postcss-color-functional-notation@npm:6.0.12" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/7fd75e6881cf62f536f79dfc0ae1b709ea0b8b84833cce1671372711f6019ab4360c6a17089b654b2d376b87e7f9455b94f0d13b45ab0ab767e547b604709b3d + checksum: 10c0/2e8faecd2609e1b4eb8c1cab21ecca5e746916795df20e6997d66eb61c29fbb01d3e75fef3e0b3e1c181918a2186570441b81779b1fc429d6d8823fbfa164231 languageName: node linkType: hard @@ -13273,46 +13273,46 @@ __metadata: languageName: node linkType: hard -"postcss-custom-media@npm:^10.0.6": - version: 10.0.6 - resolution: "postcss-custom-media@npm:10.0.6" +"postcss-custom-media@npm:^10.0.7": + version: 10.0.7 + resolution: "postcss-custom-media@npm:10.0.7" dependencies: - "@csstools/cascade-layer-name-parser": "npm:^1.0.11" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" - "@csstools/media-query-list-parser": "npm:^2.1.11" + "@csstools/cascade-layer-name-parser": "npm:^1.0.12" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" + "@csstools/media-query-list-parser": "npm:^2.1.12" peerDependencies: postcss: ^8.4 - checksum: 10c0/98a524bc46b780a86094bbe8007f1e577137da5490823631a683d4b3df4a13e40c5e1ab52380275a54f7011abfd98bb597c6293d964c14f9f22ec6cf9d75c550 + checksum: 10c0/4171385ab9370806861dcf7597e53fd6aa1862e77b475c9c565c95bfcc2b950f920f8da26a6dbec42e257388bca97c274635662b5e81fe3905b5e37babe06569 languageName: node linkType: hard -"postcss-custom-properties@npm:^13.3.10": - version: 13.3.10 - resolution: "postcss-custom-properties@npm:13.3.10" +"postcss-custom-properties@npm:^13.3.11": + version: 13.3.11 + resolution: "postcss-custom-properties@npm:13.3.11" dependencies: - "@csstools/cascade-layer-name-parser": "npm:^1.0.11" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/cascade-layer-name-parser": "npm:^1.0.12" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/utilities": "npm:^1.0.0" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/52688fd0aaadccfdf4a3d86d3a2ab988163e8108088c5e33fc9145d261f75b92b8321c044a8161345abda10df5715d674330309dcc0c17f2980db5515f6a76d6 + checksum: 10c0/4aa95628aa5d5b6df4dfeedbc3891b9666db88d75930cadc14d2fbba0a1b72f4e3cc3d83b5a0c0b8ce44f85b4fda6ebd7fe7792a1abc0a14d7d63b9f170d299c languageName: node linkType: hard -"postcss-custom-selectors@npm:^7.1.10": - version: 7.1.10 - resolution: "postcss-custom-selectors@npm:7.1.10" +"postcss-custom-selectors@npm:^7.1.11": + version: 7.1.11 + resolution: "postcss-custom-selectors@npm:7.1.11" dependencies: - "@csstools/cascade-layer-name-parser": "npm:^1.0.11" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" - postcss-selector-parser: "npm:^6.0.13" + "@csstools/cascade-layer-name-parser": "npm:^1.0.12" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" + postcss-selector-parser: "npm:^6.1.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/11311ae6f306420223c6bf926fb1798738f3aa525a267de204de8e8ee9de467bf63b580d9ad5dbb0fff4bd9266770a3fa7e27a24af08a2e0a4115d0727d1d043 + checksum: 10c0/f37d2e34239e868b35b7970ec97a7a8f657a9f92ed2b221af44f19949f7c3aedcecd0abb5fa1acb120c5ceffdf7a20869338956a37d7bfc37a83d8088f5d3dd2 languageName: node linkType: hard @@ -13430,18 +13430,18 @@ __metadata: languageName: node linkType: hard -"postcss-lab-function@npm:^6.0.16": - version: 6.0.16 - resolution: "postcss-lab-function@npm:6.0.16" +"postcss-lab-function@npm:^6.0.17": + version: 6.0.17 + resolution: "postcss-lab-function@npm:6.0.17" dependencies: - "@csstools/css-color-parser": "npm:^2.0.2" - "@csstools/css-parser-algorithms": "npm:^2.6.3" - "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/css-color-parser": "npm:^2.0.3" + "@csstools/css-parser-algorithms": "npm:^2.7.0" + "@csstools/css-tokenizer": "npm:^2.3.2" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" "@csstools/utilities": "npm:^1.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/ba8717cd8a197ec17acaac1b61631cd4403f07bd406b0c92f2e430a55e3f786cd6c338b626c3326e9178a0f3e58ff838ebaded19f480f39197a9cb17349ecdcd + checksum: 10c0/a331f188b02cc8beb315150232b6b58bc5793e8d61585973d352a9b4d370b908ff354ccf9ea1ba20a956fd37ea4ada918ea975c8d4f69e850d26edf0106436e8 languageName: node linkType: hard @@ -13762,52 +13762,52 @@ __metadata: linkType: hard "postcss-preset-env@npm:^9.5.2": - version: 9.5.14 - resolution: "postcss-preset-env@npm:9.5.14" + version: 9.5.15 + resolution: "postcss-preset-env@npm:9.5.15" dependencies: "@csstools/postcss-cascade-layers": "npm:^4.0.6" - "@csstools/postcss-color-function": "npm:^3.0.16" - "@csstools/postcss-color-mix-function": "npm:^2.0.16" - "@csstools/postcss-exponential-functions": "npm:^1.0.7" + "@csstools/postcss-color-function": "npm:^3.0.17" + "@csstools/postcss-color-mix-function": "npm:^2.0.17" + "@csstools/postcss-exponential-functions": "npm:^1.0.8" "@csstools/postcss-font-format-keywords": "npm:^3.0.2" - "@csstools/postcss-gamut-mapping": "npm:^1.0.9" - "@csstools/postcss-gradients-interpolation-method": "npm:^4.0.17" - "@csstools/postcss-hwb-function": "npm:^3.0.15" + "@csstools/postcss-gamut-mapping": "npm:^1.0.10" + "@csstools/postcss-gradients-interpolation-method": "npm:^4.0.18" + "@csstools/postcss-hwb-function": "npm:^3.0.16" "@csstools/postcss-ic-unit": "npm:^3.0.6" "@csstools/postcss-initial": "npm:^1.0.1" "@csstools/postcss-is-pseudo-class": "npm:^4.0.8" - "@csstools/postcss-light-dark-function": "npm:^1.0.5" + "@csstools/postcss-light-dark-function": "npm:^1.0.6" "@csstools/postcss-logical-float-and-clear": "npm:^2.0.1" "@csstools/postcss-logical-overflow": "npm:^1.0.1" "@csstools/postcss-logical-overscroll-behavior": "npm:^1.0.1" "@csstools/postcss-logical-resize": "npm:^2.0.1" - "@csstools/postcss-logical-viewport-units": "npm:^2.0.9" - "@csstools/postcss-media-minmax": "npm:^1.1.6" - "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^2.0.9" + "@csstools/postcss-logical-viewport-units": "npm:^2.0.10" + "@csstools/postcss-media-minmax": "npm:^1.1.7" + "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^2.0.10" "@csstools/postcss-nested-calc": "npm:^3.0.2" "@csstools/postcss-normalize-display-values": "npm:^3.0.2" - "@csstools/postcss-oklab-function": "npm:^3.0.16" + "@csstools/postcss-oklab-function": "npm:^3.0.17" "@csstools/postcss-progressive-custom-properties": "npm:^3.2.0" - "@csstools/postcss-relative-color-syntax": "npm:^2.0.16" + "@csstools/postcss-relative-color-syntax": "npm:^2.0.17" "@csstools/postcss-scope-pseudo-class": "npm:^3.0.1" - "@csstools/postcss-stepped-value-functions": "npm:^3.0.8" - "@csstools/postcss-text-decoration-shorthand": "npm:^3.0.6" - "@csstools/postcss-trigonometric-functions": "npm:^3.0.8" + "@csstools/postcss-stepped-value-functions": "npm:^3.0.9" + "@csstools/postcss-text-decoration-shorthand": "npm:^3.0.7" + "@csstools/postcss-trigonometric-functions": "npm:^3.0.9" "@csstools/postcss-unset-value": "npm:^3.0.1" autoprefixer: "npm:^10.4.19" - browserslist: "npm:^4.22.3" + browserslist: "npm:^4.23.1" css-blank-pseudo: "npm:^6.0.2" css-has-pseudo: "npm:^6.0.5" css-prefers-color-scheme: "npm:^9.0.1" cssdb: "npm:^8.0.0" postcss-attribute-case-insensitive: "npm:^6.0.3" postcss-clamp: "npm:^4.1.0" - postcss-color-functional-notation: "npm:^6.0.11" + postcss-color-functional-notation: "npm:^6.0.12" postcss-color-hex-alpha: "npm:^9.0.4" postcss-color-rebeccapurple: "npm:^9.0.3" - postcss-custom-media: "npm:^10.0.6" - postcss-custom-properties: "npm:^13.3.10" - postcss-custom-selectors: "npm:^7.1.10" + postcss-custom-media: "npm:^10.0.7" + postcss-custom-properties: "npm:^13.3.11" + postcss-custom-selectors: "npm:^7.1.11" postcss-dir-pseudo-class: "npm:^8.0.1" postcss-double-position-gradients: "npm:^5.0.6" postcss-focus-visible: "npm:^9.0.1" @@ -13815,7 +13815,7 @@ __metadata: postcss-font-variant: "npm:^5.0.0" postcss-gap-properties: "npm:^5.0.1" postcss-image-set-function: "npm:^6.0.3" - postcss-lab-function: "npm:^6.0.16" + postcss-lab-function: "npm:^6.0.17" postcss-logical: "npm:^7.0.1" postcss-nesting: "npm:^12.1.5" postcss-opacity-percentage: "npm:^2.0.0" @@ -13827,7 +13827,7 @@ __metadata: postcss-selector-not: "npm:^7.0.2" peerDependencies: postcss: ^8.4 - checksum: 10c0/8e0c8f5c2e7b8385a770c13185986dc50d7a73b10b98c65c2f86bb4cd2860de722caef8172b1676962dafbbc044d6be1955f2a092e951976a30d4ee33b0d7571 + checksum: 10c0/e2ee0b5d7dbaddb82ff6d51b5882120862d6be184973ae3d55642923183ab441d421d5f9810fe02e680a70dbc85b20b1c2eb02c68f167dcaf3ef80a71dd40e78 languageName: node linkType: hard From 3c1e1685c73fa69dec719cb0bf2847de6df31dd1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:21:55 +0200 Subject: [PATCH 47/84] fix(deps): update dependency postcss to v8.4.39 (#30877) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index b650d5f49..31b812df6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13951,13 +13951,13 @@ __metadata: linkType: hard "postcss@npm:^8.2.15, postcss@npm:^8.4.24, postcss@npm:^8.4.38": - version: 8.4.38 - resolution: "postcss@npm:8.4.38" + version: 8.4.39 + resolution: "postcss@npm:8.4.39" dependencies: nanoid: "npm:^3.3.7" - picocolors: "npm:^1.0.0" + picocolors: "npm:^1.0.1" source-map-js: "npm:^1.2.0" - checksum: 10c0/955407b8f70cf0c14acf35dab3615899a2a60a26718a63c848cf3c29f2467b0533991b985a2b994430d890bd7ec2b1963e36352b0774a19143b5f591540f7c06 + checksum: 10c0/16f5ac3c4e32ee76d1582b3c0dcf1a1fdb91334a45ad755eeb881ccc50318fb8d64047de4f1601ac96e30061df203f0f2e2edbdc0bfc49b9c57bc9fb9bedaea3 languageName: node linkType: hard From 2f1df842f8ba3ec064a560685f51489aee3a5ecf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:25:52 +0200 Subject: [PATCH 48/84] chore(deps): update dependency test-prof to v1.3.3.1 (#30872) 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 02437eab6..a1340537c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -833,7 +833,7 @@ GEM unicode-display_width (>= 1.1.1, < 3) terrapin (1.0.1) climate_control - test-prof (1.3.3) + test-prof (1.3.3.1) thor (1.3.1) tilt (2.3.0) timeout (0.4.1) From 4c701463b4717acfdf37d5992bf1d729c2221011 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:26:08 +0000 Subject: [PATCH 49/84] chore(deps): update dependency aws-sdk-s3 to v1.155.0 (#30871) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a1340537c..1d73a8ef5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,8 +100,8 @@ GEM attr_required (1.0.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.947.0) - aws-sdk-core (3.199.0) + aws-partitions (1.949.0) + aws-sdk-core (3.200.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) @@ -109,7 +109,7 @@ GEM aws-sdk-kms (1.87.0) aws-sdk-core (~> 3, >= 3.199.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.154.0) + aws-sdk-s3 (1.155.0) aws-sdk-core (~> 3, >= 3.199.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) From ed15ae075c00b72388d06f1d62b87a6ed2028e57 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:27:03 +0000 Subject: [PATCH 50/84] fix(deps): update dependency @reduxjs/toolkit to v2.2.6 (#30875) 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 31b812df6..5b4f3c22b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3058,8 +3058,8 @@ __metadata: linkType: hard "@reduxjs/toolkit@npm:^2.0.1": - version: 2.2.5 - resolution: "@reduxjs/toolkit@npm:2.2.5" + version: 2.2.6 + resolution: "@reduxjs/toolkit@npm:2.2.6" dependencies: immer: "npm:^10.0.3" redux: "npm:^5.0.1" @@ -3073,7 +3073,7 @@ __metadata: optional: true react-redux: optional: true - checksum: 10c0/be0593bf26852482fb8716b9248531466c6e8782a3114b823ae680fce90267d8c5512a3231cfecc30b17eff81a4604112772b49ad7ca6a3366ddd4f2a838e53c + checksum: 10c0/60af753e6d02c8acd3c5bc843c846d60b19821d93ff9f4415fa7011ebf17a85301ed42132fabc1aaee8523d8f61418b5ba164a11c31ab29937e485842d3744a0 languageName: node linkType: hard From 7756db65519136d92b04b8709681f62a971836f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:34:38 +0000 Subject: [PATCH 51/84] New Crowdin Translations (automated) (#30873) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/br.json | 2 ++ app/javascript/mastodon/locales/cs.json | 18 +++++++++++++----- app/javascript/mastodon/locales/de.json | 2 +- app/javascript/mastodon/locales/fo.json | 3 +++ app/javascript/mastodon/locales/ko.json | 3 +++ app/javascript/mastodon/locales/th.json | 3 +++ app/javascript/mastodon/locales/vi.json | 3 +++ config/locales/cs.yml | 3 +++ config/locales/doorkeeper.cs.yml | 1 + 9 files changed, 32 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index c919d2e9d..a150fb490 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -35,6 +35,7 @@ "account.follow_back": "Heuliañ d'ho tro", "account.followers": "Tud koumanantet", "account.followers.empty": "Den na heul an implijer·ez-mañ c'hoazh.", + "account.followers_counter": "{count, plural, one {{counter} heulier} two {{counter} heulier} few {{counter} heulier} many {{counter} heulier} other {{counter} heulier}}", "account.following": "Koumanantoù", "account.follows.empty": "An implijer·ez-mañ na heul den ebet.", "account.go_to_profile": "Gwelet ar profil", @@ -60,6 +61,7 @@ "account.requested_follow": "Gant {name} eo bet goulennet ho heuliañ", "account.share": "Skignañ profil @{name}", "account.show_reblogs": "Diskouez skignadennoù @{name}", + "account.statuses_counter": "{count, plural, one {{counter} embannadur} two {{counter} embannadur} few {{counter} embannadur} many {{counter} embannadur} other {{counter} embannadur}}", "account.unblock": "Diverzañ @{name}", "account.unblock_domain": "Diverzañ an domani {domain}", "account.unblock_short": "Distankañ", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 66aa1fe0a..e96e28397 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -20,7 +20,7 @@ "account.block_short": "Zablokovat", "account.blocked": "Blokovaný", "account.browse_more_on_origin_server": "Více na původním profilu", - "account.cancel_follow_request": "Zrušit žádost o sledování", + "account.cancel_follow_request": "Zrušit sledování", "account.copy": "Kopírovat odkaz na profil", "account.direct": "Soukromě zmínit @{name}", "account.disable_notifications": "Přestat mě upozorňovat, když @{name} zveřejní příspěvek", @@ -35,7 +35,9 @@ "account.follow_back": "Také sledovat", "account.followers": "Sledující", "account.followers.empty": "Tohoto uživatele zatím nikdo nesleduje.", + "account.followers_counter": "{count, plural, one {{counter} sledující} few {{counter} sledující} many {{counter} sledujících} other {{counter} sledujících}}", "account.following": "Sledujete", + "account.following_counter": "{count, plural, one {{counter} sledovaný} few {{counter} sledovaní} many {{counter} sledovaných} other {{counter} sledovaných}}", "account.follows.empty": "Tento uživatel zatím nikoho nesleduje.", "account.go_to_profile": "Přejít na profil", "account.hide_reblogs": "Skrýt boosty od @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} tě požádal o sledování", "account.share": "Sdílet profil @{name}", "account.show_reblogs": "Zobrazit boosty od @{name}", + "account.statuses_counter": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}", "account.unblock": "Odblokovat @{name}", "account.unblock_domain": "Odblokovat doménu {domain}", "account.unblock_short": "Odblokovat", @@ -75,9 +78,9 @@ "admin.dashboard.retention.average": "Průměr", "admin.dashboard.retention.cohort": "Měsíc registrace", "admin.dashboard.retention.cohort_size": "Noví uživatelé", - "admin.impact_report.instance_accounts": "Profily účtů, které by odstranily", - "admin.impact_report.instance_followers": "Sledovatelé, o které by naši uživatelé přišli", - "admin.impact_report.instance_follows": "Následovníci jejich uživatelé by ztratili", + "admin.impact_report.instance_accounts": "Profily účtů, které by byli odstaněny", + "admin.impact_report.instance_followers": "Sledující, o které by naši uživatelé přišli", + "admin.impact_report.instance_follows": "Sledující, o které by naši uživatelé přišli", "admin.impact_report.title": "Shrnutí dopadu", "alert.rate_limited.message": "Zkuste to prosím znovu po {retry_time, time, medium}.", "alert.rate_limited.title": "Spojení omezena", @@ -86,7 +89,7 @@ "announcement.announcement": "Oznámení", "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 být stále 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 stále být 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.", @@ -411,6 +414,8 @@ "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.account.add": "Přidat do seznamu", "lists.account.remove": "Odebrat ze seznamu", "lists.delete": "Smazat seznam", @@ -691,8 +696,11 @@ "server_banner.about_active_users": "Lidé používající tento server během posledních 30 dní (měsíční aktivní uživatelé)", "server_banner.active_users": "aktivní uživatelé", "server_banner.administered_by": "Spravováno:", + "server_banner.is_one_of_many": "{domain} je jedním z mnoha Mastodon serverů, které můžete použít k účasti na fediversu.", "server_banner.server_stats": "Statistiky serveru:", "sign_in_banner.create_account": "Vytvořit účet", + "sign_in_banner.follow_anyone": "Sledujte kohokoli napříč fediversem a uvidíte vše v chronologickém pořadí. Bez algoritmů, reklam a clickbaitu.", + "sign_in_banner.mastodon_is": "Mastodon je ten nejlepší způsob, jak udržet krok s tím, co se právě děje.", "sign_in_banner.sign_in": "Přihlásit se", "sign_in_banner.sso_redirect": "Přihlášení nebo Registrace", "status.admin_account": "Otevřít moderátorské rozhraní pro @{name}", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 4a5b666d3..86438757a 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -37,7 +37,7 @@ "account.followers.empty": "Diesem Profil folgt noch niemand.", "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}", "account.following": "Folge ich", - "account.following_counter": "{count, plural, one {{counter} folge ich} other {{counter} folge ich}}", + "account.following_counter": "{count, plural, one {{counter} Folge ich} other {{counter} Folge ich}}", "account.follows.empty": "Dieses Profil folgt noch niemandem.", "account.go_to_profile": "Profil aufrufen", "account.hide_reblogs": "Geteilte Beiträge von @{name} ausblenden", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index e7786f388..c27ffe0aa 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -35,7 +35,9 @@ "account.follow_back": "Fylg aftur", "account.followers": "Fylgjarar", "account.followers.empty": "Ongar fylgjarar enn.", + "account.followers_counter": "{count, plural, one {{counter} fylgjari} other {{counter} fylgjarar}}", "account.following": "Fylgir", + "account.following_counter": "{count, plural, one {{counter} fylgir} other {{counter} fylgja}}", "account.follows.empty": "Hesin brúkari fylgir ongum enn.", "account.go_to_profile": "Far til vanga", "account.hide_reblogs": "Fjal lyft frá @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} hevur biðið um at fylgja tær", "account.share": "Deil vanga @{name}'s", "account.show_reblogs": "Vís lyft frá @{name}", + "account.statuses_counter": "{count, plural, one {{counter} postur} other {{counter} postar}}", "account.unblock": "Banna ikki @{name}", "account.unblock_domain": "Banna ikki økisnavnið {domain}", "account.unblock_short": "Banna ikki", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 90755666b..fe3582c1d 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -35,7 +35,9 @@ "account.follow_back": "맞팔로우 하기", "account.followers": "팔로워", "account.followers.empty": "아직 아무도 이 사용자를 팔로우하고 있지 않습니다.", + "account.followers_counter": "{count, plural, other {{counter} 팔로워}}", "account.following": "팔로잉", + "account.following_counter": "{count, plural, other {{counter} 팔로잉}}", "account.follows.empty": "이 사용자는 아직 아무도 팔로우하고 있지 않습니다.", "account.go_to_profile": "프로필로 이동", "account.hide_reblogs": "@{name}의 부스트를 숨기기", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} 님이 팔로우 요청을 보냈습니다", "account.share": "@{name}의 프로필 공유", "account.show_reblogs": "@{name}의 부스트 보기", + "account.statuses_counter": "{count, plural, other {{counter} 게시물}}", "account.unblock": "차단 해제", "account.unblock_domain": "도메인 {domain} 차단 해제", "account.unblock_short": "차단 해제", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index e1d556ebf..64abb394b 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -35,7 +35,9 @@ "account.follow_back": "ติดตามกลับ", "account.followers": "ผู้ติดตาม", "account.followers.empty": "ยังไม่มีใครติดตามผู้ใช้นี้", + "account.followers_counter": "{count, plural, other {{counter} ผู้ติดตาม}}", "account.following": "กำลังติดตาม", + "account.following_counter": "{count, plural, other {{counter} กำลังติดตาม}}", "account.follows.empty": "ผู้ใช้นี้ยังไม่ได้ติดตามใคร", "account.go_to_profile": "ไปยังโปรไฟล์", "account.hide_reblogs": "ซ่อนการดันจาก @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} ได้ขอติดตามคุณ", "account.share": "แชร์โปรไฟล์ของ @{name}", "account.show_reblogs": "แสดงการดันจาก @{name}", + "account.statuses_counter": "{count, plural, other {{counter} โพสต์}}", "account.unblock": "เลิกปิดกั้น @{name}", "account.unblock_domain": "เลิกปิดกั้นโดเมน {domain}", "account.unblock_short": "เลิกปิดกั้น", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index bbfecf2c8..70932d10b 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -35,7 +35,9 @@ "account.follow_back": "Theo dõi lại", "account.followers": "Người theo dõi", "account.followers.empty": "Chưa có người theo dõi nào.", + "account.followers_counter": "{count, plural, other {{counter} người theo dõi}}", "account.following": "Đang theo dõi", + "account.following_counter": "{count, plural, other {{counter} đang theo dõi}}", "account.follows.empty": "Người này chưa theo dõi ai.", "account.go_to_profile": "Xem hồ sơ", "account.hide_reblogs": "Ẩn tút @{name} đăng lại", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} yêu cầu theo dõi bạn", "account.share": "Chia sẻ @{name}", "account.show_reblogs": "Hiện tút do @{name} đăng lại", + "account.statuses_counter": "{count, plural, other {{counter} tút}}", "account.unblock": "Bỏ chặn @{name}", "account.unblock_domain": "Bỏ ẩn {domain}", "account.unblock_short": "Bỏ chặn", diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 17c743f1d..f3b8f27d8 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -291,6 +291,7 @@ cs: update_custom_emoji_html: Uživatel %{name} aktualizoval emoji %{target} update_domain_block_html: "%{name} aktualizoval blokaci domény %{target}" update_ip_block_html: "%{name} změnil pravidlo pro IP %{target}" + update_report_html: "%{name} aktualizoval hlášení %{target}" update_status_html: Uživatel %{name} aktualizoval příspěvek uživatele %{target} update_user_role_html: "%{name} změnil %{target} roli" deleted_account: smazaný účet @@ -298,6 +299,7 @@ cs: filter_by_action: Filtrovat podle akce filter_by_user: Filtrovat podle uživatele title: Protokol auditu + unavailable_instance: "(doména není k dispozici)" announcements: destroyed_msg: Oznámení bylo úspěšně odstraněno! edit: @@ -984,6 +986,7 @@ cs: delete: Smazat edit_preset: Upravit předlohu pro varování empty: Zatím jste nedefinovali žádné předlohy varování. + title: Předvolby varování webhooks: add_new: Přidat koncový bod delete: Smazat diff --git a/config/locales/doorkeeper.cs.yml b/config/locales/doorkeeper.cs.yml index 332383468..3101779ed 100644 --- a/config/locales/doorkeeper.cs.yml +++ b/config/locales/doorkeeper.cs.yml @@ -166,6 +166,7 @@ cs: admin:write:reports: provádět moderátorské akce s hlášeními crypto: používat end-to-end šifrování follow: upravovat vztahy mezi profily + profile: číst pouze základní informace o vašem účtu push: přijímat vaše push oznámení read: vidět všechna data vašeho účtu read:accounts: vidět informace o účtech From aefb4c027b035bc3022343157d31117e7ae531b4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:35:19 +0000 Subject: [PATCH 52/84] chore(deps): update dependency rubocop-rails to v2.25.1 (#30876) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1d73a8ef5..42cc0e198 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -696,7 +696,7 @@ GEM responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.3.0) + rexml (3.3.1) strscan rotp (6.3.0) rouge (4.2.1) @@ -751,7 +751,7 @@ GEM rubocop-performance (1.21.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.25.0) + rubocop-rails (2.25.1) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) From aeefe5b2bea6e0fb511f029c2aacdefa7bd41eb8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 09:03:38 +0000 Subject: [PATCH 53/84] chore(deps): update eslint (non-major) (#30883) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- yarn.lock | 357 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 241 insertions(+), 120 deletions(-) diff --git a/package.json b/package.json index 3eb1ebad7..0379c7a5f 100644 --- a/package.json +++ b/package.json @@ -178,8 +178,8 @@ "eslint-plugin-formatjs": "^4.10.1", "eslint-plugin-import": "~2.29.0", "eslint-plugin-jsdoc": "^48.0.0", - "eslint-plugin-jsx-a11y": "~6.8.0", - "eslint-plugin-promise": "~6.2.0", + "eslint-plugin-jsx-a11y": "~6.9.0", + "eslint-plugin-promise": "~6.4.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^9.0.11", diff --git a/yarn.lock b/yarn.lock index 5b4f3c22b..9d19cb837 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1502,7 +1502,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.24.7 resolution: "@babel/runtime@npm:7.24.7" dependencies: @@ -2830,8 +2830,8 @@ __metadata: eslint-plugin-formatjs: "npm:^4.10.1" eslint-plugin-import: "npm:~2.29.0" eslint-plugin-jsdoc: "npm:^48.0.0" - eslint-plugin-jsx-a11y: "npm:~6.8.0" - eslint-plugin-promise: "npm:~6.2.0" + eslint-plugin-jsx-a11y: "npm:~6.9.0" + eslint-plugin-promise: "npm:~6.4.0" eslint-plugin-react: "npm:^7.33.2" eslint-plugin-react-hooks: "npm:^4.6.0" file-loader: "npm:^6.2.0" @@ -3036,6 +3036,13 @@ __metadata: languageName: node linkType: hard +"@pkgr/core@npm:^0.1.0": + version: 0.1.1 + resolution: "@pkgr/core@npm:0.1.1" + checksum: 10c0/3f7536bc7f57320ab2cf96f8973664bef624710c403357429fbf680a5c3b4843c1dbd389bb43daa6b1f6f1f007bb082f5abcb76bb2b5dc9f421647743b71d3d8 + languageName: node + linkType: hard + "@polka/url@npm:^1.0.0-next.20": version: 1.0.0-next.21 resolution: "@polka/url@npm:1.0.0-next.21" @@ -4120,14 +4127,14 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^7.0.0": - version: 7.11.0 - resolution: "@typescript-eslint/eslint-plugin@npm:7.11.0" + version: 7.14.1 + resolution: "@typescript-eslint/eslint-plugin@npm:7.14.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:7.11.0" - "@typescript-eslint/type-utils": "npm:7.11.0" - "@typescript-eslint/utils": "npm:7.11.0" - "@typescript-eslint/visitor-keys": "npm:7.11.0" + "@typescript-eslint/scope-manager": "npm:7.14.1" + "@typescript-eslint/type-utils": "npm:7.14.1" + "@typescript-eslint/utils": "npm:7.14.1" + "@typescript-eslint/visitor-keys": "npm:7.14.1" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -4138,25 +4145,25 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/50fedf832e4de9546569106eab1d10716204ceebc5cc7d62299112c881212270d0f7857e3d6452c07db031d40b58cf27c4d1b1a36043e8e700fc3496e377b54a + checksum: 10c0/7c2b9b98a38d78326b0ff7348fe001203eda10817ca7834a7a01f492ae7c2508469bbafaa933208d6459f8ff6685277685983cf6f6843e556a6ab2aa5c05080c languageName: node linkType: hard "@typescript-eslint/parser@npm:^7.0.0": - version: 7.11.0 - resolution: "@typescript-eslint/parser@npm:7.11.0" + version: 7.14.1 + resolution: "@typescript-eslint/parser@npm:7.14.1" dependencies: - "@typescript-eslint/scope-manager": "npm:7.11.0" - "@typescript-eslint/types": "npm:7.11.0" - "@typescript-eslint/typescript-estree": "npm:7.11.0" - "@typescript-eslint/visitor-keys": "npm:7.11.0" + "@typescript-eslint/scope-manager": "npm:7.14.1" + "@typescript-eslint/types": "npm:7.14.1" + "@typescript-eslint/typescript-estree": "npm:7.14.1" + "@typescript-eslint/visitor-keys": "npm:7.14.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/f5d1343fae90ccd91aea8adf194e22ed3eb4b2ea79d03d8a9ca6e7b669a6db306e93138ec64f7020c5b3128619d50304dea1f06043eaff6b015071822cad4972 + checksum: 10c0/db3169d4852685cfb27db741c557f58a3e52104bfacc7621beb7c94ec36ac2a08d4e410ac86745db52f482fbfc87e99fa0a26c1d7a10d37a215cce85e1661f0e languageName: node linkType: hard @@ -4170,22 +4177,22 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:7.11.0": - version: 7.11.0 - resolution: "@typescript-eslint/scope-manager@npm:7.11.0" +"@typescript-eslint/scope-manager@npm:7.14.1": + version: 7.14.1 + resolution: "@typescript-eslint/scope-manager@npm:7.14.1" dependencies: - "@typescript-eslint/types": "npm:7.11.0" - "@typescript-eslint/visitor-keys": "npm:7.11.0" - checksum: 10c0/35f9d88f38f2366017b15c9ee752f2605afa8009fa1eaf81c8b2b71fc22ddd2a33fff794a02015c8991a5fa99f315c3d6d76a5957d3fad1ccbb4cd46735c98b5 + "@typescript-eslint/types": "npm:7.14.1" + "@typescript-eslint/visitor-keys": "npm:7.14.1" + checksum: 10c0/f8c05a0d6f8de4cc19b90a4da308817c66e53f36f7ec48f6cc23e93c7399bc418643d8135933aaf5fc013199cbef0e1ea4223f5147db5ca401b239eaf087011e languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:7.11.0": - version: 7.11.0 - resolution: "@typescript-eslint/type-utils@npm:7.11.0" +"@typescript-eslint/type-utils@npm:7.14.1": + version: 7.14.1 + resolution: "@typescript-eslint/type-utils@npm:7.14.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:7.11.0" - "@typescript-eslint/utils": "npm:7.11.0" + "@typescript-eslint/typescript-estree": "npm:7.14.1" + "@typescript-eslint/utils": "npm:7.14.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" peerDependencies: @@ -4193,7 +4200,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/637395cb0f4c424c610e751906a31dcfedcdbd8c479012da6e81f9be6b930f32317bfe170ccb758d93a411b2bd9c4e7e5d18892094466099c6f9c3dceda81a72 + checksum: 10c0/bd1c4a8db6273e24156fb10da2cbeb52b4eb03f819da193d4b6bd5a95db3b5524c6fe00d088308d8855b9ae60a3b82afa3a06e89982a09a8573561da960758fd languageName: node linkType: hard @@ -4204,10 +4211,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:7.11.0, @typescript-eslint/types@npm:^7.2.0": - version: 7.11.0 - resolution: "@typescript-eslint/types@npm:7.11.0" - checksum: 10c0/c5d6c517124017eb44aa180c8ea1fad26ec8e47502f92fd12245ba3141560e69d7f7e35b8aa160ddd5df63a2952af407e2f62cc58b663c86e1f778ffb5b01789 +"@typescript-eslint/types@npm:7.14.1, @typescript-eslint/types@npm:^7.2.0": + version: 7.14.1 + resolution: "@typescript-eslint/types@npm:7.14.1" + checksum: 10c0/5b7bda83c47a9b386482e63447c6b0ed7bd4e82eb43f11a180c6e2f3d2e7a2828f57bcbed82196ad761c49e363cccf4c81a89f1fc976e9f5f0a79dcc928fa2d2 languageName: node linkType: hard @@ -4230,12 +4237,12 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:7.11.0": - version: 7.11.0 - resolution: "@typescript-eslint/typescript-estree@npm:7.11.0" +"@typescript-eslint/typescript-estree@npm:7.14.1": + version: 7.14.1 + resolution: "@typescript-eslint/typescript-estree@npm:7.14.1" dependencies: - "@typescript-eslint/types": "npm:7.11.0" - "@typescript-eslint/visitor-keys": "npm:7.11.0" + "@typescript-eslint/types": "npm:7.14.1" + "@typescript-eslint/visitor-keys": "npm:7.14.1" debug: "npm:^4.3.4" globby: "npm:^11.1.0" is-glob: "npm:^4.0.3" @@ -4245,21 +4252,21 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/a4eda43f352d20edebae0c1c221c4fd9de0673a94988cf1ae3f5e4917ef9cdb9ead8d3673ea8dd6e80d9cf3523a47c295be1326a3fae017b277233f4c4b4026b + checksum: 10c0/a8da9bcc4de3334a225424946abd99374de05c42098455419224bc0f46bb1b66115f8bd6ae268461294b90943ed4a407bcd255c0fa60eb76ba4cdc5fc7c20855 languageName: node linkType: hard -"@typescript-eslint/utils@npm:7.11.0": - version: 7.11.0 - resolution: "@typescript-eslint/utils@npm:7.11.0" +"@typescript-eslint/utils@npm:7.14.1": + version: 7.14.1 + resolution: "@typescript-eslint/utils@npm:7.14.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:7.11.0" - "@typescript-eslint/types": "npm:7.11.0" - "@typescript-eslint/typescript-estree": "npm:7.11.0" + "@typescript-eslint/scope-manager": "npm:7.14.1" + "@typescript-eslint/types": "npm:7.14.1" + "@typescript-eslint/typescript-estree": "npm:7.14.1" peerDependencies: eslint: ^8.56.0 - checksum: 10c0/539a7ff8b825ad810fc59a80269094748df1a397a42cdbb212c493fc2486711c7d8fd6d75d4cd8a067822b8e6a11f42c50441977d51c183eec47992506d1cdf8 + checksum: 10c0/c7f635a3c2c6c085e1d51a52088e55cad9d7e1257b1f60378e5eeb6eb0871db027d42747e9ef60a2f557cf9dd68b2ce014d488d795db8f771506290b164b0e5a languageName: node linkType: hard @@ -4290,13 +4297,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:7.11.0": - version: 7.11.0 - resolution: "@typescript-eslint/visitor-keys@npm:7.11.0" +"@typescript-eslint/visitor-keys@npm:7.14.1": + version: 7.14.1 + resolution: "@typescript-eslint/visitor-keys@npm:7.14.1" dependencies: - "@typescript-eslint/types": "npm:7.11.0" + "@typescript-eslint/types": "npm:7.14.1" eslint-visitor-keys: "npm:^3.4.3" - checksum: 10c0/664e558d9645896484b7ffc9381837f0d52443bf8d121a5586d02d42ca4d17dc35faf526768c4b1beb52c57c43fae555898eb087651eb1c7a3d60f1085effea1 + checksum: 10c0/39ac489990fcfdcee442f27658431a0eb44ccf694f701a45df2a108c47cea9582e0955bff0d449047549149385f72895a5d7e6c1622ece1fe32594b7cecb85f3 languageName: node linkType: hard @@ -4786,7 +4793,7 @@ __metadata: languageName: node linkType: hard -"aria-query@npm:5.3.0, aria-query@npm:^5.0.0, aria-query@npm:^5.3.0": +"aria-query@npm:5.3.0, aria-query@npm:^5.0.0": version: 5.3.0 resolution: "aria-query@npm:5.3.0" dependencies: @@ -4795,6 +4802,15 @@ __metadata: languageName: node linkType: hard +"aria-query@npm:~5.1.3": + version: 5.1.3 + resolution: "aria-query@npm:5.1.3" + dependencies: + deep-equal: "npm:^2.0.5" + checksum: 10c0/edcbc8044c4663d6f88f785e983e6784f98cb62b4ba1e9dd8d61b725d0203e4cfca38d676aee984c31f354103461102a3d583aa4fbe4fd0a89b679744f4e5faf + languageName: node + linkType: hard + "arr-diff@npm:^4.0.0": version: 4.0.0 resolution: "arr-diff@npm:4.0.0" @@ -4816,7 +4832,7 @@ __metadata: languageName: node linkType: hard -"array-buffer-byte-length@npm:^1.0.1": +"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1": version: 1.0.1 resolution: "array-buffer-byte-length@npm:1.0.1" dependencies: @@ -4960,16 +4976,16 @@ __metadata: languageName: node linkType: hard -"array.prototype.tosorted@npm:^1.1.3": - version: 1.1.3 - resolution: "array.prototype.tosorted@npm:1.1.3" +"array.prototype.tosorted@npm:^1.1.4": + version: 1.1.4 + resolution: "array.prototype.tosorted@npm:1.1.4" dependencies: - call-bind: "npm:^1.0.5" + call-bind: "npm:^1.0.7" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.22.3" - es-errors: "npm:^1.1.0" + es-abstract: "npm:^1.23.3" + es-errors: "npm:^1.3.0" es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/a27e1ca51168ecacf6042901f5ef021e43c8fa04b6c6b6f2a30bac3645cd2b519cecbe0bc45db1b85b843f64dc3207f0268f700b4b9fbdec076d12d432cf0865 + checksum: 10c0/eb3c4c4fc0381b0bf6dba2ea4d48d367c2827a0d4236a5718d97caaccc6b78f11f4cadf090736e86301d295a6aa4967ed45568f92ced51be8cbbacd9ca410943 languageName: node linkType: hard @@ -5135,10 +5151,10 @@ __metadata: languageName: node linkType: hard -"axe-core@npm:=4.7.0": - version: 4.7.0 - resolution: "axe-core@npm:4.7.0" - checksum: 10c0/89ac5712b5932ac7d23398b4cb5ba081c394a086e343acc68ba49c83472706e18e0799804e8388c779dcdacc465377deb29f2714241d3fbb389cf3a6b275c9ba +"axe-core@npm:^4.9.1": + version: 4.9.1 + resolution: "axe-core@npm:4.9.1" + checksum: 10c0/ac9e5a0c6fa115a43ebffc32a1d2189e1ca6431b5a78e88cdcf94a72a25c5964185682edd94fe6bdb1cb4266c0d06301b022866e0e50dcdf6e3cefe556470110 languageName: node linkType: hard @@ -5153,12 +5169,12 @@ __metadata: languageName: node linkType: hard -"axobject-query@npm:^3.2.1": - version: 3.2.1 - resolution: "axobject-query@npm:3.2.1" +"axobject-query@npm:~3.1.1": + version: 3.1.1 + resolution: "axobject-query@npm:3.1.1" dependencies: - dequal: "npm:^2.0.3" - checksum: 10c0/f7debc2012e456139b57d888c223f6d3cb4b61eb104164a85e3d346273dd6ef0bc9a04b6660ca9407704a14a8e05fa6b6eb9d55f44f348c7210de7ffb350c3a7 + deep-equal: "npm:^2.0.5" + checksum: 10c0/fff3175a22fd1f41fceb7ae0cd25f6594a0d7fba28c2335dd904538b80eb4e1040432564a3c643025cd2bb748f68d35aaabffb780b794da97ecfc748810b25ad languageName: node linkType: hard @@ -6996,6 +7012,32 @@ __metadata: languageName: node linkType: hard +"deep-equal@npm:^2.0.5": + version: 2.2.3 + resolution: "deep-equal@npm:2.2.3" + dependencies: + array-buffer-byte-length: "npm:^1.0.0" + call-bind: "npm:^1.0.5" + es-get-iterator: "npm:^1.1.3" + get-intrinsic: "npm:^1.2.2" + is-arguments: "npm:^1.1.1" + is-array-buffer: "npm:^3.0.2" + is-date-object: "npm:^1.0.5" + is-regex: "npm:^1.1.4" + is-shared-array-buffer: "npm:^1.0.2" + isarray: "npm:^2.0.5" + object-is: "npm:^1.1.5" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.4" + regexp.prototype.flags: "npm:^1.5.1" + side-channel: "npm:^1.0.4" + which-boxed-primitive: "npm:^1.0.2" + which-collection: "npm:^1.0.1" + which-typed-array: "npm:^1.1.13" + checksum: 10c0/a48244f90fa989f63ff5ef0cc6de1e4916b48ea0220a9c89a378561960814794a5800c600254482a2c8fd2e49d6c2e196131dc983976adb024c94a42dfe4949f + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -7578,7 +7620,7 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.2, es-abstract@npm:^1.20.4, es-abstract@npm:^1.21.2, es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3": +"es-abstract@npm:^1.17.2, es-abstract@npm:^1.17.5, es-abstract@npm:^1.20.4, es-abstract@npm:^1.21.2, es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3": version: 1.23.3 resolution: "es-abstract@npm:1.23.3" dependencies: @@ -7648,14 +7690,31 @@ __metadata: languageName: node linkType: hard -"es-errors@npm:^1.1.0, es-errors@npm:^1.2.1, es-errors@npm:^1.3.0": +"es-errors@npm:^1.2.1, es-errors@npm:^1.3.0": version: 1.3.0 resolution: "es-errors@npm:1.3.0" checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 languageName: node linkType: hard -"es-iterator-helpers@npm:^1.0.15, es-iterator-helpers@npm:^1.0.19": +"es-get-iterator@npm:^1.1.3": + version: 1.1.3 + resolution: "es-get-iterator@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.2" + get-intrinsic: "npm:^1.1.3" + has-symbols: "npm:^1.0.3" + is-arguments: "npm:^1.1.1" + is-map: "npm:^2.0.2" + is-set: "npm:^2.0.2" + is-string: "npm:^1.0.7" + isarray: "npm:^2.0.5" + stop-iteration-iterator: "npm:^1.0.0" + checksum: 10c0/ebd11effa79851ea75d7f079405f9d0dc185559fd65d986c6afea59a0ff2d46c2ed8675f19f03dce7429d7f6c14ff9aede8d121fbab78d75cfda6a263030bac0 + languageName: node + linkType: hard + +"es-iterator-helpers@npm:^1.0.19": version: 1.0.19 resolution: "es-iterator-helpers@npm:1.0.19" dependencies: @@ -7677,6 +7736,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^1.5.3": + version: 1.5.4 + resolution: "es-module-lexer@npm:1.5.4" + checksum: 10c0/300a469488c2f22081df1e4c8398c78db92358496e639b0df7f89ac6455462aaf5d8893939087c1a1cbcbf20eed4610c70e0bcb8f3e4b0d80a5d2611c539408c + languageName: node + linkType: hard + "es-object-atoms@npm:^1.0.0": version: 1.0.0 resolution: "es-object-atoms@npm:1.0.0" @@ -7867,8 +7933,8 @@ __metadata: linkType: hard "eslint-plugin-jsdoc@npm:^48.0.0": - version: 48.2.7 - resolution: "eslint-plugin-jsdoc@npm:48.2.7" + version: 48.5.0 + resolution: "eslint-plugin-jsdoc@npm:48.5.0" dependencies: "@es-joy/jsdoccomment": "npm:~0.43.1" are-docs-informative: "npm:^0.0.2" @@ -7876,46 +7942,48 @@ __metadata: debug: "npm:^4.3.4" escape-string-regexp: "npm:^4.0.0" esquery: "npm:^1.5.0" + parse-imports: "npm:^2.1.0" semver: "npm:^7.6.2" spdx-expression-parse: "npm:^4.0.0" + synckit: "npm:^0.9.0" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/74d0f95b3d880dd4221dbc0b9341266a6cce3b8ca8d3e30032223af3552364643d6b82ad733d9bc06a20f0d640f21e4d8f5a4b00901d1771572625178b8c40c3 + checksum: 10c0/1c5eb83df06cb228e44ad2c9da5b31987347a45b99d9e7a68957d178487a81603ad3c4c7db1ecba7e8a62d7ae20d9de1aec18a8cf2aa0e9169731cec54f78ab7 languageName: node linkType: hard -"eslint-plugin-jsx-a11y@npm:~6.8.0": - version: 6.8.0 - resolution: "eslint-plugin-jsx-a11y@npm:6.8.0" +"eslint-plugin-jsx-a11y@npm:~6.9.0": + version: 6.9.0 + resolution: "eslint-plugin-jsx-a11y@npm:6.9.0" dependencies: - "@babel/runtime": "npm:^7.23.2" - aria-query: "npm:^5.3.0" - array-includes: "npm:^3.1.7" + aria-query: "npm:~5.1.3" + array-includes: "npm:^3.1.8" array.prototype.flatmap: "npm:^1.3.2" ast-types-flow: "npm:^0.0.8" - axe-core: "npm:=4.7.0" - axobject-query: "npm:^3.2.1" + axe-core: "npm:^4.9.1" + axobject-query: "npm:~3.1.1" damerau-levenshtein: "npm:^1.0.8" emoji-regex: "npm:^9.2.2" - es-iterator-helpers: "npm:^1.0.15" - hasown: "npm:^2.0.0" + es-iterator-helpers: "npm:^1.0.19" + hasown: "npm:^2.0.2" jsx-ast-utils: "npm:^3.3.5" language-tags: "npm:^1.0.9" minimatch: "npm:^3.1.2" - object.entries: "npm:^1.1.7" - object.fromentries: "npm:^2.0.7" + object.fromentries: "npm:^2.0.8" + safe-regex-test: "npm:^1.0.3" + string.prototype.includes: "npm:^2.0.0" peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: 10c0/199b883e526e6f9d7c54cb3f094abc54f11a1ec816db5fb6cae3b938eb0e503acc10ccba91ca7451633a9d0b9abc0ea03601844a8aba5fe88c5e8897c9ac8f49 + checksum: 10c0/72ac719ca90b6149c8f3c708ac5b1177f6757668b6e174d72a78512d4ac10329331b9c666c21e9561237a96a45d7f147f6a5d270dadbb99eb4ee093f127792c3 languageName: node linkType: hard -"eslint-plugin-promise@npm:~6.2.0": - version: 6.2.0 - resolution: "eslint-plugin-promise@npm:6.2.0" +"eslint-plugin-promise@npm:~6.4.0": + version: 6.4.0 + resolution: "eslint-plugin-promise@npm:6.4.0" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/5f42ee774023c089453ecb792076c64c6d0739ea6e9d6cdc9d6a63da5ba928c776e349d01cc110548f2c67045ec55343136aa7eb8b486e4ab145ac016c06a492 + checksum: 10c0/5d07be976504f92d1d91756b0b0588a4c65e379af2520dd77c8655203085c0ab43e24d4698d1ac4b50926430cd8eb81cd1cc4c3653aae8386c805577bdf57b6c languageName: node linkType: hard @@ -7929,14 +7997,14 @@ __metadata: linkType: hard "eslint-plugin-react@npm:^7.33.2": - version: 7.34.2 - resolution: "eslint-plugin-react@npm:7.34.2" + version: 7.34.3 + resolution: "eslint-plugin-react@npm:7.34.3" dependencies: array-includes: "npm:^3.1.8" array.prototype.findlast: "npm:^1.2.5" array.prototype.flatmap: "npm:^1.3.2" array.prototype.toreversed: "npm:^1.1.2" - array.prototype.tosorted: "npm:^1.1.3" + array.prototype.tosorted: "npm:^1.1.4" doctrine: "npm:^2.1.0" es-iterator-helpers: "npm:^1.0.19" estraverse: "npm:^5.3.0" @@ -7952,7 +8020,7 @@ __metadata: string.prototype.matchall: "npm:^4.0.11" peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: 10c0/37dc04424da8626f20a071466e7238d53ed111c53e5e5398d813ac2cf76a2078f00d91f7833fe5b2f0fc98f2688a75b36e78e9ada9f1068705d23c7031094316 + checksum: 10c0/60717e32c9948e2b4ddc53dac7c4b62c68fc7129c3249079191c941c08ebe7d1f4793d65182922d19427c2a6634e05231a7b74ceee34169afdfd0e43d4a43d26 languageName: node linkType: hard @@ -8821,7 +8889,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": +"get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": version: 1.2.4 resolution: "get-intrinsic@npm:1.2.4" dependencies: @@ -9661,7 +9729,7 @@ __metadata: languageName: node linkType: hard -"internal-slot@npm:^1.0.7": +"internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.7": version: 1.0.7 resolution: "internal-slot@npm:1.0.7" dependencies: @@ -9777,7 +9845,7 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.0.4": +"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" dependencies: @@ -9787,7 +9855,7 @@ __metadata: languageName: node linkType: hard -"is-array-buffer@npm:^3.0.4": +"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4": version: 3.0.4 resolution: "is-array-buffer@npm:3.0.4" dependencies: @@ -10043,10 +10111,10 @@ __metadata: languageName: node linkType: hard -"is-map@npm:^2.0.1": - version: 2.0.2 - resolution: "is-map@npm:2.0.2" - checksum: 10c0/119ff9137a37fd131a72fab3f4ab8c9d6a24b0a1ee26b4eff14dc625900d8675a97785eea5f4174265e2006ed076cc24e89f6e57ebd080a48338d914ec9168a5 +"is-map@npm:^2.0.1, is-map@npm:^2.0.2": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc languageName: node linkType: hard @@ -10168,10 +10236,10 @@ __metadata: languageName: node linkType: hard -"is-set@npm:^2.0.1": - version: 2.0.2 - resolution: "is-set@npm:2.0.2" - checksum: 10c0/5f8bd1880df8c0004ce694e315e6e1e47a3452014be792880bb274a3b2cdb952fdb60789636ca6e084c7947ca8b7ae03ccaf54c93a7fcfed228af810559e5432 +"is-set@npm:^2.0.1, is-set@npm:^2.0.2": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 languageName: node linkType: hard @@ -12366,13 +12434,13 @@ __metadata: languageName: node linkType: hard -"object-is@npm:^1.0.1": - version: 1.1.5 - resolution: "object-is@npm:1.1.5" +"object-is@npm:^1.0.1, object-is@npm:^1.1.5": + version: 1.1.6 + resolution: "object-is@npm:1.1.6" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.1.3" - checksum: 10c0/8c263fb03fc28f1ffb54b44b9147235c5e233dc1ca23768e7d2569740b5d860154d7cc29a30220fe28ed6d8008e2422aefdebfe987c103e1c5d190cf02d9d886 + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + checksum: 10c0/506af444c4dce7f8e31f34fc549e2fb8152d6b9c4a30c6e62852badd7f520b579c679af433e7a072f9d78eb7808d230dc12e1cf58da9154dfbf8813099ea0fe0 languageName: node linkType: hard @@ -12404,7 +12472,7 @@ __metadata: languageName: node linkType: hard -"object.entries@npm:^1.1.7, object.entries@npm:^1.1.8": +"object.entries@npm:^1.1.8": version: 1.1.8 resolution: "object.entries@npm:1.1.8" dependencies: @@ -12708,6 +12776,16 @@ __metadata: languageName: node linkType: hard +"parse-imports@npm:^2.1.0": + version: 2.1.0 + resolution: "parse-imports@npm:2.1.0" + dependencies: + es-module-lexer: "npm:^1.5.3" + slashes: "npm:^3.0.12" + checksum: 10c0/18ef58008868d2d09e472bb540d63efc7cc27f2c33607e5d09c256ece7a30062cac292bda96d820438e94f3dbf558c85e4b084c10d238baa858796794e6cf628 + languageName: node + linkType: hard + "parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -14940,7 +15018,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.2.0, regexp.prototype.flags@npm:^1.5.2": +"regexp.prototype.flags@npm:^1.2.0, regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2": version: 1.5.2 resolution: "regexp.prototype.flags@npm:1.5.2" dependencies: @@ -15742,6 +15820,13 @@ __metadata: languageName: node linkType: hard +"slashes@npm:^3.0.12": + version: 3.0.12 + resolution: "slashes@npm:3.0.12" + checksum: 10c0/71ca2a1fcd1ab6814b0fdb8cf9c33a3d54321deec2aa8d173510f0086880201446021a9b9e6a18561f7c472b69a2145977c6a8fb9c53a8ff7be31778f203d175 + languageName: node + linkType: hard + "slice-ansi@npm:^4.0.0": version: 4.0.0 resolution: "slice-ansi@npm:4.0.0" @@ -16151,6 +16236,15 @@ __metadata: languageName: node linkType: hard +"stop-iteration-iterator@npm:^1.0.0": + version: 1.0.0 + resolution: "stop-iteration-iterator@npm:1.0.0" + dependencies: + internal-slot: "npm:^1.0.4" + checksum: 10c0/c4158d6188aac510d9e92925b58709207bd94699e9c31186a040c80932a687f84a51356b5895e6dc72710aad83addb9411c22171832c9ae0e6e11b7d61b0dfb9 + languageName: node + linkType: hard + "stream-browserify@npm:^2.0.1": version: 2.0.2 resolution: "stream-browserify@npm:2.0.2" @@ -16235,6 +16329,16 @@ __metadata: languageName: node linkType: hard +"string.prototype.includes@npm:^2.0.0": + version: 2.0.0 + resolution: "string.prototype.includes@npm:2.0.0" + dependencies: + define-properties: "npm:^1.1.3" + es-abstract: "npm:^1.17.5" + checksum: 10c0/32dff118c9e9dcc87e240b05462fa8ee7248d9e335c0015c1442fe18152261508a2146d9bb87ddae56abab69148a83c61dfaea33f53853812a6a2db737689ed2 + languageName: node + linkType: hard + "string.prototype.matchall@npm:^4.0.11, string.prototype.matchall@npm:^4.0.6": version: 4.0.11 resolution: "string.prototype.matchall@npm:4.0.11" @@ -16678,6 +16782,16 @@ __metadata: languageName: node linkType: hard +"synckit@npm:^0.9.0": + version: 0.9.0 + resolution: "synckit@npm:0.9.0" + dependencies: + "@pkgr/core": "npm:^0.1.0" + tslib: "npm:^2.6.2" + checksum: 10c0/b5c1e7c03fefe3d36a9ab4e71dd21859cb32be4138712c31a893382a568fd00efc59ede8f521dd7e53d43a2fea92bdf717e987ea9ed6ad94f97ef28d71d0ba2f + languageName: node + linkType: hard + "table@npm:^6.8.2": version: 6.8.2 resolution: "table@npm:6.8.2" @@ -17014,13 +17128,20 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.6.2, tslib@npm:^2.4.0": +"tslib@npm:2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" checksum: 10c0/e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb languageName: node linkType: hard +"tslib@npm:^2.4.0, tslib@npm:^2.6.2": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a + languageName: node + linkType: hard + "tty-browserify@npm:0.0.0": version: 0.0.0 resolution: "tty-browserify@npm:0.0.0" @@ -18009,7 +18130,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15, which-typed-array@npm:^1.1.9": +"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15, which-typed-array@npm:^1.1.9": version: 1.1.15 resolution: "which-typed-array@npm:1.1.15" dependencies: From 20fa9ce4845f1b6c8a7223f08409091808fa9bc0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Mon, 1 Jul 2024 16:45:48 +0200 Subject: [PATCH 54/84] Add timeline of public posts about a trending link in web UI (#30840) --- app/javascript/mastodon/actions/timelines.js | 1 + app/javascript/mastodon/api_types/statuses.ts | 1 + .../mastodon/components/status_list.jsx | 1 + .../features/explore/components/story.jsx | 4 +- .../mastodon/features/link_timeline/index.tsx | 76 +++++++++++++++++++ app/javascript/mastodon/features/ui/index.jsx | 2 + .../features/ui/util/async-components.js | 4 + app/javascript/mastodon/models/status.ts | 8 ++ config/routes.rb | 1 + 9 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 app/javascript/mastodon/features/link_timeline/index.tsx diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 4ca3c3a15..f0ea46118 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -158,6 +158,7 @@ export const expandAccountTimeline = (accountId, { maxId, withReplies, t export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged }); export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 }); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); +export const expandLinkTimeline = (url, { maxId } = {}, done = noOp) => expandTimeline(`link:${url}`, `/api/v1/timelines/link`, { url, max_id: maxId }, done); export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}, done = noOp) => { return expandTimeline(`hashtag:${hashtag}${local ? ':local' : ''}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId, diff --git a/app/javascript/mastodon/api_types/statuses.ts b/app/javascript/mastodon/api_types/statuses.ts index db4e20506..a934faeb7 100644 --- a/app/javascript/mastodon/api_types/statuses.ts +++ b/app/javascript/mastodon/api_types/statuses.ts @@ -44,6 +44,7 @@ export interface ApiPreviewCardJSON { type: string; author_name: string; author_url: string; + author_account?: ApiAccountJSON; provider_name: string; provider_url: string; html: string; diff --git a/app/javascript/mastodon/components/status_list.jsx b/app/javascript/mastodon/components/status_list.jsx index 3ed20f65e..fee6675fa 100644 --- a/app/javascript/mastodon/components/status_list.jsx +++ b/app/javascript/mastodon/components/status_list.jsx @@ -33,6 +33,7 @@ export default class StatusList extends ImmutablePureComponent { withCounters: PropTypes.bool, timelineId: PropTypes.string, lastId: PropTypes.string, + bindToDocument: PropTypes.bool, }; static defaultProps = { diff --git a/app/javascript/mastodon/features/explore/components/story.jsx b/app/javascript/mastodon/features/explore/components/story.jsx index a2cae942d..125df412a 100644 --- a/app/javascript/mastodon/features/explore/components/story.jsx +++ b/app/javascript/mastodon/features/explore/components/story.jsx @@ -4,6 +4,8 @@ import { useState, useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { Link } from 'react-router-dom'; + import { Blurhash } from 'mastodon/components/blurhash'; @@ -57,7 +59,7 @@ export const Story = ({ <div className='story__details__shared'> {author ? <FormattedMessage id='link_preview.author' className='story__details__shared__author' defaultMessage='By {name}' values={{ name: authorAccount ? <AuthorLink accountId={authorAccount} /> : <strong>{author}</strong> }} /> : <span />} - {typeof sharedTimes === 'number' ? <span className='story__details__shared__pill'><ShortNumber value={sharedTimes} renderer={sharesCountRenderer} /></span> : <Skeleton width='10ch' />} + {typeof sharedTimes === 'number' ? <Link className='story__details__shared__pill' to={`/links/${encodeURIComponent(url)}`}><ShortNumber value={sharedTimes} renderer={sharesCountRenderer} /></Link> : <Skeleton width='10ch' />} </div> </div> diff --git a/app/javascript/mastodon/features/link_timeline/index.tsx b/app/javascript/mastodon/features/link_timeline/index.tsx new file mode 100644 index 000000000..dd726dac1 --- /dev/null +++ b/app/javascript/mastodon/features/link_timeline/index.tsx @@ -0,0 +1,76 @@ +import { useRef, useEffect, useCallback } from 'react'; + +import { Helmet } from 'react-helmet'; +import { useParams } from 'react-router-dom'; + +import ExploreIcon from '@/material-icons/400-24px/explore.svg?react'; +import { expandLinkTimeline } from 'mastodon/actions/timelines'; +import Column from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusListContainer from 'mastodon/features/ui/containers/status_list_container'; +import type { Card } from 'mastodon/models/status'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +export const LinkTimeline: React.FC<{ + multiColumn: boolean; +}> = ({ multiColumn }) => { + const { url } = useParams<{ url: string }>(); + const decodedUrl = url ? decodeURIComponent(url) : undefined; + const dispatch = useAppDispatch(); + const columnRef = useRef<Column>(null); + const firstStatusId = useAppSelector((state) => + decodedUrl + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + (state.timelines.getIn([`link:${decodedUrl}`, 'items', 0]) as string) + : undefined, + ); + const story = useAppSelector((state) => + firstStatusId + ? (state.statuses.getIn([firstStatusId, 'card']) as Card) + : undefined, + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback( + (maxId: string) => { + dispatch(expandLinkTimeline(decodedUrl, { maxId })); + }, + [dispatch, decodedUrl], + ); + + useEffect(() => { + dispatch(expandLinkTimeline(decodedUrl)); + }, [dispatch, decodedUrl]); + + return ( + <Column bindToDocument={!multiColumn} ref={columnRef} label={story?.title}> + <ColumnHeader + icon='explore' + iconComponent={ExploreIcon} + title={story?.title} + onClick={handleHeaderClick} + multiColumn={multiColumn} + showBackButton + /> + + <StatusListContainer + timelineId={`link:${decodedUrl}`} + onLoadMore={handleLoadMore} + trackScroll + scrollKey={`link_timeline-${decodedUrl}`} + bindToDocument={!multiColumn} + /> + + <Helmet> + <title>{story?.title}</title> + <meta name='robots' content='noindex' /> + </Helmet> + </Column> + ); +}; + +// eslint-disable-next-line import/no-default-export +export default LinkTimeline; diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index b58e191ed..d41132f9c 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -56,6 +56,7 @@ import { FavouritedStatuses, BookmarkedStatuses, FollowedTags, + LinkTimeline, ListTimeline, Blocks, DomainBlocks, @@ -202,6 +203,7 @@ class SwitchingColumnsArea extends PureComponent { <WrappedRoute path='/public/remote' exact component={Firehose} componentParams={{ feedType: 'public:remote' }} content={children} /> <WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} /> <WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} /> + <WrappedRoute path='/links/:url' component={LinkTimeline} content={children} /> <WrappedRoute path='/lists/:id' component={ListTimeline} content={children} /> <WrappedRoute path='/notifications' component={Notifications} content={children} exact /> <WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact /> diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index e1f5bfdaf..b8a2359d9 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -201,3 +201,7 @@ export function NotificationRequests () { export function NotificationRequest () { return import(/*webpackChunkName: "features/notifications/request" */'../../notifications/request'); } + +export function LinkTimeline () { + return import(/*webpackChunkName: "features/link_timeline" */'../../link_timeline'); +} diff --git a/app/javascript/mastodon/models/status.ts b/app/javascript/mastodon/models/status.ts index 7907fc34f..3900df4e3 100644 --- a/app/javascript/mastodon/models/status.ts +++ b/app/javascript/mastodon/models/status.ts @@ -1,4 +1,12 @@ +import type { RecordOf } from 'immutable'; + +import type { ApiPreviewCardJSON } from 'mastodon/api_types/statuses'; + export type { StatusVisibility } from 'mastodon/api_types/statuses'; // Temporary until we type it correctly export type Status = Immutable.Map<string, unknown>; + +type CardShape = Required<ApiPreviewCardJSON>; + +export type Card = RecordOf<CardShape>; diff --git a/config/routes.rb b/config/routes.rb index f4662dd5d..4b3bd4f18 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,6 +27,7 @@ Rails.application.routes.draw do /public/remote /conversations /lists/(*any) + /links/(*any) /notifications/(*any) /favourites /bookmarks From b728c0e8ce9ac3a74f116bedff85e36dd7cc6a1e Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Mon, 1 Jul 2024 17:52:01 +0200 Subject: [PATCH 55/84] Change hover cards to not appear until the mouse stops in web UI (#30850) --- app/javascript/hooks/useTimeout.ts | 17 +- .../mastodon/components/follow_button.tsx | 4 +- .../components/hover_card_account.tsx | 2 +- .../components/hover_card_controller.tsx | 159 ++++++++++++------ .../components/conversation.jsx | 2 +- .../styles/mastodon/components.scss | 2 + 6 files changed, 131 insertions(+), 55 deletions(-) diff --git a/app/javascript/hooks/useTimeout.ts b/app/javascript/hooks/useTimeout.ts index f1814ae8e..bb1e8848d 100644 --- a/app/javascript/hooks/useTimeout.ts +++ b/app/javascript/hooks/useTimeout.ts @@ -2,19 +2,34 @@ import { useRef, useCallback, useEffect } from 'react'; export const useTimeout = () => { const timeoutRef = useRef<ReturnType<typeof setTimeout>>(); + const callbackRef = useRef<() => void>(); const set = useCallback((callback: () => void, delay: number) => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } + callbackRef.current = callback; timeoutRef.current = setTimeout(callback, delay); }, []); + const delay = useCallback((delay: number) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + if (!callbackRef.current) { + return; + } + + timeoutRef.current = setTimeout(callbackRef.current, delay); + }, []); + const cancel = useCallback(() => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = undefined; + callbackRef.current = undefined; } }, []); @@ -25,5 +40,5 @@ export const useTimeout = () => { [cancel], ); - return [set, cancel] as const; + return [set, cancel, delay] as const; }; diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index db5994288..62771c254 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -27,7 +27,7 @@ const messages = defineMessages({ }); export const FollowButton: React.FC<{ - accountId: string; + accountId?: string; }> = ({ accountId }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -36,7 +36,7 @@ export const FollowButton: React.FC<{ accountId ? state.accounts.get(accountId) : undefined, ); const relationship = useAppSelector((state) => - state.relationships.get(accountId), + accountId ? state.relationships.get(accountId) : undefined, ); const following = relationship?.following || relationship?.requested; diff --git a/app/javascript/mastodon/components/hover_card_account.tsx b/app/javascript/mastodon/components/hover_card_account.tsx index 59f957783..8933e14a9 100644 --- a/app/javascript/mastodon/components/hover_card_account.tsx +++ b/app/javascript/mastodon/components/hover_card_account.tsx @@ -17,7 +17,7 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store'; export const HoverCardAccount = forwardRef< HTMLDivElement, - { accountId: string } + { accountId?: string } >(({ accountId }, ref) => { const dispatch = useAppDispatch(); diff --git a/app/javascript/mastodon/components/hover_card_controller.tsx b/app/javascript/mastodon/components/hover_card_controller.tsx index 0130390ef..5ca55ebde 100644 --- a/app/javascript/mastodon/components/hover_card_controller.tsx +++ b/app/javascript/mastodon/components/hover_card_controller.tsx @@ -12,8 +12,8 @@ import { useTimeout } from 'mastodon/../hooks/useTimeout'; import { HoverCardAccount } from 'mastodon/components/hover_card_account'; const offset = [-12, 4] as OffsetValue; -const enterDelay = 650; -const leaveDelay = 250; +const enterDelay = 750; +const leaveDelay = 150; const popperConfig = { strategy: 'fixed' } as UsePopperOptions; const isHoverCardAnchor = (element: HTMLElement) => @@ -23,50 +23,12 @@ export const HoverCardController: React.FC = () => { const [open, setOpen] = useState(false); const [accountId, setAccountId] = useState<string | undefined>(); const [anchor, setAnchor] = useState<HTMLElement | null>(null); - const cardRef = useRef<HTMLDivElement>(null); + const cardRef = useRef<HTMLDivElement | null>(null); const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout(); - const [setEnterTimeout, cancelEnterTimeout] = useTimeout(); + const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout(); + const [setScrollTimeout] = useTimeout(); const location = useLocation(); - const handleAnchorMouseEnter = useCallback( - (e: MouseEvent) => { - const { target } = e; - - if (target instanceof HTMLElement && isHoverCardAnchor(target)) { - cancelLeaveTimeout(); - - setEnterTimeout(() => { - target.setAttribute('aria-describedby', 'hover-card'); - setAnchor(target); - setOpen(true); - setAccountId( - target.getAttribute('data-hover-card-account') ?? undefined, - ); - }, enterDelay); - } - - if (target === cardRef.current?.parentNode) { - cancelLeaveTimeout(); - } - }, - [cancelLeaveTimeout, setEnterTimeout, setOpen, setAccountId, setAnchor], - ); - - const handleAnchorMouseLeave = useCallback( - (e: MouseEvent) => { - if (e.target === anchor || e.target === cardRef.current?.parentNode) { - cancelEnterTimeout(); - - setLeaveTimeout(() => { - anchor?.removeAttribute('aria-describedby'); - setOpen(false); - setAnchor(null); - }, leaveDelay); - } - }, - [cancelEnterTimeout, setLeaveTimeout, setOpen, setAnchor, anchor], - ); - const handleClose = useCallback(() => { cancelEnterTimeout(); cancelLeaveTimeout(); @@ -79,22 +41,119 @@ export const HoverCardController: React.FC = () => { }, [handleClose, location]); useEffect(() => { - document.body.addEventListener('mouseenter', handleAnchorMouseEnter, { + let isScrolling = false; + let currentAnchor: HTMLElement | null = null; + + const open = (target: HTMLElement) => { + target.setAttribute('aria-describedby', 'hover-card'); + setOpen(true); + setAnchor(target); + setAccountId(target.getAttribute('data-hover-card-account') ?? undefined); + }; + + const close = () => { + currentAnchor?.removeAttribute('aria-describedby'); + currentAnchor = null; + setOpen(false); + setAnchor(null); + setAccountId(undefined); + }; + + const handleMouseEnter = (e: MouseEvent) => { + const { target } = e; + + // We've exited the window + if (!(target instanceof HTMLElement)) { + close(); + return; + } + + // We've entered an anchor + if (!isScrolling && isHoverCardAnchor(target)) { + cancelLeaveTimeout(); + + currentAnchor?.removeAttribute('aria-describedby'); + currentAnchor = target; + + setEnterTimeout(() => { + open(target); + }, enterDelay); + } + + // We've entered the hover card + if ( + !isScrolling && + (target === currentAnchor || target === cardRef.current) + ) { + cancelLeaveTimeout(); + } + }; + + const handleMouseLeave = (e: MouseEvent) => { + if (!currentAnchor) { + return; + } + + if (e.target === currentAnchor || e.target === cardRef.current) { + cancelEnterTimeout(); + + setLeaveTimeout(() => { + close(); + }, leaveDelay); + } + }; + + const handleScrollEnd = () => { + isScrolling = false; + }; + + const handleScroll = () => { + isScrolling = true; + cancelEnterTimeout(); + setScrollTimeout(handleScrollEnd, 100); + }; + + const handleMouseMove = () => { + delayEnterTimeout(enterDelay); + }; + + document.body.addEventListener('mouseenter', handleMouseEnter, { passive: true, capture: true, }); - document.body.addEventListener('mouseleave', handleAnchorMouseLeave, { + + document.body.addEventListener('mousemove', handleMouseMove, { + passive: true, + capture: false, + }); + + document.body.addEventListener('mouseleave', handleMouseLeave, { + passive: true, + capture: true, + }); + + document.addEventListener('scroll', handleScroll, { passive: true, capture: true, }); return () => { - document.body.removeEventListener('mouseenter', handleAnchorMouseEnter); - document.body.removeEventListener('mouseleave', handleAnchorMouseLeave); + document.body.removeEventListener('mouseenter', handleMouseEnter); + document.body.removeEventListener('mousemove', handleMouseMove); + document.body.removeEventListener('mouseleave', handleMouseLeave); + document.removeEventListener('scroll', handleScroll); }; - }, [handleAnchorMouseEnter, handleAnchorMouseLeave]); - - if (!accountId) return null; + }, [ + setEnterTimeout, + setLeaveTimeout, + setScrollTimeout, + cancelEnterTimeout, + cancelLeaveTimeout, + delayEnterTimeout, + setOpen, + setAccountId, + setAnchor, + ]); return ( <Overlay diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx index 3af89f997..a2b72f716 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx @@ -163,7 +163,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown }) menu.push({ text: intl.formatMessage(messages.delete), action: handleDelete }); const names = accounts.map(a => ( - <Link to={`/@${a.get('acct')}`} key={a.get('id')} title={a.get('acct')}> + <Link to={`/@${a.get('acct')}`} key={a.get('id')} data-hover-card-account={a.get('id')}> <bdi> <strong className='display-name__html' diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index cbf9314ff..12eac79b9 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -10468,12 +10468,14 @@ noscript { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + text-align: end; } &.verified { dd { display: flex; align-items: center; + justify-content: flex-end; gap: 4px; overflow: hidden; white-space: nowrap; From d3f504245cab5a9a0e89262e0a1398d035dffac9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Mon, 1 Jul 2024 20:10:22 +0200 Subject: [PATCH 56/84] Fix missing confirmation when unfollowing from hover card in web UI (#30879) --- .../mastodon/components/follow_button.tsx | 31 +++++++++++++------ .../features/account/components/header.jsx | 6 ++-- .../containers/header_container.jsx | 12 +------ 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index 62771c254..ecc4e1ee1 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect } from 'react'; -import { useIntl, defineMessages } from 'react-intl'; +import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; import { useIdentity } from '@/mastodon/identity_context'; import { @@ -19,10 +19,6 @@ const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' }, mutual: { id: 'account.mutual', defaultMessage: 'Mutual' }, - cancel_follow_request: { - id: 'account.cancel_follow_request', - defaultMessage: 'Withdraw follow request', - }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, }); @@ -65,11 +61,28 @@ export const FollowButton: React.FC<{ if (accountId === me) { return; } else if (relationship.following || relationship.requested) { - dispatch(unfollowAccount(accountId)); + dispatch( + openModal({ + modalType: 'CONFIRM', + modalProps: { + message: ( + <FormattedMessage + id='confirmations.unfollow.message' + defaultMessage='Are you sure you want to unfollow {name}?' + values={{ name: <strong>@{account?.acct}</strong> }} + /> + ), + confirm: intl.formatMessage(messages.unfollow), + onConfirm: () => { + dispatch(unfollowAccount(accountId)); + }, + }, + }), + ); } else { dispatch(followAccount(accountId)); } - }, [dispatch, accountId, relationship, account, signedIn]); + }, [dispatch, intl, accountId, relationship, account, signedIn]); let label; @@ -79,13 +92,11 @@ export const FollowButton: React.FC<{ label = intl.formatMessage(messages.edit_profile); } else if (!relationship) { label = <LoadingIndicator />; - } else if (relationship.requested) { - label = intl.formatMessage(messages.cancel_follow_request); } else if (relationship.following && relationship.followed_by) { label = intl.formatMessage(messages.mutual); } else if (!relationship.following && relationship.followed_by) { label = intl.formatMessage(messages.followBack); - } else if (relationship.following) { + } else if (relationship.following || relationship.requested) { label = intl.formatMessage(messages.unfollow); } else { label = intl.formatMessage(messages.follow); diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index b10ef6ef7..1326874e5 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -94,7 +94,7 @@ const messageForFollowButton = relationship => { return messages.mutual; } else if (!relationship.get('following') && relationship.get('followed_by')) { return messages.followBack; - } else if (relationship.get('following')) { + } else if (relationship.get('following') || relationship.get('requested')) { return messages.unfollow; } else { return messages.follow; @@ -291,10 +291,8 @@ class Header extends ImmutablePureComponent { if (me !== account.get('id')) { if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded actionBtn = <Button disabled><LoadingIndicator /></Button>; - } else if (account.getIn(['relationship', 'requested'])) { - actionBtn = <Button text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; } else if (!account.getIn(['relationship', 'blocking'])) { - actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(messageForFollowButton(account.get('relationship')))} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />; + actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) })} text={intl.formatMessage(messageForFollowButton(account.get('relationship')))} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />; } else if (account.getIn(['relationship', 'blocking'])) { actionBtn = <Button text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />; } diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx index 73fd62841..d55d8c58e 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx @@ -25,7 +25,6 @@ import { makeGetAccount, getAccountHidden } from '../../../selectors'; import Header from '../components/header'; const messages = defineMessages({ - cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' }, }); @@ -45,7 +44,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch, { intl }) => ({ onFollow (account) { - if (account.getIn(['relationship', 'following'])) { + if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { dispatch(openModal({ modalType: 'CONFIRM', modalProps: { @@ -54,15 +53,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onConfirm: () => dispatch(unfollowAccount(account.get('id'))), }, })); - } else if (account.getIn(['relationship', 'requested'])) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - }, - })); } else { dispatch(followAccount(account.get('id'))); } From 1fc14e324bf103e379cf73ffceebbf46472cccde Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:41:54 +0200 Subject: [PATCH 57/84] New Crowdin Translations (automated) (#30890) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/en-GB.json | 8 ++++++++ app/javascript/mastodon/locales/sk.json | 3 +++ config/locales/doorkeeper.en-GB.yml | 2 ++ config/locales/en-GB.yml | 3 +++ 4 files changed, 16 insertions(+) diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index c4f401d86..94d7defc7 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -35,7 +35,9 @@ "account.follow_back": "Follow back", "account.followers": "Followers", "account.followers.empty": "No one follows this user yet.", + "account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}", "account.following": "Following", + "account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}", "account.follows.empty": "This user doesn't follow anyone yet.", "account.go_to_profile": "Go to profile", "account.hide_reblogs": "Hide boosts from @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} has requested to follow you", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Show boosts from @{name}", + "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}", "account.unblock": "Unblock @{name}", "account.unblock_domain": "Unblock domain {domain}", "account.unblock_short": "Unblock", @@ -411,6 +414,8 @@ "limited_account_hint.action": "Show profile anyway", "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.", "link_preview.author": "By {name}", + "link_preview.more_from_author": "More from {name}", + "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}", "lists.account.add": "Add to list", "lists.account.remove": "Remove from list", "lists.delete": "Delete list", @@ -691,8 +696,11 @@ "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)", "server_banner.active_users": "active users", "server_banner.administered_by": "Administered by:", + "server_banner.is_one_of_many": "{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.", "server_banner.server_stats": "Server stats:", "sign_in_banner.create_account": "Create account", + "sign_in_banner.follow_anyone": "Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.", + "sign_in_banner.mastodon_is": "Mastodon is the best way to keep up with what's happening.", "sign_in_banner.sign_in": "Sign in", "sign_in_banner.sso_redirect": "Login or Register", "status.admin_account": "Open moderation interface for @{name}", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index ed9c0de60..ed877f766 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -88,7 +88,10 @@ "audio.hide": "Skryť zvuk", "block_modal.show_less": "Zobraziť menej", "block_modal.show_more": "Zobraziť viac", + "block_modal.they_cant_mention": "Nemôžu ťa spomenúť, alebo nasledovať.", + "block_modal.they_will_know": "Môžu vidieť, že sú zablokovaní/ý.", "block_modal.title": "Blokovať užívateľa?", + "block_modal.you_wont_see_mentions": "Neuvidíš príspevky, ktoré ich spomínajú.", "boost_modal.combo": "Nabudúce môžete preskočiť stlačením {combo}", "bundle_column_error.copy_stacktrace": "Kopírovať chybovú hlášku", "bundle_column_error.error.body": "Požadovanú stránku nebolo možné vykresliť. Môže to byť spôsobené chybou v našom kóde alebo problémom s kompatibilitou prehliadača.", diff --git a/config/locales/doorkeeper.en-GB.yml b/config/locales/doorkeeper.en-GB.yml index b3ceffb13..f254825b1 100644 --- a/config/locales/doorkeeper.en-GB.yml +++ b/config/locales/doorkeeper.en-GB.yml @@ -135,6 +135,7 @@ en-GB: media: Media attachments mutes: Mutes notifications: Notifications + profile: Your Mastodon profile push: Push notifications reports: Reports search: Search @@ -165,6 +166,7 @@ en-GB: admin:write:reports: perform moderation actions on reports crypto: use end-to-end encryption follow: modify account relationships + profile: read only your account's profile information push: receive your push notifications read: read all your account's data read:accounts: see accounts information diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 07eb84ebb..928823b99 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -285,6 +285,7 @@ en-GB: update_custom_emoji_html: "%{name} updated emoji %{target}" update_domain_block_html: "%{name} updated domain block for %{target}" update_ip_block_html: "%{name} changed rule for IP %{target}" + update_report_html: "%{name} updated report %{target}" update_status_html: "%{name} updated post by %{target}" update_user_role_html: "%{name} changed %{target} role" deleted_account: deleted account @@ -292,6 +293,7 @@ en-GB: filter_by_action: Filter by action filter_by_user: Filter by user title: Audit log + unavailable_instance: "(domain name unavailable)" announcements: destroyed_msg: Announcement successfully deleted! edit: @@ -950,6 +952,7 @@ en-GB: delete: Delete edit_preset: Edit warning preset empty: You haven't defined any warning presets yet. + title: Warning presets webhooks: add_new: Add endpoint delete: Delete From ebd8e1bbb6465c78f6542fe7f09938fdb768dbb7 Mon Sep 17 00:00:00 2001 From: David Roetzel <david@roetzel.de> Date: Wed, 3 Jul 2024 09:19:54 +0200 Subject: [PATCH 58/84] Add system check for missing database indexes (#30888) --- Gemfile | 1 + Gemfile.lock | 2 + app/lib/admin/db/schema_parser.rb | 92 +++++++++++++++++++ app/lib/admin/system_check.rb | 1 + .../system_check/missing_indexes_check.rb | 36 ++++++++ config/locales/en.yml | 2 + spec/lib/admin/db/schema_parser_spec.rb | 49 ++++++++++ .../missing_indexes_check_spec.rb | 65 +++++++++++++ 8 files changed, 248 insertions(+) create mode 100644 app/lib/admin/db/schema_parser.rb create mode 100644 app/lib/admin/system_check/missing_indexes_check.rb create mode 100644 spec/lib/admin/db/schema_parser_spec.rb create mode 100644 spec/lib/admin/system_check/missing_indexes_check_spec.rb diff --git a/Gemfile b/Gemfile index be3f9e6f9..0d6cc4c4e 100644 --- a/Gemfile +++ b/Gemfile @@ -229,3 +229,4 @@ gem 'rubyzip', '~> 2.3' gem 'hcaptcha', '~> 7.1' gem 'mail', '~> 2.8' +gem 'prism' diff --git a/Gemfile.lock b/Gemfile.lock index 42cc0e198..969d18493 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -600,6 +600,7 @@ GEM actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) + prism (0.30.0) propshaft (0.9.0) actionpack (>= 7.0.0) activesupport (>= 7.0.0) @@ -1003,6 +1004,7 @@ DEPENDENCIES pg (~> 1.5) pghero premailer-rails + prism propshaft public_suffix (~> 6.0) puma (~> 6.3) diff --git a/app/lib/admin/db/schema_parser.rb b/app/lib/admin/db/schema_parser.rb new file mode 100644 index 000000000..e61a2281e --- /dev/null +++ b/app/lib/admin/db/schema_parser.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +class Admin::Db::SchemaParser + class Index + attr_reader :name, :table_name, :columns, :options + + def initialize(name:, table_name:, columns:, options:) + @name = name + @table_name = table_name + @columns = columns + @options = options + end + end + + attr_reader :indexes_by_table + + def initialize(source) + parse(source) + end + + private + + def parse(source) + @indexes_by_table = {} + queue = [Prism.parse(source).value] + while (node = queue.shift) + if node.type == :call_node && node.name == :create_table + parse_create_table(node) + elsif node.type == :call_node && node.name == :add_index + parse_add_index(node) + else + queue.concat(node.compact_child_nodes) + end + end + end + + def parse_create_table(node) + table_name = parse_arguments(node).first + queue = node.compact_child_nodes + while (node = queue.shift) + if node.type == :call_node && node.name == :index + parse_index(node, table_name:) + else + queue.concat(node.compact_child_nodes) + end + end + end + + def parse_index(node, table_name:) + arguments = parse_arguments(node) + save_index( + name: arguments.last[:name], + table_name: table_name, + columns: arguments.first, + options: arguments.last + ) + end + + def parse_add_index(node) + arguments = parse_arguments(node) + save_index( + name: arguments.last[:name], + table_name: arguments.first, + columns: arguments[1], + options: arguments.last + ) + end + + def parse_arguments(node) + node.arguments.arguments.map { |a| parse_argument(a) } + end + + def parse_argument(argument) + case argument + when Prism::StringNode + argument.unescaped + when Prism::SymbolNode + argument.unescaped.to_sym + when Prism::ArrayNode + argument.elements.map { |e| parse_argument(e) } + when Prism::KeywordHashNode + argument.elements.to_h do |element| + [element.key.unescaped.to_sym, parse_argument(element.value)] + end + end + end + + def save_index(name:, table_name:, columns:, options:) + @indexes_by_table[table_name] ||= [] + @indexes_by_table[table_name] << Index.new(name:, table_name:, columns:, options:) + end +end diff --git a/app/lib/admin/system_check.rb b/app/lib/admin/system_check.rb index 25c88341a..453011f7a 100644 --- a/app/lib/admin/system_check.rb +++ b/app/lib/admin/system_check.rb @@ -8,6 +8,7 @@ class Admin::SystemCheck Admin::SystemCheck::SidekiqProcessCheck, Admin::SystemCheck::RulesCheck, Admin::SystemCheck::ElasticsearchCheck, + Admin::SystemCheck::MissingIndexesCheck, ].freeze def self.perform(current_user) diff --git a/app/lib/admin/system_check/missing_indexes_check.rb b/app/lib/admin/system_check/missing_indexes_check.rb new file mode 100644 index 000000000..b7eecbb06 --- /dev/null +++ b/app/lib/admin/system_check/missing_indexes_check.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class Admin::SystemCheck::MissingIndexesCheck < Admin::SystemCheck::BaseCheck + def skip? + !current_user.can?(:view_devops) + end + + def pass? + missing_indexes.none? + end + + def message + Admin::SystemCheck::Message.new(:missing_indexes_check, missing_indexes.join(', ')) + end + + private + + def missing_indexes + @missing_indexes ||= begin + expected_indexes_by_table.flat_map do |table, indexes| + expected_indexes = indexes.map(&:name) + expected_indexes - existing_indexes_for(table) + end + end + end + + def expected_indexes_by_table + schema_rb = Rails.root.join('db', 'schema.rb').read + schema_parser = Admin::Db::SchemaParser.new(schema_rb) + schema_parser.indexes_by_table + end + + def existing_indexes_for(table) + ActiveRecord::Base.connection.indexes(table).map(&:name) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 20df80c27..270382dd1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -862,6 +862,8 @@ en: elasticsearch_version_check: message_html: 'Incompatible Elasticsearch version: %{value}' version_comparison: Elasticsearch %{running_version} is running while %{required_version} is required + missing_indexes_check: + message_html: 'The following indexes are missing from the database and should be recreated: <code>%{value}</code>.<br> Missing indexes may lead to severely reduced performance and data inconsistencies.' rules_check: action: Manage server rules message_html: You haven't defined any server rules. diff --git a/spec/lib/admin/db/schema_parser_spec.rb b/spec/lib/admin/db/schema_parser_spec.rb new file mode 100644 index 000000000..e28d5c1f9 --- /dev/null +++ b/spec/lib/admin/db/schema_parser_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Db::SchemaParser do + let(:dummy_schema) do + <<~SCHEMA + # Comment + ActiveRecord::Schema[7.1].define(version: 23) do + create_table "people", force: :cascade do |t| + t.string "name" + end + + create_table "posts", force: :cascade do |t| + t.string "title", null: false + t.bigint "size", null: false + t.string "description" + # t.index ["size", "title"], name: "index_posts_on_size_and_title" + t.index ["title"], name: "index_posts_on_title", unique: true + t.index ["size"], name: "index_posts_on_size" + end + + # add_index "people", ["name"], name: "commented_out_index" + add_index "people", ["name"], name: "index_people_on_name" + end + SCHEMA + end + let(:schema_parser) { described_class.new(dummy_schema) } + + describe '#indexes_by_table' do + subject { schema_parser.indexes_by_table } + + it 'returns index info for all affected tables' do + expect(subject.keys).to match_array(%w(people posts)) + end + + it 'returns all index information for the `people` table' do + people_info = subject['people'] + expect(people_info.map(&:name)).to contain_exactly('index_people_on_name') + end + + it 'returns all index information for the `posts` table' do + posts_info = subject['posts'] + expect(posts_info.map(&:name)).to contain_exactly( + 'index_posts_on_title', 'index_posts_on_size' + ) + end + end +end diff --git a/spec/lib/admin/system_check/missing_indexes_check_spec.rb b/spec/lib/admin/system_check/missing_indexes_check_spec.rb new file mode 100644 index 000000000..e183be562 --- /dev/null +++ b/spec/lib/admin/system_check/missing_indexes_check_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::MissingIndexesCheck do + subject(:check) { described_class.new(user) } + + let(:user) { Fabricate(:user) } + let(:schema_parser) do + instance_double(Admin::Db::SchemaParser, indexes_by_table: index_info) + end + let(:index_info) do + { + 'users' => [instance_double(Admin::Db::SchemaParser::Index, name: 'index_users_on_profile_id')], + 'posts' => [instance_double(Admin::Db::SchemaParser::Index, name: 'index_posts_on_user_id')], + } + end + let(:posts_indexes) { [] } + let(:users_indexes) { [] } + + before do + allow(Admin::Db::SchemaParser).to receive(:new).and_return(schema_parser) + allow(ActiveRecord::Base.connection).to receive(:indexes).with('posts').and_return(posts_indexes) + allow(ActiveRecord::Base.connection).to receive(:indexes).with('users').and_return(users_indexes) + end + + it_behaves_like 'a check available to devops users' + + describe '#pass?' do + context 'when indexes are missing' do + let(:posts_indexes) do + [instance_double(ActiveRecord::ConnectionAdapters::IndexDefinition, name: 'index_posts_on_user_id')] + end + + it 'returns false' do + expect(check.pass?).to be false + end + end + + context 'when all expected indexes are present' do + let(:posts_indexes) do + [instance_double(ActiveRecord::ConnectionAdapters::IndexDefinition, name: 'index_posts_on_user_id')] + end + let(:users_indexes) do + [instance_double(ActiveRecord::ConnectionAdapters::IndexDefinition, name: 'index_users_on_profile_id')] + end + + it 'returns true' do + expect(check.pass?).to be true + end + end + end + + describe '#message' do + subject { check.message } + + it 'sets the class name as the message key' do + expect(subject.key).to eq(:missing_indexes_check) + end + + it 'sets a list of missing indexes as message value' do + expect(subject.value).to eq('index_users_on_profile_id, index_posts_on_user_id') + end + end +end From 2e295bd5e7d3e7fefd4d4f03279f0dd0f3e5427d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 07:20:48 +0000 Subject: [PATCH 59/84] chore(deps): update dependency aws-sdk-s3 to v1.156.0 (#30899) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 969d18493..d855c05f4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,19 +100,19 @@ GEM attr_required (1.0.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.949.0) - aws-sdk-core (3.200.0) + aws-partitions (1.950.0) + aws-sdk-core (3.201.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.87.0) - aws-sdk-core (~> 3, >= 3.199.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.155.0) - aws-sdk-core (~> 3, >= 3.199.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) + aws-sigv4 (~> 1.5) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) azure-storage-blob (2.0.3) From ba7e7a6368f4e6097a7c6ee7145c5ff43e906517 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:21:05 +0200 Subject: [PATCH 60/84] chore(deps): update opentelemetry-ruby (non-major) (#30898) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 0d6cc4c4e..d1a8c2c5a 100644 --- a/Gemfile +++ b/Gemfile @@ -114,7 +114,7 @@ group :opentelemetry do gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false gem 'opentelemetry-instrumentation-pg', '~> 0.27.1', require: false gem 'opentelemetry-instrumentation-rack', '~> 0.24.1', require: false - gem 'opentelemetry-instrumentation-rails', '~> 0.30.0', require: false + gem 'opentelemetry-instrumentation-rails', '~> 0.31.0', require: false gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false gem 'opentelemetry-sdk', '~> 1.4', require: false diff --git a/Gemfile.lock b/Gemfile.lock index d855c05f4..e66077ff0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -525,7 +525,7 @@ GEM opentelemetry-instrumentation-active_record (0.7.2) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_support (0.5.1) + opentelemetry-instrumentation-active_support (0.6.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (0.22.3) @@ -556,14 +556,14 @@ GEM opentelemetry-instrumentation-rack (0.24.5) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-rails (0.30.2) + opentelemetry-instrumentation-rails (0.31.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-action_mailer (~> 0.1.0) opentelemetry-instrumentation-action_pack (~> 0.9.0) opentelemetry-instrumentation-action_view (~> 0.7.0) opentelemetry-instrumentation-active_job (~> 0.7.0) opentelemetry-instrumentation-active_record (~> 0.7.0) - opentelemetry-instrumentation-active_support (~> 0.5.0) + opentelemetry-instrumentation-active_support (~> 0.6.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-redis (0.25.6) opentelemetry-api (~> 1.0) @@ -995,7 +995,7 @@ DEPENDENCIES opentelemetry-instrumentation-net_http (~> 0.22.4) opentelemetry-instrumentation-pg (~> 0.27.1) opentelemetry-instrumentation-rack (~> 0.24.1) - opentelemetry-instrumentation-rails (~> 0.30.0) + opentelemetry-instrumentation-rails (~> 0.31.0) opentelemetry-instrumentation-redis (~> 0.25.3) opentelemetry-instrumentation-sidekiq (~> 0.25.2) opentelemetry-sdk (~> 1.4) From dd85e3bcc5e04a11775cbdd9cb1f7d3577ddd9d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 07:30:30 +0000 Subject: [PATCH 61/84] New Crowdin Translations (automated) (#30901) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/uk.json | 5 +++++ config/locales/cs.yml | 2 +- config/locales/simple_form.cs.yml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 338b65061..150b808f8 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -35,7 +35,9 @@ "account.follow_back": "Підписатися взаємно", "account.followers": "Підписники", "account.followers.empty": "Ніхто ще не підписаний на цього користувача.", + "account.followers_counter": "{count, plural, one {{counter} підписник} few {{counter} підписники} many {{counter} підписників} other {{counter} підписники}}", "account.following": "Ви стежите", + "account.following_counter": "{count, plural, one {{counter} підписка} few {{counter} підписки} many {{counter} підписок} other {{counter} підписки}}", "account.follows.empty": "Цей користувач ще ні на кого не підписався.", "account.go_to_profile": "Перейти до профілю", "account.hide_reblogs": "Сховати поширення від @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} надсилає запит на стеження", "account.share": "Поділитися профілем @{name}", "account.show_reblogs": "Показати поширення від @{name}", + "account.statuses_counter": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}", "account.unblock": "Розблокувати @{name}", "account.unblock_domain": "Розблокувати {domain}", "account.unblock_short": "Розблокувати", @@ -412,6 +415,7 @@ "limited_account_hint.title": "Цей профіль сховали модератори {domain}.", "link_preview.author": "Від {name}", "link_preview.more_from_author": "Більше від {name}", + "link_preview.shares": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}", "lists.account.add": "Додати до списку", "lists.account.remove": "Вилучити зі списку", "lists.delete": "Видалити список", @@ -695,6 +699,7 @@ "server_banner.is_one_of_many": "{domain} - один з багатьох незалежних серверів Mastodon, які ви можете використати, щоб брати участь у федівері.", "server_banner.server_stats": "Статистика сервера:", "sign_in_banner.create_account": "Створити обліковий запис", + "sign_in_banner.follow_anyone": "Слідкуйте за ким завгодно у всьому fediverse і дивіться все це в хронологічному порядку. Немає алгоритмів, реклами чи наживок для натискань при перегляді.", "sign_in_banner.mastodon_is": "Мастодон - найкращий спосіб продовжувати свою справу.", "sign_in_banner.sign_in": "Увійти", "sign_in_banner.sso_redirect": "Увійдіть або зареєструйтесь", diff --git a/config/locales/cs.yml b/config/locales/cs.yml index f3b8f27d8..20e7e4d46 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -81,7 +81,7 @@ cs: invite_request_text: Důvody založení invited_by: Pozván uživatelem ip: IP adresa - joined: Uživatel založen + joined: Uživatelem od location: all: Všechny local: Místní diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index 0b1a34e1b..f8422102f 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -59,7 +59,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_use_blurhash: Gradienty jsou založeny na barvách skryté grafiky, ale zakrývají jakékoliv detaily + 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 whole_word: Je-li klíčové slovo či fráze pouze alfanumerická, bude aplikován pouze, pokud se shoduje s celým slovem From 5651c16d11a3bb35e44c260b0c18afcd451fd5aa Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Wed, 3 Jul 2024 03:47:40 -0400 Subject: [PATCH 62/84] Limit `browser` version to enforce ruby 3.1 support (#30766) --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index d1a8c2c5a..769f834f4 100644 --- a/Gemfile +++ b/Gemfile @@ -25,7 +25,7 @@ gem 'ruby-vips', '~> 2.2', require: false gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.8' gem 'bootsnap', '~> 1.18.0', require: false -gem 'browser' +gem 'browser', '< 6' # https://github.com/fnando/browser/issues/543 gem 'charlock_holmes', '~> 0.7.7' gem 'chewy', '~> 7.3' gem 'devise', '~> 4.9' diff --git a/Gemfile.lock b/Gemfile.lock index e66077ff0..aafad69ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -917,7 +917,7 @@ DEPENDENCIES blurhash (~> 0.1) bootsnap (~> 1.18.0) brakeman (~> 6.0) - browser + browser (< 6) bundler-audit (~> 0.9) capybara (~> 3.39) charlock_holmes (~> 0.7.7) From 1dbffc30f12d20f3c5f038c72b06967480f82129 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:04:38 +0000 Subject: [PATCH 63/84] chore(deps): update opentelemetry-ruby (non-major) (#30903) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index aafad69ed..30835adef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -516,7 +516,7 @@ GEM opentelemetry-api (~> 1.0) opentelemetry-instrumentation-active_support (~> 0.1) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-active_job (0.7.1) + opentelemetry-instrumentation-active_job (0.7.2) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-active_model_serializers (0.20.1) @@ -568,7 +568,7 @@ GEM opentelemetry-instrumentation-redis (0.25.6) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) - opentelemetry-instrumentation-sidekiq (0.25.5) + opentelemetry-instrumentation-sidekiq (0.25.6) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-registry (0.3.1) From 6270281037c1f0991bfbf8d195280be3f5733bf3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:05:19 +0000 Subject: [PATCH 64/84] chore(deps): update dependency doorkeeper to v5.7.1 (#30053) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 30835adef..6444e4687 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -208,7 +208,7 @@ GEM activerecord (>= 4.2, < 8) docile (1.4.0) domain_name (0.6.20240107) - doorkeeper (5.6.9) + doorkeeper (5.7.1) railties (>= 5) dotenv (3.1.2) drb (2.2.1) @@ -431,7 +431,7 @@ GEM mime-types-data (3.2024.0604) mini_mime (1.1.5) mini_portile2 (2.8.7) - minitest (5.23.1) + minitest (5.24.1) msgpack (1.7.2) multi_json (1.15.0) multipart-post (2.4.0) From f99159d1eb671d72138edc42d11cd58472c33aec Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:46:51 +0200 Subject: [PATCH 65/84] fix(deps): update dependency webpack-merge to v6 (#30891) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 0379c7a5f..404c4f486 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "webpack-assets-manifest": "^4.0.6", "webpack-bundle-analyzer": "^4.8.0", "webpack-cli": "^3.3.12", - "webpack-merge": "^5.9.0", + "webpack-merge": "^6.0.0", "wicg-inert": "^3.1.2", "workbox-expiration": "^7.0.0", "workbox-precaching": "^7.0.0", diff --git a/yarn.lock b/yarn.lock index 9d19cb837..8e72138a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2901,7 +2901,7 @@ __metadata: webpack-bundle-analyzer: "npm:^4.8.0" webpack-cli: "npm:^3.3.12" webpack-dev-server: "npm:^3.11.3" - webpack-merge: "npm:^5.9.0" + webpack-merge: "npm:^6.0.0" wicg-inert: "npm:^3.1.2" workbox-expiration: "npm:^7.0.0" workbox-precaching: "npm:^7.0.0" @@ -17928,14 +17928,14 @@ __metadata: languageName: node linkType: hard -"webpack-merge@npm:^5.9.0": - version: 5.10.0 - resolution: "webpack-merge@npm:5.10.0" +"webpack-merge@npm:^6.0.0": + version: 6.0.1 + resolution: "webpack-merge@npm:6.0.1" dependencies: clone-deep: "npm:^4.0.1" flat: "npm:^5.0.2" - wildcard: "npm:^2.0.0" - checksum: 10c0/b607c84cabaf74689f965420051a55a08722d897bdd6c29cb0b2263b451c090f962d41ecf8c9bf56b0ab3de56e65476ace0a8ecda4f4a4663684243d90e0512b + wildcard: "npm:^2.0.1" + checksum: 10c0/bf1429567858b353641801b8a2696ca0aac270fc8c55d4de8a7b586fe07d27fdcfc83099a98ab47e6162383db8dd63bb8cc25b1beb2ec82150422eec843b0dc0 languageName: node linkType: hard @@ -18183,7 +18183,7 @@ __metadata: languageName: node linkType: hard -"wildcard@npm:^2.0.0": +"wildcard@npm:^2.0.1": version: 2.0.1 resolution: "wildcard@npm:2.0.1" checksum: 10c0/08f70cd97dd9a20aea280847a1fe8148e17cae7d231640e41eb26d2388697cbe65b67fd9e68715251c39b080c5ae4f76d71a9a69fa101d897273efdfb1b58bf7 From 20c749bd45286fbf0aca5610562e5dc6f2c616dd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:14:15 +0200 Subject: [PATCH 66/84] chore(deps): update dependency rubocop-rspec to v3.0.2 (#30902) 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 6444e4687..ca36c8cf9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -757,7 +757,7 @@ GEM rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (3.0.1) + rubocop-rspec (3.0.2) rubocop (~> 1.61) rubocop-rspec_rails (2.30.0) rubocop (~> 1.61) From 9be77fc0dbb01c1a8a54cd3da97e16c7941df367 Mon Sep 17 00:00:00 2001 From: David Roetzel <david@roetzel.de> Date: Wed, 3 Jul 2024 15:36:42 +0200 Subject: [PATCH 67/84] Revert "Add system check for missing database indexes" (#30909) --- Gemfile | 1 - Gemfile.lock | 2 - app/lib/admin/db/schema_parser.rb | 92 ------------------- app/lib/admin/system_check.rb | 1 - .../system_check/missing_indexes_check.rb | 36 -------- config/locales/en.yml | 2 - spec/lib/admin/db/schema_parser_spec.rb | 49 ---------- .../missing_indexes_check_spec.rb | 65 ------------- 8 files changed, 248 deletions(-) delete mode 100644 app/lib/admin/db/schema_parser.rb delete mode 100644 app/lib/admin/system_check/missing_indexes_check.rb delete mode 100644 spec/lib/admin/db/schema_parser_spec.rb delete mode 100644 spec/lib/admin/system_check/missing_indexes_check_spec.rb diff --git a/Gemfile b/Gemfile index 769f834f4..ef52d50ca 100644 --- a/Gemfile +++ b/Gemfile @@ -229,4 +229,3 @@ gem 'rubyzip', '~> 2.3' gem 'hcaptcha', '~> 7.1' gem 'mail', '~> 2.8' -gem 'prism' diff --git a/Gemfile.lock b/Gemfile.lock index ca36c8cf9..eb6720e45 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -600,7 +600,6 @@ GEM actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) - prism (0.30.0) propshaft (0.9.0) actionpack (>= 7.0.0) activesupport (>= 7.0.0) @@ -1004,7 +1003,6 @@ DEPENDENCIES pg (~> 1.5) pghero premailer-rails - prism propshaft public_suffix (~> 6.0) puma (~> 6.3) diff --git a/app/lib/admin/db/schema_parser.rb b/app/lib/admin/db/schema_parser.rb deleted file mode 100644 index e61a2281e..000000000 --- a/app/lib/admin/db/schema_parser.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -class Admin::Db::SchemaParser - class Index - attr_reader :name, :table_name, :columns, :options - - def initialize(name:, table_name:, columns:, options:) - @name = name - @table_name = table_name - @columns = columns - @options = options - end - end - - attr_reader :indexes_by_table - - def initialize(source) - parse(source) - end - - private - - def parse(source) - @indexes_by_table = {} - queue = [Prism.parse(source).value] - while (node = queue.shift) - if node.type == :call_node && node.name == :create_table - parse_create_table(node) - elsif node.type == :call_node && node.name == :add_index - parse_add_index(node) - else - queue.concat(node.compact_child_nodes) - end - end - end - - def parse_create_table(node) - table_name = parse_arguments(node).first - queue = node.compact_child_nodes - while (node = queue.shift) - if node.type == :call_node && node.name == :index - parse_index(node, table_name:) - else - queue.concat(node.compact_child_nodes) - end - end - end - - def parse_index(node, table_name:) - arguments = parse_arguments(node) - save_index( - name: arguments.last[:name], - table_name: table_name, - columns: arguments.first, - options: arguments.last - ) - end - - def parse_add_index(node) - arguments = parse_arguments(node) - save_index( - name: arguments.last[:name], - table_name: arguments.first, - columns: arguments[1], - options: arguments.last - ) - end - - def parse_arguments(node) - node.arguments.arguments.map { |a| parse_argument(a) } - end - - def parse_argument(argument) - case argument - when Prism::StringNode - argument.unescaped - when Prism::SymbolNode - argument.unescaped.to_sym - when Prism::ArrayNode - argument.elements.map { |e| parse_argument(e) } - when Prism::KeywordHashNode - argument.elements.to_h do |element| - [element.key.unescaped.to_sym, parse_argument(element.value)] - end - end - end - - def save_index(name:, table_name:, columns:, options:) - @indexes_by_table[table_name] ||= [] - @indexes_by_table[table_name] << Index.new(name:, table_name:, columns:, options:) - end -end diff --git a/app/lib/admin/system_check.rb b/app/lib/admin/system_check.rb index 453011f7a..25c88341a 100644 --- a/app/lib/admin/system_check.rb +++ b/app/lib/admin/system_check.rb @@ -8,7 +8,6 @@ class Admin::SystemCheck Admin::SystemCheck::SidekiqProcessCheck, Admin::SystemCheck::RulesCheck, Admin::SystemCheck::ElasticsearchCheck, - Admin::SystemCheck::MissingIndexesCheck, ].freeze def self.perform(current_user) diff --git a/app/lib/admin/system_check/missing_indexes_check.rb b/app/lib/admin/system_check/missing_indexes_check.rb deleted file mode 100644 index b7eecbb06..000000000 --- a/app/lib/admin/system_check/missing_indexes_check.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -class Admin::SystemCheck::MissingIndexesCheck < Admin::SystemCheck::BaseCheck - def skip? - !current_user.can?(:view_devops) - end - - def pass? - missing_indexes.none? - end - - def message - Admin::SystemCheck::Message.new(:missing_indexes_check, missing_indexes.join(', ')) - end - - private - - def missing_indexes - @missing_indexes ||= begin - expected_indexes_by_table.flat_map do |table, indexes| - expected_indexes = indexes.map(&:name) - expected_indexes - existing_indexes_for(table) - end - end - end - - def expected_indexes_by_table - schema_rb = Rails.root.join('db', 'schema.rb').read - schema_parser = Admin::Db::SchemaParser.new(schema_rb) - schema_parser.indexes_by_table - end - - def existing_indexes_for(table) - ActiveRecord::Base.connection.indexes(table).map(&:name) - end -end diff --git a/config/locales/en.yml b/config/locales/en.yml index 270382dd1..20df80c27 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -862,8 +862,6 @@ en: elasticsearch_version_check: message_html: 'Incompatible Elasticsearch version: %{value}' version_comparison: Elasticsearch %{running_version} is running while %{required_version} is required - missing_indexes_check: - message_html: 'The following indexes are missing from the database and should be recreated: <code>%{value}</code>.<br> Missing indexes may lead to severely reduced performance and data inconsistencies.' rules_check: action: Manage server rules message_html: You haven't defined any server rules. diff --git a/spec/lib/admin/db/schema_parser_spec.rb b/spec/lib/admin/db/schema_parser_spec.rb deleted file mode 100644 index e28d5c1f9..000000000 --- a/spec/lib/admin/db/schema_parser_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Admin::Db::SchemaParser do - let(:dummy_schema) do - <<~SCHEMA - # Comment - ActiveRecord::Schema[7.1].define(version: 23) do - create_table "people", force: :cascade do |t| - t.string "name" - end - - create_table "posts", force: :cascade do |t| - t.string "title", null: false - t.bigint "size", null: false - t.string "description" - # t.index ["size", "title"], name: "index_posts_on_size_and_title" - t.index ["title"], name: "index_posts_on_title", unique: true - t.index ["size"], name: "index_posts_on_size" - end - - # add_index "people", ["name"], name: "commented_out_index" - add_index "people", ["name"], name: "index_people_on_name" - end - SCHEMA - end - let(:schema_parser) { described_class.new(dummy_schema) } - - describe '#indexes_by_table' do - subject { schema_parser.indexes_by_table } - - it 'returns index info for all affected tables' do - expect(subject.keys).to match_array(%w(people posts)) - end - - it 'returns all index information for the `people` table' do - people_info = subject['people'] - expect(people_info.map(&:name)).to contain_exactly('index_people_on_name') - end - - it 'returns all index information for the `posts` table' do - posts_info = subject['posts'] - expect(posts_info.map(&:name)).to contain_exactly( - 'index_posts_on_title', 'index_posts_on_size' - ) - end - end -end diff --git a/spec/lib/admin/system_check/missing_indexes_check_spec.rb b/spec/lib/admin/system_check/missing_indexes_check_spec.rb deleted file mode 100644 index e183be562..000000000 --- a/spec/lib/admin/system_check/missing_indexes_check_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Admin::SystemCheck::MissingIndexesCheck do - subject(:check) { described_class.new(user) } - - let(:user) { Fabricate(:user) } - let(:schema_parser) do - instance_double(Admin::Db::SchemaParser, indexes_by_table: index_info) - end - let(:index_info) do - { - 'users' => [instance_double(Admin::Db::SchemaParser::Index, name: 'index_users_on_profile_id')], - 'posts' => [instance_double(Admin::Db::SchemaParser::Index, name: 'index_posts_on_user_id')], - } - end - let(:posts_indexes) { [] } - let(:users_indexes) { [] } - - before do - allow(Admin::Db::SchemaParser).to receive(:new).and_return(schema_parser) - allow(ActiveRecord::Base.connection).to receive(:indexes).with('posts').and_return(posts_indexes) - allow(ActiveRecord::Base.connection).to receive(:indexes).with('users').and_return(users_indexes) - end - - it_behaves_like 'a check available to devops users' - - describe '#pass?' do - context 'when indexes are missing' do - let(:posts_indexes) do - [instance_double(ActiveRecord::ConnectionAdapters::IndexDefinition, name: 'index_posts_on_user_id')] - end - - it 'returns false' do - expect(check.pass?).to be false - end - end - - context 'when all expected indexes are present' do - let(:posts_indexes) do - [instance_double(ActiveRecord::ConnectionAdapters::IndexDefinition, name: 'index_posts_on_user_id')] - end - let(:users_indexes) do - [instance_double(ActiveRecord::ConnectionAdapters::IndexDefinition, name: 'index_users_on_profile_id')] - end - - it 'returns true' do - expect(check.pass?).to be true - end - end - end - - describe '#message' do - subject { check.message } - - it 'sets the class name as the message key' do - expect(subject.key).to eq(:missing_indexes_check) - end - - it 'sets a list of missing indexes as message value' do - expect(subject.value).to eq('index_users_on_profile_id, index_posts_on_user_id') - end - end -end From 47f0faebc9cb197ea7b4cf8d191df0a1ec926f59 Mon Sep 17 00:00:00 2001 From: Emelia Smith <ThisIsMissEm@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:05:59 +0200 Subject: [PATCH 68/84] Implement HTML ruby tags for east-asian languages (#30897) --- lib/sanitize_ext/sanitize_config.rb | 2 +- spec/lib/html_aware_formatter_spec.rb | 8 ++++++++ spec/lib/plain_text_formatter_spec.rb | 8 ++++++++ spec/lib/sanitize/config_spec.rb | 4 ++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/sanitize_ext/sanitize_config.rb b/lib/sanitize_ext/sanitize_config.rb index 70efe7c1a..3379823cb 100644 --- a/lib/sanitize_ext/sanitize_config.rb +++ b/lib/sanitize_ext/sanitize_config.rb @@ -65,7 +65,7 @@ class Sanitize end MASTODON_STRICT = freeze_config( - elements: %w(p br span a del pre blockquote code b strong u i em ul ol li), + elements: %w(p br span a del pre blockquote code b strong u i em ul ol li ruby rt rp), attributes: { 'a' => %w(href rel class translate), diff --git a/spec/lib/html_aware_formatter_spec.rb b/spec/lib/html_aware_formatter_spec.rb index a20902d4f..b75ccb06e 100644 --- a/spec/lib/html_aware_formatter_spec.rb +++ b/spec/lib/html_aware_formatter_spec.rb @@ -41,6 +41,14 @@ RSpec.describe HtmlAwareFormatter do expect(subject).to_not include 'status__content__spoiler-link' end end + + context 'when given text containing ruby tags for east-asian languages' do + let(:text) { '<ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby>' } + + it 'keeps the ruby tags' do + expect(subject).to eq '<ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby>' + end + end end end end diff --git a/spec/lib/plain_text_formatter_spec.rb b/spec/lib/plain_text_formatter_spec.rb index 80b3c331a..b22f473d0 100644 --- a/spec/lib/plain_text_formatter_spec.rb +++ b/spec/lib/plain_text_formatter_spec.rb @@ -72,6 +72,14 @@ RSpec.describe PlainTextFormatter do expect(subject).to eq 'Lorem ipsum' end end + + context 'when text contains HTML ruby tags' do + let(:status) { Fabricate(:status, account: remote_account, text: '<p>Lorem <ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby> ipsum</p>') } + + it 'strips the comment' do + expect(subject).to eq 'Lorem 明日 (Ashita) ipsum' + end + end end end end diff --git a/spec/lib/sanitize/config_spec.rb b/spec/lib/sanitize/config_spec.rb index 2d8dc2f63..fe0b272c0 100644 --- a/spec/lib/sanitize/config_spec.rb +++ b/spec/lib/sanitize/config_spec.rb @@ -18,6 +18,10 @@ describe Sanitize::Config do expect(Sanitize.fragment('<p>Check out:</p><ol start="3" reversed=""><li>Foo</li><li>Bar</li></ol>', subject)).to eq '<p>Check out:</p><ol start="3" reversed=""><li>Foo</li><li>Bar</li></ol>' end + it 'keeps ruby tags' do + expect(Sanitize.fragment('<p><ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby></p>', subject)).to eq '<p><ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby></p>' + end + it 'removes a without href' do expect(Sanitize.fragment('<a>Test</a>', subject)).to eq 'Test' end From 8331f9e379220847020aed9cd0a8a1d6ab0e7d43 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:46:27 +0200 Subject: [PATCH 69/84] New Crowdin Translations (automated) (#30916) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/gl.json | 2 +- app/javascript/mastodon/locales/he.json | 8 +++++++- app/javascript/mastodon/locales/ia.json | 4 ++-- app/javascript/mastodon/locales/sr-Latn.json | 3 +++ app/javascript/mastodon/locales/sr.json | 3 +++ config/locales/ia.yml | 8 ++++---- config/locales/simple_form.ja.yml | 2 +- 7 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index fae48ed06..03287c7e5 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -224,7 +224,7 @@ "domain_pill.their_server": "O seu fogar dixital, onde están as súas publicacións.", "domain_pill.their_username": "O seu identificador único no seu servidor. É posible atopar usuarias co mesmo nome de usuaria en diferentes servidores.", "domain_pill.username": "Nome de usuaria", - "domain_pill.whats_in_a_handle": "Que é o alcume?", + "domain_pill.whats_in_a_handle": "As partes do alcume?", "domain_pill.who_they_are": "O alcume dinos quen é esa persoa e onde está, para que poidas interactuar con ela en toda a web social de <button>plataformas ActivityPub</button>.", "domain_pill.who_you_are": "Como o teu alcume informa de quen es e onde estás, as persoas poden interactuar contigo desde toda a web social de <button>plataformas ActivityPub</button>.", "domain_pill.your_handle": "O teu alcume:", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index e022eac11..8111a56e8 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -35,7 +35,9 @@ "account.follow_back": "לעקוב בחזרה", "account.followers": "עוקבים", "account.followers.empty": "אף אחד לא עוקב אחר המשתמש הזה עדיין.", + "account.followers_counter": "{count, plural,one {עוקב אחד} other {{count} עוקבים}}", "account.following": "נעקבים", + "account.following_counter": "{count, plural,one {עוקב אחרי {count}}other {עוקב אחרי {count}}}", "account.follows.empty": "משתמש זה עדיין לא עוקב אחרי אף אחד.", "account.go_to_profile": "מעבר לפרופיל", "account.hide_reblogs": "להסתיר הידהודים מאת @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} ביקשו לעקוב אחריך", "account.share": "שתף את הפרופיל של @{name}", "account.show_reblogs": "הצג הדהודים מאת @{name}", + "account.statuses_counter": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{count} הודעות} other {{count} הודעות}}", "account.unblock": "להסיר חסימה ל- @{name}", "account.unblock_domain": "הסירי את החסימה של קהילת {domain}", "account.unblock_short": "הסר חסימה", @@ -693,8 +696,11 @@ "server_banner.about_active_users": "משתמשים פעילים בשרת ב־30 הימים האחרונים (משתמשים פעילים חודשיים)", "server_banner.active_users": "משתמשים פעילים", "server_banner.administered_by": "מנוהל ע\"י:", + "server_banner.is_one_of_many": "{domain} הוא שרת אחד משרתי מסטודון עצמאיים רבים שדרגם תוכלו להשתתף בפדיוורס (רשת חברתית מבוזרת).", "server_banner.server_stats": "סטטיסטיקות שרת:", "sign_in_banner.create_account": "יצירת חשבון", + "sign_in_banner.follow_anyone": "תוכלו לעקוב אחרי כל משמתמש בפדיוורס ולקרוא הכל לפי סדר הפרסום בציר הזמן. אין אלגוריתמים, פרסומות, או קליקבייט מטעם בעלי הרשת.", + "sign_in_banner.mastodon_is": "מסטודון הוא הדרך הטובה ביותר לעקוב אחרי מה שקורה.", "sign_in_banner.sign_in": "התחברות", "sign_in_banner.sso_redirect": "התחברות/הרשמה", "status.admin_account": "פתח/י ממשק ניהול עבור @{name}", @@ -771,7 +777,7 @@ "timeline_hint.resources.followers": "עוקבים", "timeline_hint.resources.follows": "נעקבים", "timeline_hint.resources.statuses": "הודעות ישנות יותר", - "trends.counter_by_accounts": "{count, plural, one {אדם {count}} other {{count} א.נשים}} {days, plural, one {מאז אתמול} two {ביומיים האחרונים} other {במשך {days} הימים האחרונים}}", + "trends.counter_by_accounts": "{count, plural, one {אדם אחד} other {{count} א.נשים}} {days, plural, one {מאז אתמול} two {ביומיים האחרונים} other {במשך {days} הימים האחרונים}}", "trends.trending_now": "נושאים חמים", "ui.beforeunload": "הטיוטא תאבד אם תעזבו את מסטודון.", "units.short.billion": "{count} מליארד", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index a2e64c10f..ace6402ee 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -354,7 +354,7 @@ "home.pending_critical_update.link": "Vider actualisationes", "home.pending_critical_update.title": "Actualisation de securitate critic disponibile!", "home.show_announcements": "Monstrar annuncios", - "interaction_modal.description.favourite": "Con un conto sur Mastodon, tu pote marcar iste message como favorite pro informar le autor que tu lo apprecia e salveguarda pro plus tarde.", + "interaction_modal.description.favourite": "Con un conto sur Mastodon, tu pote marcar iste message como favorite pro informar le autor que tu lo apprecia e lo salva pro plus tarde.", "interaction_modal.description.follow": "Con un conto sur Mastodon, tu pote sequer {name} e reciper su messages in tu fluxo de initio.", "interaction_modal.description.reblog": "Con un conto sur Mastodon, tu pote impulsar iste message pro condivider lo con tu proprie sequitores.", "interaction_modal.description.reply": "Con un conto sur Mastodon, tu pote responder a iste message.", @@ -764,7 +764,7 @@ "status.unmute_conversation": "Non plus silentiar conversation", "status.unpin": "Disfixar del profilo", "subscribed_languages.lead": "Solmente le messages in le linguas seligite apparera in tu chronologias de initio e de listas post le cambiamento. Selige necun pro reciper messages in tote le linguas.", - "subscribed_languages.save": "Salveguardar le cambiamentos", + "subscribed_languages.save": "Salvar le cambiamentos", "subscribed_languages.target": "Cambiar le linguas subscribite pro {target}", "tabs_bar.home": "Initio", "tabs_bar.notifications": "Notificationes", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 93c3b8fe2..71b69d428 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -35,7 +35,9 @@ "account.follow_back": "Uzvrati praćenje", "account.followers": "Pratioci", "account.followers.empty": "Još uvek niko ne prati ovog korisnika.", + "account.followers_counter": "{count, plural, one {{counter} pratilac} few {{counter} pratioca} other {{counter} pratilaca}}", "account.following": "Prati", + "account.following_counter": "{count, plural, one {{counter} prati} few {{counter} prati} other {{counter} prati}}", "account.follows.empty": "Ovaj korisnik još uvek nikog ne prati.", "account.go_to_profile": "Idi na profil", "account.hide_reblogs": "Sakrij podržavanja @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} je zatražio da vas prati", "account.share": "Podeli profil korisnika @{name}", "account.show_reblogs": "Prikaži podržavanja od korisnika @{name}", + "account.statuses_counter": "{count, plural, one {{counter} objava} few {{counter} objave} other {{counter} objava}}", "account.unblock": "Odblokiraj korisnika @{name}", "account.unblock_domain": "Odblokiraj domen {domain}", "account.unblock_short": "Odblokiraj", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 0273002b3..2c4649f9d 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -35,7 +35,9 @@ "account.follow_back": "Узврати праћење", "account.followers": "Пратиоци", "account.followers.empty": "Још увек нико не прати овог корисника.", + "account.followers_counter": "{count, plural, one {{counter} пратилац} few {{counter} пратиоца} other {{counter} пратилаца}}", "account.following": "Прати", + "account.following_counter": "{count, plural, one {{counter} прати} few {{counter} прати} other {{counter} прати}}", "account.follows.empty": "Овај корисник још увек никог не прати.", "account.go_to_profile": "Иди на профил", "account.hide_reblogs": "Сакриј подржавања од @{name}", @@ -61,6 +63,7 @@ "account.requested_follow": "{name} је затражио да вас прати", "account.share": "Подели профил корисника @{name}", "account.show_reblogs": "Прикажи подржавања од корисника @{name}", + "account.statuses_counter": "{count, plural, one {{counter} објава} few {{counter} објаве} other {{counter} објава}}", "account.unblock": "Одблокирај корисника @{name}", "account.unblock_domain": "Одблокирај домен {domain}", "account.unblock_short": "Одблокирај", diff --git a/config/locales/ia.yml b/config/locales/ia.yml index 4932d42ac..7350ffcee 100644 --- a/config/locales/ia.yml +++ b/config/locales/ia.yml @@ -574,7 +574,7 @@ ia: enabled: Activate inbox_url: URL del repetitor pending: Attende le approbation del repetitor - save_and_enable: Salveguardar e activar + save_and_enable: Salvar e activar setup: Crear un connexion con un repetitor signatures_not_enabled: Le repetitores pote non functionar correctemente durante que le modo secur o le modo de federation limitate es activate status: Stato @@ -1276,7 +1276,7 @@ ia: other: "%{count} messages individual celate" title: Filtros new: - save: Salveguardar nove filtro + save: Salvar nove filtro title: Adder nove filtro statuses: back_to_filter: Retro al filtro @@ -1294,14 +1294,14 @@ ia: one: "<strong>%{count}</strong> elemento correspondente al recerca es seligite." other: Tote le <strong>%{count}</strong> elementos correspondente al recerca es seligite. cancel: Cancellar - changes_saved_msg: Cambios salveguardate con successo! + changes_saved_msg: Le cambiamentos ha essite salvate! confirm: Confirmar copy: Copiar delete: Deler deselect: Deseliger toto none: Necun order_by: Ordinar per - save_changes: Salvar le cambios + save_changes: Salvar le cambiamentos select_all_matching_items: one: Selige %{count} elemento correspondente a tu recerca. other: Selige %{count} elementos correspondente a tu recerca. diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index c0698c3f7..664082dab 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -81,7 +81,7 @@ ja: backups_retention_period: ユーザーには、後でダウンロードするために投稿のアーカイブを生成する機能があります。正の値に設定すると、これらのアーカイブは指定された日数後に自動的にストレージから削除されます。 bootstrap_timeline_accounts: これらのアカウントは、新しいユーザー向けのおすすめユーザーの一番上にピン留めされます。 closed_registrations_message: アカウント作成を停止している時に表示されます - content_cache_retention_period: 他のサーバーからのすべての投稿(ブーストや返信を含む)は、指定された日数が経過すると、ローカルユーザーとのやりとりに関係なく削除されます。これには、ローカルユーザーがブックマークやお気に入りとして登録した投稿も含まれます。異なるサーバーのユーザー間の非公開な変身も失われ、復元することは不可能です。この設定の使用は特別な目的のインスタンスのためのものであり、一般的な目的のサーバーで使用するした場合、多くのユーザーの期待を裏切ることになります。 + content_cache_retention_period: 他のサーバーからのすべての投稿(ブーストや返信を含む)は、指定された日数が経過すると、ローカルユーザーとのやりとりに関係なく削除されます。これには、ローカルユーザーがブックマークやお気に入りとして登録した投稿も含まれます。異なるサーバーのユーザー間の非公開な返信も失われ、復元することは不可能です。この設定の使用は特別な目的のインスタンスのためのものであり、一般的な目的のサーバーで使用した場合、多くのユーザーの期待を裏切ることになります。 custom_css: ウェブ版のMastodonでカスタムスタイルを適用できます。 favicon: デフォルトのMastodonのブックマークアイコンを独自のアイコンで上書きします。WEBP、PNG、GIF、JPGが利用可能です。 mascot: 上級者向けWebインターフェースのイラストを上書きします。 From 528661a091bbef5b5359bfacd1478231a1cb7d10 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:46:08 +0200 Subject: [PATCH 70/84] fix(deps): update dependency pino-http to v10.2.0 (#30913) 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 8e72138a1..52a8fff72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13139,14 +13139,14 @@ __metadata: linkType: hard "pino-http@npm:^10.0.0": - version: 10.1.0 - resolution: "pino-http@npm:10.1.0" + version: 10.2.0 + resolution: "pino-http@npm:10.2.0" dependencies: get-caller-file: "npm:^2.0.5" pino: "npm:^9.0.0" pino-std-serializers: "npm:^7.0.0" process-warning: "npm:^3.0.0" - checksum: 10c0/d97691f2ee248b0aca0e49169d0c7ca0d4c604ee57b63ae264a6f9914fc7277cace74686d5088a876f8152a8d5b8211af904b2d24a516728a662de0e9cc79e9f + checksum: 10c0/0b79cd3602531ee5043693e2a3ccf9d955bd93759e80c0b3a458b95b241f36ca8ebc72c8050b395e9d8fcb9581ebc18ecd6b7dc136526bebe924bc5c5079374d languageName: node linkType: hard From b73014761807f3171b7ecb46052e9aa618b9c029 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:46:14 +0200 Subject: [PATCH 71/84] fix(deps): update dependency ws to v8.18.0 (#30914) 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 52a8fff72..d61ef4163 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18491,8 +18491,8 @@ __metadata: linkType: hard "ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.17.0": - version: 8.17.1 - resolution: "ws@npm:8.17.1" + version: 8.18.0 + resolution: "ws@npm:8.18.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -18501,7 +18501,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10c0/f4a49064afae4500be772abdc2211c8518f39e1c959640457dcee15d4488628620625c783902a52af2dd02f68558da2868fd06e6fd0e67ebcd09e6881b1b5bfe + checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 languageName: node linkType: hard From 395f17ca17b27f9e722425a4d76ef1b02bcebea8 Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Thu, 4 Jul 2024 16:11:28 +0200 Subject: [PATCH 72/84] Merge pull request from GHSA-vp5r-5pgw-jwqx * Fix streaming sessions not being closed when revoking access to an app * Add tests for GHSA-7w3c-p9j8-mq3x --- .../oauth/authorized_applications_controller.rb | 1 + app/lib/application_extension.rb | 8 +++++--- .../oauth/authorized_applications_controller_spec.rb | 6 ++++++ .../settings/applications_controller_spec.rb | 10 +++++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 8440df6b7..7bb22453c 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -17,6 +17,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio def destroy Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner) + Doorkeeper::Application.find_by(id: params[:id])&.close_streaming_sessions(current_resource_owner) super end diff --git a/app/lib/application_extension.rb b/app/lib/application_extension.rb index 2fea1057c..d7aaeba5b 100644 --- a/app/lib/application_extension.rb +++ b/app/lib/application_extension.rb @@ -16,7 +16,7 @@ module ApplicationExtension # dependent: delete_all, which means the ActiveRecord callback in # AccessTokenExtension is not run, so instead we manually announce to # streaming that these tokens are being deleted. - before_destroy :push_to_streaming_api, prepend: true + before_destroy :close_streaming_sessions, prepend: true end def confirmation_redirect_uri @@ -29,10 +29,12 @@ module ApplicationExtension redirect_uri.split end - def push_to_streaming_api + def close_streaming_sessions(resource_owner = nil) # TODO: #28793 Combine into a single topic payload = Oj.dump(event: :kill) - access_tokens.in_batches do |tokens| + scope = access_tokens + scope = scope.where(resource_owner_id: resource_owner.id) unless resource_owner.nil? + scope.in_batches do |tokens| redis.pipelined do |pipeline| tokens.ids.each do |id| pipeline.publish("timeline:access_token:#{id}", payload) diff --git a/spec/controllers/oauth/authorized_applications_controller_spec.rb b/spec/controllers/oauth/authorized_applications_controller_spec.rb index b46b944d0..3fd9f9499 100644 --- a/spec/controllers/oauth/authorized_applications_controller_spec.rb +++ b/spec/controllers/oauth/authorized_applications_controller_spec.rb @@ -50,9 +50,11 @@ describe Oauth::AuthorizedApplicationsController do let!(:application) { Fabricate(:application) } let!(:access_token) { Fabricate(:accessible_access_token, application: application, resource_owner_id: user.id) } let!(:web_push_subscription) { Fabricate(:web_push_subscription, user: user, access_token: access_token) } + let(:redis_pipeline_stub) { instance_double(Redis::Namespace, publish: nil) } before do sign_in user, scope: :user + allow(redis).to receive(:pipelined).and_yield(redis_pipeline_stub) post :destroy, params: { id: application.id } end @@ -67,5 +69,9 @@ describe Oauth::AuthorizedApplicationsController do it 'removes the web_push_subscription' do expect { web_push_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound) end + + it 'sends a session kill payload to the streaming server' do + expect(redis_pipeline_stub).to have_received(:publish).with("timeline:access_token:#{access_token.id}", '{"event":"kill"}') + end end end diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb index ccbb63491..ce2e0749a 100644 --- a/spec/controllers/settings/applications_controller_spec.rb +++ b/spec/controllers/settings/applications_controller_spec.rb @@ -147,14 +147,22 @@ describe Settings::ApplicationsController do end describe 'destroy' do + let(:redis_pipeline_stub) { instance_double(Redis::Namespace, publish: nil) } + let!(:access_token) { Fabricate(:accessible_access_token, application: app) } + before do + allow(redis).to receive(:pipelined).and_yield(redis_pipeline_stub) post :destroy, params: { id: app.id } end - it 'redirects back to applications page and removes the app' do + it 'redirects back to applications page removes the app' do expect(response).to redirect_to(settings_applications_path) expect(Doorkeeper::Application.find_by(id: app.id)).to be_nil end + + it 'sends a session kill payload to the streaming server' do + expect(redis_pipeline_stub).to have_received(:publish).with("timeline:access_token:#{access_token.id}", '{"event":"kill"}') + end end describe 'regenerate' do From 502cf75b160c76f963a179d46498e3ea1a9fefca Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Thu, 4 Jul 2024 16:26:49 +0200 Subject: [PATCH 73/84] Merge pull request from GHSA-58x8-3qxw-6hm7 * Fix insufficient permission checking for public timeline endpoints Note that this changes unauthenticated access failure code from 401 to 422 * Add more tests for public timelines * Require user token in `/api/v1/statuses/:id/translate` and `/api/v1/scheduled_statuses` --- .../api/v1/scheduled_statuses_controller.rb | 1 + .../v1/statuses/translations_controller.rb | 1 + .../api/v1/timelines/base_controller.rb | 6 +++++ .../api/v1/timelines/link_controller.rb | 6 +---- .../api/v1/timelines/public_controller.rb | 6 +---- .../api/v1/timelines/tag_controller.rb | 2 +- spec/requests/api/v1/scheduled_status_spec.rb | 11 ++++++++ .../api/v1/statuses/translations_spec.rb | 16 ++++++++++++ spec/requests/api/v1/timelines/link_spec.rb | 20 +++++++++++--- spec/requests/api/v1/timelines/public_spec.rb | 26 ++++++++++++++----- spec/requests/api/v1/timelines/tag_spec.rb | 10 ++++--- 11 files changed, 82 insertions(+), 23 deletions(-) diff --git a/app/controllers/api/v1/scheduled_statuses_controller.rb b/app/controllers/api/v1/scheduled_statuses_controller.rb index 45ee58651..c62305d71 100644 --- a/app/controllers/api/v1/scheduled_statuses_controller.rb +++ b/app/controllers/api/v1/scheduled_statuses_controller.rb @@ -6,6 +6,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, except: [:update, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:update, :destroy] + before_action :require_user! before_action :set_statuses, only: :index before_action :set_status, except: :index diff --git a/app/controllers/api/v1/statuses/translations_controller.rb b/app/controllers/api/v1/statuses/translations_controller.rb index 7d406b0a3..8cf495f78 100644 --- a/app/controllers/api/v1/statuses/translations_controller.rb +++ b/app/controllers/api/v1/statuses/translations_controller.rb @@ -2,6 +2,7 @@ class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseController before_action -> { doorkeeper_authorize! :read, :'read:statuses' } + before_action :require_user! before_action :set_translation rescue_from TranslationService::NotConfiguredError, with: :not_found diff --git a/app/controllers/api/v1/timelines/base_controller.rb b/app/controllers/api/v1/timelines/base_controller.rb index e79eba79e..1dba4a5bb 100644 --- a/app/controllers/api/v1/timelines/base_controller.rb +++ b/app/controllers/api/v1/timelines/base_controller.rb @@ -3,8 +3,14 @@ class Api::V1::Timelines::BaseController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + before_action :require_user!, if: :require_auth? + private + def require_auth? + !Setting.timeline_preview + end + def pagination_collection @statuses end diff --git a/app/controllers/api/v1/timelines/link_controller.rb b/app/controllers/api/v1/timelines/link_controller.rb index af962c430..37ed084f0 100644 --- a/app/controllers/api/v1/timelines/link_controller.rb +++ b/app/controllers/api/v1/timelines/link_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Timelines::LinkController < Api::V1::Timelines::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth? + before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action :set_preview_card before_action :set_statuses @@ -17,10 +17,6 @@ class Api::V1::Timelines::LinkController < Api::V1::Timelines::BaseController private - def require_auth? - !Setting.timeline_preview - end - def set_preview_card @preview_card = PreviewCard.joins(:trend).merge(PreviewCardTrend.allowed).find_by!(url: params[:url]) end diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index d164854d6..029e8fc2c 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController - before_action :require_user!, only: [:show], if: :require_auth? + before_action -> { authorize_if_got_token! :read, :'read:statuses' } PERMITTED_PARAMS = %i(local remote limit only_media).freeze @@ -13,10 +13,6 @@ class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController private - def require_auth? - !Setting.timeline_preview - end - def load_statuses preloaded_public_statuses_page end diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index 3bf8f374e..2b097aab0 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth? + before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action :load_tag PERMITTED_PARAMS = %i(local limit only_media).freeze diff --git a/spec/requests/api/v1/scheduled_status_spec.rb b/spec/requests/api/v1/scheduled_status_spec.rb index 49ccde275..f4612410b 100644 --- a/spec/requests/api/v1/scheduled_status_spec.rb +++ b/spec/requests/api/v1/scheduled_status_spec.rb @@ -25,6 +25,17 @@ describe 'Scheduled Statuses' do it_behaves_like 'forbidden for wrong scope', 'write write:statuses' end + context 'with an application token' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read:statuses') } + + it 'returns http unprocessable entity' do + get api_v1_scheduled_statuses_path, headers: headers + + expect(response) + .to have_http_status(422) + end + end + context 'with correct scope' do let(:scopes) { 'read:statuses' } diff --git a/spec/requests/api/v1/statuses/translations_spec.rb b/spec/requests/api/v1/statuses/translations_spec.rb index 5b0a99456..e2ab5d0b8 100644 --- a/spec/requests/api/v1/statuses/translations_spec.rb +++ b/spec/requests/api/v1/statuses/translations_spec.rb @@ -8,6 +8,22 @@ describe 'API V1 Statuses Translations' do let(:scopes) { 'read:statuses' } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + context 'with an application token' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: scopes) } + + describe 'POST /api/v1/statuses/:status_id/translate' do + let(:status) { Fabricate(:status, account: user.account, text: 'Hola', language: 'es') } + + before do + post "/api/v1/statuses/#{status.id}/translate", headers: headers + end + + it 'returns http unprocessable entity' do + expect(response).to have_http_status(422) + end + end + end + context 'with an oauth token' do describe 'POST /api/v1/statuses/:status_id/translate' do let(:status) { Fabricate(:status, account: user.account, text: 'Hola', language: 'es') } diff --git a/spec/requests/api/v1/timelines/link_spec.rb b/spec/requests/api/v1/timelines/link_spec.rb index a219c9bcd..e1c914ab8 100644 --- a/spec/requests/api/v1/timelines/link_spec.rb +++ b/spec/requests/api/v1/timelines/link_spec.rb @@ -41,6 +41,8 @@ describe 'Link' do end end + it_behaves_like 'forbidden for wrong scope', 'profile' + context 'when there is no preview card' do let(:preview_card) { nil } @@ -80,13 +82,25 @@ describe 'Link' do Form::AdminSettings.new(timeline_preview: false).save end - context 'when the user is not authenticated' do + it_behaves_like 'forbidden for wrong scope', 'profile' + + context 'without an authentication token' do let(:headers) { {} } - it 'returns http unauthorized' do + it 'returns http unprocessable entity' do subject - expect(response).to have_http_status(401) + expect(response).to have_http_status(422) + end + end + + context 'with an application access token, not bound to a user' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: scopes) } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) end end diff --git a/spec/requests/api/v1/timelines/public_spec.rb b/spec/requests/api/v1/timelines/public_spec.rb index 364e48d3d..100f6c1bf 100644 --- a/spec/requests/api/v1/timelines/public_spec.rb +++ b/spec/requests/api/v1/timelines/public_spec.rb @@ -34,6 +34,8 @@ describe 'Public' do context 'when the instance allows public preview' do let(:expected_statuses) { [local_status, remote_status, media_status] } + it_behaves_like 'forbidden for wrong scope', 'profile' + context 'with an authorized user' do it_behaves_like 'a successful request to the public timeline' end @@ -99,13 +101,9 @@ describe 'Public' do Form::AdminSettings.new(timeline_preview: false).save end - context 'with an authenticated user' do - let(:expected_statuses) { [local_status, remote_status, media_status] } + it_behaves_like 'forbidden for wrong scope', 'profile' - it_behaves_like 'a successful request to the public timeline' - end - - context 'with an unauthenticated user' do + context 'without an authentication token' do let(:headers) { {} } it 'returns http unprocessable entity' do @@ -114,6 +112,22 @@ describe 'Public' do expect(response).to have_http_status(422) end end + + context 'with an application access token, not bound to a user' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: scopes) } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'with an authenticated user' do + let(:expected_statuses) { [local_status, remote_status, media_status] } + + it_behaves_like 'a successful request to the public timeline' + end end end end diff --git a/spec/requests/api/v1/timelines/tag_spec.rb b/spec/requests/api/v1/timelines/tag_spec.rb index 861134170..5e1415bb1 100644 --- a/spec/requests/api/v1/timelines/tag_spec.rb +++ b/spec/requests/api/v1/timelines/tag_spec.rb @@ -30,6 +30,8 @@ RSpec.describe 'Tag' do let(:params) { {} } let(:hashtag) { 'life' } + it_behaves_like 'forbidden for wrong scope', 'profile' + context 'when given only one hashtag' do let(:expected_statuses) { [life_status] } @@ -93,13 +95,15 @@ RSpec.describe 'Tag' do Form::AdminSettings.new(timeline_preview: false).save end - context 'when the user is not authenticated' do + it_behaves_like 'forbidden for wrong scope', 'profile' + + context 'without an authentication token' do let(:headers) { {} } - it 'returns http unauthorized' do + it 'returns http unprocessable entity' do subject - expect(response).to have_http_status(401) + expect(response).to have_http_status(422) end end From d3a056adfd0eca4fff57dde65ee9d95ce7c9bb3e Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Thu, 4 Jul 2024 16:45:52 +0200 Subject: [PATCH 74/84] Merge pull request from GHSA-xjvf-fm67-4qc3 --- app/lib/activitypub/activity/create.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 7ec7e84bd..5d700b496 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -104,7 +104,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def find_existing_status status = status_from_uri(object_uri) status ||= Status.find_by(uri: @object['atomUri']) if @object['atomUri'].present? - status + status if status&.account_id == @account.id end def process_status_params From df9e26158d9787859b24bdc276af478abf05e1af Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Thu, 4 Jul 2024 16:59:54 +0200 Subject: [PATCH 75/84] Bump version to v4.3.0-alpha.5 (#30920) --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ docker-compose.yml | 6 +++--- lib/mastodon/version.rb | 2 +- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9b24d6f1..7c3d96ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,37 @@ All notable changes to this project will be documented in this file. +## [4.2.10] - 2024-07-04 + +### Security + +- Fix incorrect permission checking on multiple API endpoints ([GHSA-58x8-3qxw-6hm7](https://github.com/mastodon/mastodon/security/advisories/GHSA-58x8-3qxw-6hm7)) +- Fix incorrect authorship checking when processing some activities (CVE-2024-37903, [GHSA-xjvf-fm67-4qc3](https://github.com/mastodon/mastodon/security/advisories/GHSA-xjvf-fm67-4qc3)) +- Fix ongoing streaming sessions not being invalidated when application tokens get revoked ([GHSA-vp5r-5pgw-jwqx](https://github.com/mastodon/mastodon/security/advisories/GHSA-vp5r-5pgw-jwqx)) +- Update dependencies + +### Added + +- Add yarn version specification to avoid confusion with Yarn 3 and Yarn 4 + +### Changed + +- Change preview cards generation to skip unusually long URLs ([oneiros](https://github.com/mastodon/mastodon/pull/30854)) +- Change search modifiers to be case-insensitive ([Gargron](https://github.com/mastodon/mastodon/pull/30865)) +- Change `STATSD_ADDR` handling to emit a warning rather than crashing if the address is unreachable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30691)) +- Change PWA start URL from `/home` to `/` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27377)) + +### Removed + +- Removed dependency on `posix-spawn` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18559)) + +### Fixed + +- Fix scheduled statuses scheduled in less than 5 minutes being immediately published ([danielmbrasil](https://github.com/mastodon/mastodon/pull/30584)) +- Fix encoding detection for link cards ([oneiros](https://github.com/mastodon/mastodon/pull/30780)) +- Fix `/admin/accounts/:account_id/statuses/:id` for edited posts with media attachments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30819)) +- Fix duplicate `@context` attribute in user archive export ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30653)) + ## [4.2.9] - 2024-05-30 ### Security diff --git a/docker-compose.yml b/docker-compose.yml index 7089b0d14..7a6f9be50 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,7 +58,7 @@ services: web: build: . - image: ghcr.io/mastodon/mastodon:v4.2.9 + image: ghcr.io/mastodon/mastodon:v4.2.10 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -79,7 +79,7 @@ services: streaming: build: . - image: ghcr.io/mastodon/mastodon:v4.2.9 + image: ghcr.io/mastodon/mastodon:v4.2.10 restart: always env_file: .env.production command: node ./streaming @@ -97,7 +97,7 @@ services: sidekiq: build: . - image: ghcr.io/mastodon/mastodon:v4.2.9 + image: ghcr.io/mastodon/mastodon:v4.2.10 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 03972ba93..96ad40928 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -17,7 +17,7 @@ module Mastodon end def default_prerelease - 'alpha.4' + 'alpha.5' end def prerelease From 8de5df225edb89ef13f5ea2697876018953a97d9 Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Fri, 5 Jul 2024 10:54:45 +0200 Subject: [PATCH 76/84] Change instructions to use `bundle exec rails` instead of `rake` (#30917) --- .env.production.sample | 6 +++--- config/initializers/vapid.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.env.production.sample b/.env.production.sample index 0bf01bdc3..0b458a1aa 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -1,5 +1,5 @@ # This is a sample configuration file. You can generate your configuration -# with the `rake mastodon:setup` interactive setup wizard, but to customize +# with the `bundle exec rails mastodon:setup` interactive setup wizard, but to customize # your setup even further, you'll need to edit it manually. This sample does # not demonstrate all available configuration options. Please look at # https://docs.joinmastodon.org/admin/config/ for the full documentation. @@ -40,14 +40,14 @@ ES_PASS=password # Secrets # ------- -# Make sure to use `rake secret` to generate secrets +# Make sure to use `bundle exec rails secret` to generate secrets # ------- SECRET_KEY_BASE= OTP_SECRET= # Web Push # -------- -# Generate with `rake mastodon:webpush:generate_vapid_key` +# Generate with `bundle exec rails mastodon:webpush:generate_vapid_key` # -------- VAPID_PRIVATE_KEY= VAPID_PUBLIC_KEY= diff --git a/config/initializers/vapid.rb b/config/initializers/vapid.rb index 7dd870c8b..551ede34f 100644 --- a/config/initializers/vapid.rb +++ b/config/initializers/vapid.rb @@ -5,7 +5,7 @@ Rails.application.configure do # You should only generate this once per instance. If you later decide to change it, all push subscription will # be invalidated, requiring the users to access the website again to resubscribe. # - # Generate with `rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose) + # Generate with `bundle exec rails mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web bundle exec rails mastodon:webpush:generate_vapid_key` if you use docker compose) # # For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html From 8c375d8a5c899b735950a7a88670a1bb2b7adcd2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski <matt@jankowski.online> Date: Fri, 5 Jul 2024 04:57:54 -0400 Subject: [PATCH 77/84] Use `scope module: ...` for settings/2FA routes (#30919) --- config/routes/settings.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/routes/settings.rb b/config/routes/settings.rb index 888fa9ecb..b14606656 100644 --- a/config/routes/settings.rb +++ b/config/routes/settings.rb @@ -37,13 +37,13 @@ namespace :settings do end end - resource :otp_authentication, only: [:show, :create], controller: 'two_factor_authentication/otp_authentication' + scope module: :two_factor_authentication do + resource :otp_authentication, only: [:show, :create], controller: :otp_authentication - resources :webauthn_credentials, only: [:index, :new, :create, :destroy], - path: 'security_keys', - controller: 'two_factor_authentication/webauthn_credentials' do - collection do - get :options + resources :webauthn_credentials, only: [:index, :new, :create, :destroy], path: 'security_keys' do + collection do + get :options + end end end From 81547845ac73f860df2c86594fe0bf346745ce0e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:09:40 +0200 Subject: [PATCH 78/84] New Crowdin Translations (automated) (#30925) Co-authored-by: GitHub Actions <noreply@github.com> --- app/javascript/mastodon/locales/ja.json | 2 ++ app/javascript/mastodon/locales/uk.json | 2 +- config/locales/gl.yml | 42 ++++++++++++------------- config/locales/simple_form.gl.yml | 2 +- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 575c68de0..60788baff 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -411,6 +411,7 @@ "limited_account_hint.action": "構わず表示する", "limited_account_hint.title": "このプロフィールは{domain}のモデレーターによって非表示にされています。", "link_preview.author": "{name}", + "link_preview.more_from_author": "{name}さんの投稿をもっと読む", "lists.account.add": "リストに追加", "lists.account.remove": "リストから外す", "lists.delete": "リストを削除", @@ -693,6 +694,7 @@ "server_banner.administered_by": "管理者", "server_banner.server_stats": "サーバーの情報", "sign_in_banner.create_account": "アカウント作成", + "sign_in_banner.follow_anyone": "連合内の誰でもフォローして投稿を時系列で見ることができます。アルゴリズム、広告、クリックベイトはありません。", "sign_in_banner.sign_in": "ログイン", "sign_in_banner.sso_redirect": "ログインまたは登録", "status.admin_account": "@{name}さんのモデレーション画面を開く", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 150b808f8..5d7d040fd 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -32,7 +32,7 @@ "account.featured_tags.last_status_never": "Немає дописів", "account.featured_tags.title": "{name} виділяє хештеґи", "account.follow": "Підписатися", - "account.follow_back": "Підписатися взаємно", + "account.follow_back": "Стежити також", "account.followers": "Підписники", "account.followers.empty": "Ніхто ще не підписаний на цього користувача.", "account.followers_counter": "{count, plural, one {{counter} підписник} few {{counter} підписники} many {{counter} підписників} other {{counter} підписники}}", diff --git a/config/locales/gl.yml b/config/locales/gl.yml index ad4744e15..c9f08dcad 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -31,18 +31,18 @@ gl: created_msg: Nota de moderación creada correctamente! destroyed_msg: Nota de moderación eliminada de xeito correcto! accounts: - add_email_domain_block: Bloquear o dominio do email + add_email_domain_block: Bloquear o dominio do enderezo approve: Aprobar approved_msg: Aprobada a solicitude de rexistro de %{username} are_you_sure: Está segura? avatar: Imaxe de perfil by_domain: Dominio change_email: - changed_msg: Email mudado de xeito correcto! - current_email: Email actual - label: Mudar email - new_email: Novo email - submit: Mudar email + changed_msg: Correo cambiado de xeito correcto! + current_email: Enderezo actual + label: Cambiar de enderezo + new_email: Novo enderezo + submit: Cambiar de enderezo title: Mudar email de %{username} change_role: changed_msg: Rol mudado correctamente! @@ -64,10 +64,10 @@ gl: display_name: Nome a amosar domain: Dominio edit: Editar - email: Email - email_status: Estado do email + email: Enderezo de correo + email_status: Estado do correo enable: Activar - enable_sign_in_token_auth: Activar autenticación cun token no email + enable_sign_in_token_auth: Activar autenticación cun token no correo enabled: Activado enabled_msg: Desbloqueada a conta de %{username} followers: Seguidoras @@ -132,7 +132,7 @@ gl: resubscribe: Resubscribir role: Rol search: Procurar - search_same_email_domain: Outras usuarias co mesmo dominio de email + search_same_email_domain: Outras usuarias co mesmo dominio de correo search_same_ip: Outras usuarias co mesmo IP security: Seguridade security_measures: @@ -154,9 +154,9 @@ gl: suspension_irreversible: Elimináronse de xeito irreversible os datos desta conta. Podes reactivar a conta para facela usable novamente pero non recuperará os datos eliminados. suspension_reversible_hint_html: Esta conta foi suspendida, e os datos serán totalmente eliminados o %{date}. Ata entón, a conta pode ser restaurada sen danos. Se desexas eliminar agora mesmo todos os datos da conta, podes facelo aquí embaixo. title: Contas - unblock_email: Desbloquear enderezo de email - unblocked_email_msg: Enderezo de email de %{username} desbloqueado - unconfirmed_email: Email non confirmado + unblock_email: Desbloquear enderezo de correo + unblocked_email_msg: Enderezo de correo de %{username} desbloqueado + unconfirmed_email: Enderezo de correo sen confirmar undo_sensitized: Desmarcar como sensible undo_silenced: Desfacer acalar undo_suspension: Desfacer suspensión @@ -173,12 +173,12 @@ gl: approve_appeal: Aprobar apelación approve_user: Aprobar Usuaria assigned_to_self_report: Asignar denuncia - change_email_user: Editar email da usuaria + change_email_user: Editar correo electrónico da usuaria change_role_user: Cambiar Rol da Usuaria confirm_user: Confirmar usuaria create_account_warning: Crear aviso create_announcement: Crear anuncio - create_canonical_email_block: Crear Bloqueo de email + create_canonical_email_block: Crear Bloqueo de correo electrónico create_custom_emoji: Crear emoticonas personalizadas create_domain_allow: Crear Dominio Permitido create_domain_block: Crear bloquedo do Dominio @@ -188,7 +188,7 @@ gl: create_user_role: Crear Rol demote_user: Degradar usuaria destroy_announcement: Eliminar anuncio - destroy_canonical_email_block: Eliminar Bloqueo de email + destroy_canonical_email_block: Eliminar Bloqueo de correo electrónico destroy_custom_emoji: Eliminar emoticona personalizada destroy_domain_allow: Eliminar Dominio permitido destroy_domain_block: Eliminar bloqueo do Dominio @@ -200,7 +200,7 @@ gl: destroy_user_role: Eliminar Rol disable_2fa_user: Desactivar 2FA disable_custom_emoji: Desactivar emoticona personalizada - disable_sign_in_token_auth_user: Desactivar Autenticación por token no email para Usuaria + disable_sign_in_token_auth_user: Desactivar Autenticación con token no correo para Usuaria disable_user: Desactivar usuaria enable_custom_emoji: Activar emoticona personalizada enable_sign_in_token_auth_user: Activar Autenticación con token no email para Usuaria @@ -211,14 +211,14 @@ gl: reject_user: Rexeitar Usuaria remove_avatar_user: Eliminar avatar reopen_report: Reabrir denuncia - resend_user: Reenviar o email de confirmación + resend_user: Reenviar o correo de confirmación reset_password_user: Restabelecer contrasinal resolve_report: Resolver denuncia sensitive_account: Marca o multimedia da túa conta como sensible silence_account: Silenciar conta suspend_account: Suspender conta unassigned_report: Desasignar denuncia - unblock_email_account: Desbloquear enderezo de email + unblock_email_account: Desbloquear enderezo de correo unsensitive_account: Retira a marca de sensible do multimedia da conta unsilence_account: Deixar de silenciar conta unsuspend_account: Retirar suspensión de conta @@ -660,7 +660,7 @@ gl: delete_data_html: Eliminar o perfil e contidos de <strong>@%{acct}</strong> para os próximos 30 días a non ser que sexa suspendida nese período preview_preamble_html: "<strong>@%{acct}</strong> vai recibir un aviso co seguinte contido:" record_strike_html: Anotar un aviso contra <strong>@%{acct}</strong> para axudarche a xestionar futuros problemas con esta conta - send_email_html: Enviar un email de aviso a <strong>@%{acct}</strong> + send_email_html: Enviar un correo de aviso a <strong>@%{acct}</strong> warning_placeholder: Razóns adicionais optativas para a acción de moderación. target_origin: Orixe da conta denunciada title: Denuncias @@ -1060,7 +1060,7 @@ gl: redirect_to_app_html: Ímoste redirixir á app <strong>%{app_name}</strong>. Se iso non acontece, proba %{clicking_this_link} ou volve ti manualmente á app. registration_complete: Completouse a creación da conta en %{domain}! welcome_title: Benvida, %{name}! - wrong_email_hint: Se o enderezo de email non é correcto, podes cambialo nos axustes da conta. + wrong_email_hint: Se o enderezo de correo non é correcto, podes cambialo nos axustes da conta. delete_account: Eliminar conta delete_account_html: Se queres eliminar a túa conta, podes <a href="%{path}">facelo aquí</a>. Deberás confirmar a acción. description: diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 0411c45bc..e46ccb873 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -255,7 +255,7 @@ gl: require_invite_text: Pedir unha razón para unirse show_domain_blocks: Amosar dominios bloqueados show_domain_blocks_rationale: Explicar porque están bloqueados os dominios - site_contact_email: Email de contacto + site_contact_email: Correo de contacto site_contact_username: Nome do contacto site_extended_description: Descrición ampla site_short_description: Descrición do servidor From a16c2c99b576b6815c9fefede9acc0be0d60d54d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:51:55 +0200 Subject: [PATCH 79/84] fix(deps): update dependency cssnano to v7.0.4 (#30927) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index d61ef4163..4c1a79220 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6742,16 +6742,16 @@ __metadata: languageName: node linkType: hard -"cssnano-preset-default@npm:^7.0.3": - version: 7.0.3 - resolution: "cssnano-preset-default@npm:7.0.3" +"cssnano-preset-default@npm:^7.0.4": + version: 7.0.4 + resolution: "cssnano-preset-default@npm:7.0.4" dependencies: browserslist: "npm:^4.23.1" css-declaration-sorter: "npm:^7.2.0" cssnano-utils: "npm:^5.0.0" postcss-calc: "npm:^10.0.0" postcss-colormin: "npm:^7.0.1" - postcss-convert-values: "npm:^7.0.1" + postcss-convert-values: "npm:^7.0.2" postcss-discard-comments: "npm:^7.0.1" postcss-discard-duplicates: "npm:^7.0.0" postcss-discard-empty: "npm:^7.0.0" @@ -6778,7 +6778,7 @@ __metadata: postcss-unique-selectors: "npm:^7.0.1" peerDependencies: postcss: ^8.4.31 - checksum: 10c0/ab3e51003efed6542a12d43c10ca693ab26138a1d035697b9be8f07e084e37a78617cbb8028b0a7e7841302ec151f4ecf35cbd763efe291846b62c35ea4c0bb4 + checksum: 10c0/0083821e778bdf7b8aa9589408a01a717be730f73584e7b81756a6fcf87af05b8f17342025e666572a8d573cc30783f2d817b0f7ad63670398bc3135b017ccad languageName: node linkType: hard @@ -6792,14 +6792,14 @@ __metadata: linkType: hard "cssnano@npm:^7.0.0": - version: 7.0.3 - resolution: "cssnano@npm:7.0.3" + version: 7.0.4 + resolution: "cssnano@npm:7.0.4" dependencies: - cssnano-preset-default: "npm:^7.0.3" + cssnano-preset-default: "npm:^7.0.4" lilconfig: "npm:^3.1.2" peerDependencies: postcss: ^8.4.31 - checksum: 10c0/4cbcd1e0ebe0bd83196cc5b16b3a60d3ebc98326c79b2f71df597bb73c8e3ee1f42b89159d7a038acc398251184d648d9dd516f4194e46746f3af6fa74b4aec7 + checksum: 10c0/3939a0b37b11cb4bae92f7916517c7ba21257551f92517b49a640d5df32e855fb7e73321f4be44d2c2de578309c05d711cdcb1976e95607b1b7f92bd4cbd1350 languageName: node linkType: hard @@ -13339,15 +13339,15 @@ __metadata: languageName: node linkType: hard -"postcss-convert-values@npm:^7.0.1": - version: 7.0.1 - resolution: "postcss-convert-values@npm:7.0.1" +"postcss-convert-values@npm:^7.0.2": + version: 7.0.2 + resolution: "postcss-convert-values@npm:7.0.2" dependencies: browserslist: "npm:^4.23.1" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.4.31 - checksum: 10c0/612f025f179f0f2ad7365db8c0b423614dcb8e1e4061875a4691a39dede0bca758d1a8f9f5c8b08e12af053e9e884f65ca5626ccc723d5b3f420650d67fe3046 + checksum: 10c0/beb59faf6aae97e6d3c233c5e6ed06cc60d65c49eec576036e3d0da1a831a1e827e3d41f5e81d016440b4f0bdf1406268ae069c4d5b38a6667b310c3da079d22 languageName: node linkType: hard From 05f0d510052fb56478986c474192abc7cea3e775 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:32:29 +0200 Subject: [PATCH 80/84] chore(deps): update dependency sidekiq-scheduler to v5.0.5 (#30918) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index eb6720e45..c0fa8a603 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -793,10 +793,10 @@ GEM redis (>= 4.5.0, < 5) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (5.0.3) + sidekiq-scheduler (5.0.5) rufus-scheduler (~> 3.2) sidekiq (>= 6, < 8) - tilt (>= 1.4.0) + tilt (>= 1.4.0, < 3) sidekiq-unique-jobs (7.1.33) brpoplpush-redis_script (> 0.1.1, <= 2.0.0) concurrent-ruby (~> 1.0, >= 1.0.5) From 016c1e4e788890f0c81a47640f76de136a0a8f32 Mon Sep 17 00:00:00 2001 From: David Roetzel <david@roetzel.de> Date: Fri, 5 Jul 2024 13:54:38 +0200 Subject: [PATCH 81/84] Improve handling of encoding problems when creating link previews (#30929) --- app/lib/link_details_extractor.rb | 22 +++++++++++---- app/services/fetch_link_card_service.rb | 2 +- .../requests/latin1_posing_as_utf8_broken.txt | 17 +++++++++++ .../latin1_posing_as_utf8_recoverable.txt | 17 +++++++++++ spec/services/fetch_link_card_service_spec.rb | 28 +++++++++++++++++-- 5 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 spec/fixtures/requests/latin1_posing_as_utf8_broken.txt create mode 100644 spec/fixtures/requests/latin1_posing_as_utf8_recoverable.txt diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb index dbfdd33fc..9436d20b5 100644 --- a/app/lib/link_details_extractor.rb +++ b/app/lib/link_details_extractor.rb @@ -156,11 +156,11 @@ class LinkDetailsExtractor end def title - html_entities.decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first).strip + html_entities_decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first).strip end def description - html_entities.decode(structured_data&.description || opengraph_tag('og:description') || meta_tag('description')) + html_entities_decode(structured_data&.description || opengraph_tag('og:description') || meta_tag('description')) end def published_at @@ -180,7 +180,7 @@ class LinkDetailsExtractor end def provider_name - html_entities.decode(structured_data&.publisher_name || opengraph_tag('og:site_name')) + html_entities_decode(structured_data&.publisher_name || opengraph_tag('og:site_name')) end def provider_url @@ -188,7 +188,7 @@ class LinkDetailsExtractor end def author_name - html_entities.decode(structured_data&.author_name || opengraph_tag('og:author') || opengraph_tag('og:author:username')) + html_entities_decode(structured_data&.author_name || opengraph_tag('og:author') || opengraph_tag('og:author:username')) end def author_url @@ -257,7 +257,7 @@ class LinkDetailsExtractor next if json_ld.blank? - structured_data = StructuredData.new(html_entities.decode(json_ld)) + structured_data = StructuredData.new(html_entities_decode(json_ld)) next unless structured_data.valid? @@ -273,10 +273,11 @@ class LinkDetailsExtractor end def detect_encoding_and_parse_document - [detect_encoding, nil, @html_charset, 'UTF-8'].uniq.each do |encoding| + [detect_encoding, nil, @html_charset].uniq.each do |encoding| document = Nokogiri::HTML(@html, nil, encoding) return document if document.to_s.valid_encoding? end + Nokogiri::HTML(@html, nil, 'UTF-8') end def detect_encoding @@ -290,6 +291,15 @@ class LinkDetailsExtractor end end + def html_entities_decode(string) + return if string.nil? + + unicode_string = string.encode('UTF-8') + raise EncodingError, 'cannot convert string to valid UTF-8' unless unicode_string.valid_encoding? + + html_entities.decode(unicode_string) + end + def html_entities @html_entities ||= HTMLEntities.new(:expanded) end diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 8bc9f912c..436e024c9 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -32,7 +32,7 @@ class FetchLinkCardService < BaseService end attach_card if @card&.persisted? - rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Encoding::UndefinedConversionError => e + rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, EncodingError => e Rails.logger.debug { "Error fetching link #{@original_url}: #{e}" } nil end diff --git a/spec/fixtures/requests/latin1_posing_as_utf8_broken.txt b/spec/fixtures/requests/latin1_posing_as_utf8_broken.txt new file mode 100644 index 000000000..ed8a4716a --- /dev/null +++ b/spec/fixtures/requests/latin1_posing_as_utf8_broken.txt @@ -0,0 +1,17 @@ +HTTP/1.1 200 OK +server: nginx +date: Thu, 13 Jun 2024 14:33:13 GMT +content-type: text/html; charset=utf-8 +content-length: 158 +accept-ranges: bytes + +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Tofu � l'orange</title> +</head> +<body> + <h2>Tofu � l'orange</h2> +</body> +</html> diff --git a/spec/fixtures/requests/latin1_posing_as_utf8_recoverable.txt b/spec/fixtures/requests/latin1_posing_as_utf8_recoverable.txt new file mode 100644 index 000000000..a24985832 --- /dev/null +++ b/spec/fixtures/requests/latin1_posing_as_utf8_recoverable.txt @@ -0,0 +1,17 @@ +HTTP/1.1 200 OK +server: nginx +date: Thu, 13 Jun 2024 14:33:13 GMT +content-type: text/html; charset=utf-8 +content-length: 158 +accept-ranges: bytes + +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Tofu with orange sauce</title> +</head> +<body> + <h2>Tofu � l'orange</h2> +</body> +</html> diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index d83a52751..547737b61 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -27,6 +27,8 @@ RSpec.describe FetchLinkCardService do stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt')) stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt')) stub_request(:get, 'http://example.com/low_confidence_latin1').to_return(request_fixture('low_confidence_latin1.txt')) + stub_request(:get, 'http://example.com/latin1_posing_as_utf8_broken').to_return(request_fixture('latin1_posing_as_utf8_broken.txt')) + stub_request(:get, 'http://example.com/latin1_posing_as_utf8_recoverable').to_return(request_fixture('latin1_posing_as_utf8_recoverable.txt')) stub_request(:get, 'http://example.com/aergerliche-umlaute').to_return(request_fixture('redirect_with_utf8_url.txt')) Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache @@ -159,10 +161,30 @@ RSpec.describe FetchLinkCardService do end context 'with a URL of a page in ISO-8859-1 encoding, that charlock_holmes cannot detect' do - let(:status) { Fabricate(:status, text: 'Check out http://example.com/low_confidence_latin1') } + context 'when encoding in http header is correct' do + let(:status) { Fabricate(:status, text: 'Check out http://example.com/low_confidence_latin1') } - it 'decodes the HTML' do - expect(status.preview_card.title).to eq("Tofu á l'orange") + it 'decodes the HTML' do + expect(status.preview_card.title).to eq("Tofu á l'orange") + end + end + + context 'when encoding in http header is incorrect' do + context 'when encoding problems appear in unrelated tags' do + let(:status) { Fabricate(:status, text: 'Check out http://example.com/latin1_posing_as_utf8_recoverable') } + + it 'decodes the HTML' do + expect(status.preview_card.title).to eq('Tofu with orange sauce') + end + end + + context 'when encoding problems appear in title tag' do + let(:status) { Fabricate(:status, text: 'Check out http://example.com/latin1_posing_as_utf8_broken') } + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end end end From 97eddb5906640a79a1cba20823ce10e986f7f317 Mon Sep 17 00:00:00 2001 From: David Roetzel <david@roetzel.de> Date: Fri, 5 Jul 2024 15:28:52 +0200 Subject: [PATCH 82/84] Fix details extraction when no title exists. (#30933) --- app/lib/link_details_extractor.rb | 2 +- spec/fixtures/requests/page_without_title.txt | 17 +++++++++++++++++ spec/services/fetch_link_card_service_spec.rb | 9 +++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/requests/page_without_title.txt diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb index 9436d20b5..a62ede2bb 100644 --- a/app/lib/link_details_extractor.rb +++ b/app/lib/link_details_extractor.rb @@ -156,7 +156,7 @@ class LinkDetailsExtractor end def title - html_entities_decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first).strip + html_entities_decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first)&.strip end def description diff --git a/spec/fixtures/requests/page_without_title.txt b/spec/fixtures/requests/page_without_title.txt new file mode 100644 index 000000000..0054aa3b7 --- /dev/null +++ b/spec/fixtures/requests/page_without_title.txt @@ -0,0 +1,17 @@ +HTTP/1.1 200 OK +server: nginx +date: Thu, 13 Jun 2024 14:33:13 GMT +content-type: text/html; charset=utf-8 +content-length: 171 +accept-ranges: bytes + +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"> +</head> +<body> + <h2>I am not a valid page</h2> + <p>Thankfully, browsers do not care</p> +</body> +</html> diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index 547737b61..b2cd99cea 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -30,6 +30,7 @@ RSpec.describe FetchLinkCardService do stub_request(:get, 'http://example.com/latin1_posing_as_utf8_broken').to_return(request_fixture('latin1_posing_as_utf8_broken.txt')) stub_request(:get, 'http://example.com/latin1_posing_as_utf8_recoverable').to_return(request_fixture('latin1_posing_as_utf8_recoverable.txt')) stub_request(:get, 'http://example.com/aergerliche-umlaute').to_return(request_fixture('redirect_with_utf8_url.txt')) + stub_request(:get, 'http://example.com/page_without_title').to_return(request_fixture('page_without_title.txt')) Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache @@ -112,6 +113,14 @@ RSpec.describe FetchLinkCardService do end end + context 'with a page that has no title' do + let(:status) { Fabricate(:status, text: 'http://example.com/page_without_title') } + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end + context 'with a 404 URL' do let(:status) { Fabricate(:status, text: 'http://example.com/not-found') } From 8f5694d79e531b94784697d92bed24d003d77353 Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Fri, 5 Jul 2024 15:40:53 +0200 Subject: [PATCH 83/84] Fix right-to-left text in preview cards (#30930) --- app/javascript/mastodon/features/status/components/card.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index f0ae40cbc..ee1fbe0f8 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -141,7 +141,7 @@ export default class Card extends PureComponent { const showAuthor = !!card.getIn(['authors', 0, 'accountId']); const description = ( - <div className='status-card__content'> + <div className='status-card__content' dir='auto'> <span className='status-card__host'> <span lang={language}>{provider}</span> {card.get('published_at') && <> · <RelativeTimestamp timestamp={card.get('published_at')} /></>} From 63ba69810eca80fc2d10114a79f2988c1b75892f Mon Sep 17 00:00:00 2001 From: Claire <claire.github-309c@sitedethib.com> Date: Sat, 6 Jul 2024 09:22:24 +0200 Subject: [PATCH 84/84] Fix overflow behavior on profile fields in hover cards (#30928) --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 12eac79b9..c114885d1 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -10453,7 +10453,7 @@ noscript { gap: 4px; dt { - flex: 0 0 auto; + flex: 0 1 auto; color: $dark-text-color; min-width: 0; overflow: hidden;