diff --git a/.env.production.sample b/.env.production.sample index 4afaf8d756..a311ad5f8d 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -110,3 +110,6 @@ FETCH_REPLIES_MAX_SINGLE=500 # Max number of replies Collection pages to fetch - total FETCH_REPLIES_MAX_PAGES=500 + +# Maximum allowed character count +MAX_CHARS=5555 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8232ec8ec3..13fb25d333 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.75.1. +# using RuboCop version 1.75.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -62,12 +62,6 @@ Style/FormatStringToken: Style/GuardClause: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/HashTransformValues: - Exclude: - - 'app/serializers/rest/web_push_subscription_serializer.rb' - - 'app/services/import_service.rb' - # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: diff --git a/.ruby-version b/.ruby-version index 4d9d11cf50..6cb9d3dd0d 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.4.2 +3.4.3 diff --git a/CHANGELOG.md b/CHANGELOG.md index a9819a6c79..4dd4783597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,34 +2,9 @@ All notable changes to this project will be documented in this file. -## [4.3.8] - 2025-05-06 - -### Security - -- Update dependencies -- Check scheme on account, profile, and media URLs ([GHSA-x2rc-v5wx-g3m5](https://github.com/mastodon/mastodon/security/advisories/GHSA-x2rc-v5wx-g3m5)) - -### Added - -- Add warning for REDIS_NAMESPACE deprecation at startup (#34581 by @ClearlyClaire) -- Add built-in context for interaction policies (#34574 by @ClearlyClaire) - -### Changed - -- Change activity distribution error handling to skip retrying for deleted accounts (#33617 by @ClearlyClaire) - -### Removed - -- Remove double-query for signed query strings (#34610 by @ClearlyClaire) - -### Fixed - -- Fix incorrect redirect in response to unauthenticated API requests in limited federation mode (#34549 by @ClearlyClaire) -- Fix sign-up e-mail confirmation page reloading on error or redirect (#34548 by @ClearlyClaire) - ## [4.3.7] - 2025-04-02 -### Added +### Add - Add delay to profile updates to debounce them (#34137 by @ClearlyClaire) - Add support for paginating partial collections in `SynchronizeFollowersService` (#34272 and #34277 by @ClearlyClaire) diff --git a/Gemfile.lock b/Gemfile.lock index 0fb210eaa4..f13df0c43f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -94,7 +94,7 @@ GEM ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.3.2) - aws-partitions (1.1066.0) + aws-partitions (1.1087.0) aws-sdk-core (3.215.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -120,13 +120,13 @@ GEM rack (>= 0.9.0) rouge (>= 1.0.0) bigdecimal (3.1.9) - bindata (2.5.0) + bindata (2.5.1) binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) blurhash (0.1.8) bootsnap (1.18.4) msgpack (~> 1.2) - brakeman (7.0.0) + brakeman (7.0.2) racc browser (6.2.0) brpoplpush-redis_script (0.1.3) @@ -170,7 +170,7 @@ GEM crass (1.0.6) css_parser (1.21.1) addressable - csv (3.3.3) + csv (3.3.4) database_cleaner-active_record (2.2.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) @@ -194,14 +194,14 @@ GEM devise_pam_authenticatable2 (9.2.0) devise (>= 4.0.0) rpam2 (~> 4.0) - diff-lcs (1.6.0) + diff-lcs (1.6.1) discard (1.4.0) activerecord (>= 4.2, < 9.0) docile (1.4.1) domain_name (0.6.20240107) - doorkeeper (5.8.1) + doorkeeper (5.8.2) railties (>= 5) - dotenv (3.1.7) + dotenv (3.1.8) drb (2.2.1) elasticsearch (7.17.11) elasticsearch-api (= 7.17.11) @@ -227,7 +227,7 @@ GEM fabrication (2.31.0) faker (3.5.1) i18n (>= 1.8.11, < 2) - faraday (2.12.2) + faraday (2.13.0) faraday-net_http (>= 2.0, < 3.5) json logger @@ -239,7 +239,7 @@ GEM net-http (>= 0.5.0) fast_blank (1.0.1) fastimage (2.4.0) - ffi (1.17.1) + ffi (1.17.2) ffi-compiler (1.3.2) ffi (>= 1.15.5) rake @@ -266,10 +266,10 @@ GEM raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) - google-protobuf (4.30.1) + google-protobuf (4.30.2) bigdecimal rake (>= 13) - googleapis-common-protos-types (1.18.0) + googleapis-common-protos-types (1.19.0) google-protobuf (>= 3.18, < 5.a) haml (6.3.0) temple (>= 0.8.2) @@ -280,7 +280,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.61.1) + haml_lint (0.62.0) haml (>= 5.0) parallel (~> 1.10) rainbow @@ -328,7 +328,7 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.8.0) - irb (1.15.1) + irb (1.15.2) pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) @@ -395,7 +395,7 @@ GEM rexml link_header (0.0.8) lint_roller (1.1.0) - linzer (0.6.3) + linzer (0.6.5) openssl (~> 3.0, >= 3.0.0) rack (>= 2.2, < 4.0) starry (~> 0.2) @@ -426,7 +426,7 @@ GEM mime-types (3.6.2) logger mime-types-data (~> 3.2015) - mime-types-data (3.2025.0318) + mime-types-data (3.2025.0408) mini_mime (1.1.5) mini_portile2 (2.8.8) minitest (5.25.5) @@ -435,7 +435,7 @@ GEM mutex_m (0.3.0) net-http (0.6.0) uri - net-imap (0.5.7) + net-imap (0.5.6) date net-protocol net-ldap (0.19.0) @@ -446,7 +446,7 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.4) - nokogiri (1.18.8) + nokogiri (1.18.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) oj (3.16.10) @@ -585,8 +585,8 @@ GEM ostruct (0.6.1) ox (2.14.22) bigdecimal (>= 3.0) - parallel (1.26.3) - parser (3.3.7.4) + parallel (1.27.0) + parser (3.3.8.0) ast (~> 2.4.1) racc parslet (2.0.0) @@ -688,7 +688,7 @@ GEM link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.7.0) rdf (~> 3.3) - rdoc (6.12.0) + rdoc (6.13.1) psych (>= 4.0.0) redcarpet (3.6.1) redis (4.8.1) @@ -697,7 +697,7 @@ GEM redlock (1.3.2) redis (>= 3.0.0, < 6.0) regexp_parser (2.10.0) - reline (0.6.0) + reline (0.6.1) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) @@ -740,7 +740,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.2) - rubocop (1.75.1) + rubocop (1.75.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -748,10 +748,10 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.43.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.43.0) + rubocop-ast (1.44.1) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-capybara (2.22.1) @@ -797,7 +797,7 @@ GEM activerecord (>= 4.0.0) railties (>= 4.0.0) securerandom (0.4.1) - selenium-webdriver (4.30.1) + selenium-webdriver (4.31.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -840,7 +840,7 @@ GEM stoplight (4.1.1) redlock (~> 1.0) stringio (3.1.6) - strong_migrations (2.2.1) + strong_migrations (2.3.0) activerecord (>= 7) swd (2.0.3) activesupport (>= 3) @@ -851,7 +851,7 @@ GEM temple (0.10.3) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) - terrapin (1.0.1) + terrapin (1.1.0) climate_control test-prof (1.4.4) thor (1.3.2) @@ -1085,4 +1085,4 @@ RUBY VERSION ruby 3.4.1p0 BUNDLED WITH - 2.6.6 + 2.6.8 diff --git a/README.md b/README.md index 200d58d8c4..854e8ac3d9 100644 --- a/README.md +++ b/README.md @@ -1,123 +1,27 @@ -# ![kmyblue icon](https://raw.githubusercontent.com/kmycode/mastodon/kb_development/app/javascript/icons/favicon-32x32.png) kmyblue +NAS is an KMY & Mastodon Fork -[![Ruby Testing](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml) +The following are just a few of the most common features. There are many other minor changes to the specifications. -! FOR ENGLISH USER ! We do not provide English documentation for kmyblue; we assume that you will use automatic translation software, such as Google, to translate the site. +Emoji reactions -kmyblueは、ActivityPubに接続するSNSの1つである[Mastodon](https://github.com/mastodon/mastodon)のフォークです。創作作家のためのMastodonを目指して開発しました。 +Local Public (Does not appear on the federated timeline of remote servers, but does appear on followers' home timelines. This is different from local only) -kmyblueはフォーク名であり、同時に[サーバー名](https://kmy.blue)でもあります。以下は特に記述がない限り、フォークとしてのkmyblueをさします。 +Bookmark classification -kmyblueは AGPL ライセンスで公開されているため、どなたでも自由にフォークし、このソースコードを元に自分でサーバーを立てて公開することができます。確かにサーバーkmyblueは創作作家向けの利用規約が設定されていますが、フォークとしてのkmyblueのルールは全くの別物です。いかなるコミュニティにも平等にお使いいただけます。 -kmyblueは、閉鎖的なコミュニティ、あまり目立ちたくないコミュニティには特に強力な機能を提供します。kmyblueはプライバシーを考慮したうえで強力な独自機能を提供するため、汎用サーバーとして利用するにもある程度十分な機能が揃っています。 +Set who can search your posts for each post (Searchability) -テストコード、Lint どちらも動いています。 +Quote posts, modest quotes (references) -### アジェンダ +Record posts that meet certain conditions such as domains, accounts, and keywords (Subscriptions/Antennas) -- 利用方法 -- kmyblueの開発方針 -- kmyblueは何でないか -- kmyblueの独自機能 -- 英語のサポートについて +Send posts to a designated set of followers (Circles) (different from direct messages) -## 利用方法 +Notification of new posts on lists -### インストール方法 +Exclude posts from people you follow when filtering posts -[Wiki](https://github.com/kmycode/mastodon/wiki/Installation)を参照してください。 +Hide number of followers and followings -### 開発への参加方法 +Automatically delete posts after a specified time has passed -CONTRIBUTING.mdを参照してください。 - -### テスト - -``` -# デバッグ実行(以下のいずれか) -foreman start -DB_USER=postgres DB_PASS=password foreman start - -# 一部を除く全てのテストを行う -RAILS_ENV=test bundle exec rspec spec - -# ElasticSearch連携テストを行う -新 -RAILS_ENV=test ES_ENABLED=true bundle exec rspec --tag search -旧 -RAILS_ENV=test ES_ENABLED=true RUN_SEARCH_SPECS=true bundle exec rspec spec/search -``` - -## kmyblueの開発方針 - -### 本家Mastodonへの積極的追従 - -kmyblueは、追加機能を控えめにする代わりに本家Mastodonに積極的に追従を行います。kmyblueの追加機能そのままに、Mastodonの新機能も利用できるよう調整を行います。 - -### ゆるやかな内輪での運用 - -kmyblueは同人向けサーバーとして出発したため、同人作家に需要のある「内輪ノリを外部にできるだけもらさない」という部分に特化しています。 - -「ローカル公開」は、投稿を見せたくない人に見つかりにくくする効果があります。「サークル」は、フォロワーの中でも特に見せたい人だけに見せる効果があります。 -「検索許可」という独自の検索オプションを利用することで、公開投稿の一部だけを検索されにくくするだけでなく、非収載投稿が誰でも自由に検索できるようになります。 - -内輪とは自分のサーバーに限ったものではありません。内輪同士で複数のサーバーを運営するとき、お互いが深く繋がれる「フレンドサーバー」というシステムも用意しています。 - -### 少人数サーバーでの運用 - -kmyblueは、人の少ないサーバーでの運用を考慮して設計しています。そのため、Fedibirdにあるような、人の多いサーバー向けの機能はあまり作っていません。 - -サーバーの負荷については一部度外視している部分があります。たとえば絵文字リアクション機能はサーバーへ著しい負荷をかける場合があります。ただしkmyblueでは、絵文字リアクション機能そのものを無効にしたり、負荷の高いストリーミング処理を無効にする管理者オプションも存在します。 - -もちろん人の多いサーバーでの運用が不便になるような修正は行っていません。人数にかかわらず、そのままお使いいただけます。 - -### 比較的高い防御力 - -kmyblueでは、「Fediverseは将来的に荒むのではないか」「Fediverseは将来的にスパムに溢れるのではないか」を念頭に設計している部分があります。投稿だけでなく絵文字リアクションも対象にした防衛策があります。 - -管理者は「NGワード」「NGルール」機能の利用が可能です。設定を変更することで、一部のモデレーターもこの機能を利用できます。 -利用者は、独自拡張されたフィルター機能、絵文字リアクションのブロックなどを利用できます。 - -ただし防御力の高さは自由を犠牲にします。例えばNGワードが多すぎると、他のサーバーからの投稿が制限され、かつそれに気づきにくくなります。 - -## kmyblueは何でないか - -kmyblueは、企業・政府機関向けに開発されたものではありません。開発者はセキュリティに関する専門知識を有しておらず、高度なセキュリティを求められる機関向けのソフトウェアを制作する能力はありません。また、kmyblueのメンテナは現在1人のみであり、そのメンテナが飽きたら開発がストップするリスクも高いです。Mastodonのような高い信頼性・安全性を保証することはできないので、導入の際はご自身で安全を十分に確認してからお使いになることを強くおすすめします。 -個人サーバーであっても、安定性を強く求める方にはおすすめできません。glitch-socがよりよい選択肢になるでしょう。 - -kmyblueは、Misskeyではありません。Misskeyは「楽しむ」をコンセプトにしていますが、kmyblueはMastodonの思想を受け継ぎ、炎上や喧騒を避けることのできる落ち着いた場所を目指しています。そのため、思想に合わない機能は実装しないか、大幅に弱体化しています。 - -kmyblueは、Fedibirdではありません。Fedibirdは大規模サーバー向けに設定していると思われる機能があり、例えば購読機能がその代表例です。Fedibirdの購読は擬似的なフォロー体験を与えるものですが、本物のフォローではないため、購読対象の投稿が配送されることを確約したものではありません。小規模サーバーだとかえって不便になる機能を、kmyblueは避けています。 - -## kmyblueの独自機能 - -以下に列挙したものはあくまで代表的なものです。これ以外にも、細かい仕様変更などが多数含まれます。 - -- 絵文字リアクション -- ローカル公開(Local Public)(リモートサーバーの連合タイムラインには流れませんが、フォロワーのホームタイムラインには流れます。**ローカル限定とは異なります**) -- ブックマークの分類 -- 自分の投稿を検索できる人を投稿ごとに設定(検索許可・Searchability) -- 投稿の引用、ひかえめな引用(参照) -- ドメイン・アカウント・キーワードなど特定条件を満たした投稿を記録する機能(購読・アンテナ) -- フォロワーの一部を指名して投稿を送る機能(サークル)(ダイレクトメッセージとは異なります) -- リスト新着投稿の通知 -- 投稿のフィルタリングにおいて、自分がフォローしている相手の投稿を除外 -- フォロー・フォロワー数を隠す機能 -- 指定した時間が経過したあとに投稿を自動削除する機能 -- モデレーション機能の拡張 - -## 英語のサポートについて - -kmyblueのメイン開発者である[雪あすか](https://kmy.blue/@askyq)は、英語の読み書きがほとんどできません。そのため、ドキュメントの英語化、海外向け公式アカウントの新設などを行う予定はありません。 - -要望やバグ報告はIssueに書いて構いませんが、Issue画面内の説明やテンプレートはすべて日本語になっています。投稿が難しければ、Discussionに投稿してください。こちらで必要と判断したものは、改めてIssueとして起票します。 - -そのほか開発者へ質問があれば、[@askyq@kmy.blue](https://kmy.blue/@askyq)へ英語のまま送ってください。 - -ただしkmyblueのドキュメント、[@askyq@kmy.blue](https://kmy.blue/@askyq)内のkmyblueフォークに関係する投稿を、許可なく翻訳して公開することは問題ありません。 - -## 開発者のアカウントについて - -kmyblueのメイン開発者である[雪あすか](https://kmy.blue/@askyq)は、用途別にアカウントを分けるようなことはせず、すべての発言を1つのアカウントで行っています。そのため、kmyblueの開発だけでなく、成人向け同人作品の話も混ざっています。 - -このうち、公開範囲「公開」「ローカル公開」「非収載」であるkmyblueフォークの開発に関係する投稿に限り抽出し、翻訳の有無に関係なく公開することを許可します。これはkmyblueフォークの利用者にとって公共性の高いコンテンツであると思われます。これは、日本と欧米では一般的に考えられている児童ポルノの基準が異なり、欧米のサーバーの中にはこのアカウントをフォローしづらいものもあるという懸念を考慮したものです。 +Expanding moderation functions diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index b73dae17e5..06a113511c 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -72,13 +72,6 @@ class Api::BaseController < ApplicationController end end - # Redefine `require_functional!` to properly output JSON instead of HTML redirects - def require_functional! - return if current_user.functional? - - require_user! - end - def render_empty render json: {}, status: 200 end diff --git a/app/controllers/api/v1/accounts/endorsements_controller.rb b/app/controllers/api/v1/accounts/endorsements_controller.rb new file mode 100644 index 0000000000..1e21994a90 --- /dev/null +++ b/app/controllers/api/v1/accounts/endorsements_controller.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::EndorsementsController < Api::BaseController + include Authorization + + before_action -> { authorize_if_got_token! :read, :'read:accounts' }, only: :index + before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index + before_action :require_user!, except: :index + before_action :set_account + before_action :set_endorsed_accounts, only: :index + after_action :insert_pagination_headers, only: :index + + def index + cache_if_unauthenticated! + render json: @endorsed_accounts, each_serializer: REST::AccountSerializer + end + + def create + AccountPin.find_or_create_by!(account: current_account, target_account: @account) + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter + end + + def destroy + pin = AccountPin.find_by(account: current_account, target_account: @account) + pin&.destroy! + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def set_endorsed_accounts + @endorsed_accounts = @account.unavailable? ? [] : paginated_endorsed_accounts + end + + def paginated_endorsed_accounts + @account.endorsed_accounts.without_suspended.includes(:account_stat, :user).paginate_by_max_id( + limit_param(DEFAULT_ACCOUNTS_LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def relationships_presenter + AccountRelationshipsPresenter.new([@account], current_user.account_id) + end + + def next_path + api_v1_account_endorsements_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_account_endorsements_url pagination_params(since_id: pagination_since_id) unless @endorsed_accounts.empty? + end + + def pagination_collection + @endorsed_accounts + end + + def records_continue? + @endorsed_accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) + end +end diff --git a/app/controllers/api/v1/accounts/featured_tags_controller.rb b/app/controllers/api/v1/accounts/featured_tags_controller.rb index 0101fb469b..f95846366c 100644 --- a/app/controllers/api/v1/accounts/featured_tags_controller.rb +++ b/app/controllers/api/v1/accounts/featured_tags_controller.rb @@ -17,6 +17,6 @@ class Api::V1::Accounts::FeaturedTagsController < Api::BaseController end def set_featured_tags - @featured_tags = @account.suspended? ? [] : @account.featured_tags + @featured_tags = @account.unavailable? ? [] : @account.featured_tags end end diff --git a/app/controllers/api/v1/accounts/pins_controller.rb b/app/controllers/api/v1/accounts/pins_controller.rb deleted file mode 100644 index 0eb13c048c..0000000000 --- a/app/controllers/api/v1/accounts/pins_controller.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Accounts::PinsController < Api::BaseController - include Authorization - - before_action -> { doorkeeper_authorize! :write, :'write:accounts' } - before_action :require_user! - before_action :set_account - - def create - AccountPin.find_or_create_by!(account: current_account, target_account: @account) - render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter - end - - def destroy - pin = AccountPin.find_by(account: current_account, target_account: @account) - pin&.destroy! - render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter - end - - private - - def set_account - @account = Account.find(params[:account_id]) - end - - def relationships_presenter - AccountRelationshipsPresenter.new([@account], current_user.account_id) - end -end diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb index 2086bf116d..b019ab6018 100644 --- a/app/controllers/api/v1/lists_controller.rb +++ b/app/controllers/api/v1/lists_controller.rb @@ -7,10 +7,6 @@ class Api::V1::ListsController < Api::BaseController before_action :require_user! before_action :set_list, except: [:index, :create] - rescue_from ArgumentError do |e| - render json: { error: e.to_s }, status: 422 - end - def index @lists = List.where(account: current_account).all render json: @lists, each_serializer: REST::ListSerializer diff --git a/app/controllers/api/v1/suggestions_controller.rb b/app/controllers/api/v1/suggestions_controller.rb index 918ec45beb..df9346832f 100644 --- a/app/controllers/api/v1/suggestions_controller.rb +++ b/app/controllers/api/v1/suggestions_controller.rb @@ -4,7 +4,7 @@ class Api::V1::SuggestionsController < Api::BaseController include Authorization include DeprecationConcern - deprecate_api '2021-05-16' + deprecate_api '2021-05-16', only: [:index] before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c11fd2a635..1b071e8655 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -72,24 +72,10 @@ class ApplicationController < ActionController::Base def require_functional! return if current_user.functional? - respond_to do |format| - format.any do - if current_user.confirmed? - redirect_to edit_user_registration_path - else - redirect_to auth_setup_path - end - end - - format.json do - if !current_user.confirmed? - render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403 - elsif !current_user.approved? - render json: { error: 'Your login is currently pending approval' }, status: 403 - elsif !current_user.functional? - render json: { error: 'Your login is currently disabled' }, status: 403 - end - end + if current_user.confirmed? + redirect_to edit_user_registration_path + else + redirect_to auth_setup_path end end diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 79080895a4..077c5272a5 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -35,13 +35,6 @@ module ContextHelper suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } }, misskey_license: { 'misskey' => 'https://misskey-hub.net/ns#', '_misskey_license' => 'misskey:_misskey_license' }, - interaction_policies: { - 'gts' => 'https://gotosocial.org/ns#', - 'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' }, - 'canQuote' => { '@id' => 'gts:canQuote', '@type' => '@id' }, - 'automaticApproval' => { '@id' => 'gts:automaticApproval', '@type' => '@id' }, - 'manualApproval' => { '@id' => 'gts:manualApproval', '@type' => '@id' }, - }, }.freeze def full_context diff --git a/app/javascript/entrypoints/sign_up.ts b/app/javascript/entrypoints/sign_up.ts index 87100be56d..880738fcb7 100644 --- a/app/javascript/entrypoints/sign_up.ts +++ b/app/javascript/entrypoints/sign_up.ts @@ -4,12 +4,9 @@ import axios from 'axios'; import ready from '../mastodon/ready'; async function checkConfirmation() { - const response = await axios.get('/api/v1/emails/check_confirmation', { - headers: { Accept: 'application/json' }, - withCredentials: true, - }); + const response = await axios.get('/api/v1/emails/check_confirmation'); - if (response.status === 200 && response.data === true) { + if (response.data) { window.location.href = '/start'; } } diff --git a/app/javascript/mastodon/actions/dropdown_menu.ts b/app/javascript/mastodon/actions/dropdown_menu.ts index 3694df1ae0..d9d395ba33 100644 --- a/app/javascript/mastodon/actions/dropdown_menu.ts +++ b/app/javascript/mastodon/actions/dropdown_menu.ts @@ -1,11 +1,11 @@ import { createAction } from '@reduxjs/toolkit'; export const openDropdownMenu = createAction<{ - id: string; + id: number; keyboard: boolean; - scrollKey: string; + scrollKey?: string; }>('dropdownMenu/open'); -export const closeDropdownMenu = createAction<{ id: string }>( +export const closeDropdownMenu = createAction<{ id: number }>( 'dropdownMenu/close', ); diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index ff2e10c79d..b643cf5613 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -94,17 +94,6 @@ export function normalizeStatus(status, normalOldStatus) { normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap); normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive; - - if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) { - normalStatus.url = null; - } - - normalStatus.url ||= normalStatus.uri; - - normalStatus.media_attachments.forEach(item => { - if (item.remote_url && !(item.remote_url.startsWith('http://') || item.remote_url.startsWith('https://'))) - item.remote_url = null; - }); } if (normalOldStatus) { diff --git a/app/javascript/mastodon/actions/tags.js b/app/javascript/mastodon/actions/tags.js deleted file mode 100644 index 6e0c95288a..0000000000 --- a/app/javascript/mastodon/actions/tags.js +++ /dev/null @@ -1,81 +0,0 @@ -import api, { getLinks } from '../api'; - -export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST'; -export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS'; -export const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL'; - -export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST'; -export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS'; -export const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL'; - -export const fetchFollowedHashtags = () => (dispatch) => { - dispatch(fetchFollowedHashtagsRequest()); - - api().get('/api/v1/followed_tags').then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null)); - }).catch(err => { - dispatch(fetchFollowedHashtagsFail(err)); - }); -}; - -export function fetchFollowedHashtagsRequest() { - return { - type: FOLLOWED_HASHTAGS_FETCH_REQUEST, - }; -} - -export function fetchFollowedHashtagsSuccess(followed_tags, next) { - return { - type: FOLLOWED_HASHTAGS_FETCH_SUCCESS, - followed_tags, - next, - }; -} - -export function fetchFollowedHashtagsFail(error) { - return { - type: FOLLOWED_HASHTAGS_FETCH_FAIL, - error, - }; -} - -export function expandFollowedHashtags() { - return (dispatch, getState) => { - const url = getState().getIn(['followed_tags', 'next']); - - if (url === null) { - return; - } - - dispatch(expandFollowedHashtagsRequest()); - - api().get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null)); - }).catch(error => { - dispatch(expandFollowedHashtagsFail(error)); - }); - }; -} - -export function expandFollowedHashtagsRequest() { - return { - type: FOLLOWED_HASHTAGS_EXPAND_REQUEST, - }; -} - -export function expandFollowedHashtagsSuccess(followed_tags, next) { - return { - type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS, - followed_tags, - next, - }; -} - -export function expandFollowedHashtagsFail(error) { - return { - type: FOLLOWED_HASHTAGS_EXPAND_FAIL, - error, - }; -} diff --git a/app/javascript/mastodon/api/tags.ts b/app/javascript/mastodon/api/tags.ts index 2cb802800c..4b111def81 100644 --- a/app/javascript/mastodon/api/tags.ts +++ b/app/javascript/mastodon/api/tags.ts @@ -1,4 +1,4 @@ -import { apiRequestPost, apiRequestGet } from 'mastodon/api'; +import api, { getLinks, apiRequestPost, apiRequestGet } from 'mastodon/api'; import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; export const apiGetTag = (tagId: string) => @@ -9,3 +9,15 @@ export const apiFollowTag = (tagId: string) => export const apiUnfollowTag = (tagId: string) => apiRequestPost(`v1/tags/${tagId}/unfollow`); + +export const apiGetFollowedTags = async (url?: string) => { + const response = await api().request({ + method: 'GET', + url: url ?? '/api/v1/followed_tags', + }); + + return { + tags: response.data, + links: getLinks(response), + }; +}; diff --git a/app/javascript/mastodon/components/account.tsx b/app/javascript/mastodon/components/account.tsx index f43b17aef0..c6c2204085 100644 --- a/app/javascript/mastodon/components/account.tsx +++ b/app/javascript/mastodon/components/account.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react'; import type React from 'react'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; @@ -14,18 +14,19 @@ import { muteAccount, unmuteAccount, } from 'mastodon/actions/accounts'; +import { openModal } from 'mastodon/actions/modal'; import { initMuteModal } from 'mastodon/actions/mutes'; import { Avatar } from 'mastodon/components/avatar'; import { Button } from 'mastodon/components/button'; import { FollowersCounter } from 'mastodon/components/counters'; import { DisplayName } from 'mastodon/components/display_name'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { FollowButton } from 'mastodon/components/follow_button'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { ShortNumber } from 'mastodon/components/short_number'; import { Skeleton } from 'mastodon/components/skeleton'; import { VerifiedBadge } from 'mastodon/components/verified_badge'; -import DropdownMenu from 'mastodon/containers/dropdown_menu_container'; -import { me } from 'mastodon/initial_state'; +import type { MenuItem } from 'mastodon/models/dropdown_menu'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; const messages = defineMessages({ @@ -48,6 +49,14 @@ const messages = defineMessages({ mute: { id: 'account.mute_short', defaultMessage: 'Mute' }, block: { id: 'account.block_short', defaultMessage: 'Block' }, more: { id: 'status.more', defaultMessage: 'More' }, + addToLists: { + id: 'account.add_or_remove_from_list', + defaultMessage: 'Add or Remove from lists', + }, + openOriginalPage: { + id: 'account.open_original_page', + defaultMessage: 'Open original page', + }, }); export const Account: React.FC<{ @@ -73,6 +82,7 @@ export const Account: React.FC<{ const account = useAppSelector((state) => state.accounts.get(id)); const relationship = useAppSelector((state) => state.relationships.get(id)); const dispatch = useAppDispatch(); + const accountUrl = account?.url; const handleBlock = useCallback(() => { if (relationship?.blocking) { @@ -90,13 +100,62 @@ export const Account: React.FC<{ } }, [dispatch, id, account, relationship]); - const handleMuteNotifications = useCallback(() => { - dispatch(muteAccount(id, true)); - }, [dispatch, id]); + const menu = useMemo(() => { + let arr: MenuItem[] = []; - const handleUnmuteNotifications = useCallback(() => { - dispatch(muteAccount(id, false)); - }, [dispatch, id]); + if (defaultAction === 'mute') { + const handleMuteNotifications = () => { + dispatch(muteAccount(id, true)); + }; + + const handleUnmuteNotifications = () => { + dispatch(muteAccount(id, false)); + }; + + arr = [ + { + text: intl.formatMessage( + relationship?.muting_notifications + ? messages.unmute_notifications + : messages.mute_notifications, + ), + action: relationship?.muting_notifications + ? handleUnmuteNotifications + : handleMuteNotifications, + }, + ]; + } else if (defaultAction !== 'block') { + const handleAddToLists = () => { + dispatch( + openModal({ + modalType: 'LIST_ADDER', + modalProps: { + accountId: id, + }, + }), + ); + }; + + arr = [ + { + text: intl.formatMessage(messages.addToLists), + action: handleAddToLists, + }, + ]; + + if (accountUrl) { + arr.unshift( + { + text: intl.formatMessage(messages.openOriginalPage), + href: accountUrl, + }, + null, + ); + } + } + + return arr; + }, [dispatch, intl, id, accountUrl, relationship, defaultAction]); if (hidden) { return ( @@ -107,73 +166,46 @@ export const Account: React.FC<{ ); } - let buttons; + let button: React.ReactNode, dropdown: React.ReactNode; - if (account && account.id !== me && relationship) { - const { requested, blocking, muting } = relationship; + if (menu.length > 0) { + dropdown = ( + + ); + } - if (requested) { - buttons = ; - } else if (blocking) { - buttons = ( - + ); + } else if (isExternalLinkItem(option)) { + element = ( + + {text} + + ); + } else { + element = ( + + {text} + + ); + } + + return ( +
  • + {element} +
  • + ); + }; + + const renderItemMethod = renderItem ?? nativeRenderItem; + + return ( +
    + {(loading || !items) && } + + {!loading && renderHeader && items && ( +
    + {renderHeader(items)} +
    + )} + + {!loading && items && ( +
      + {items.map((option, i) => + renderItemMethod(option, i, { + onClick: handleItemClick, + onKeyUp: handleItemKeyUp, + }), + )} +
    + )} +
    + ); +}; + +interface DropdownProps { + children?: React.ReactElement; + icon?: string; + iconComponent?: IconProp; + items?: Item[]; + loading?: boolean; + title?: string; + disabled?: boolean; + scrollable?: boolean; + active?: boolean; + scrollKey?: string; + status?: ImmutableMap; + renderItem?: RenderItemFn; + renderHeader?: RenderHeaderFn; + onOpen?: () => void; + onItemClick?: ItemClickFn; +} + +const offset = [5, 5] as OffsetValue; +const popperConfig = { strategy: 'fixed' } as UsePopperOptions; + +export const Dropdown = ({ + children, + icon, + iconComponent, + items, + loading, + title = 'Menu', + disabled, + scrollable, + active, + status, + renderItem, + renderHeader, + onOpen, + onItemClick, + scrollKey, +}: DropdownProps) => { + const dispatch = useAppDispatch(); + const openDropdownId = useAppSelector((state) => state.dropdownMenu.openId); + const openedViaKeyboard = useAppSelector( + (state) => state.dropdownMenu.keyboard, + ); + const [currentId] = useState(id++); + const open = currentId === openDropdownId; + const activeElement = useRef(null); + const targetRef = useRef(null); + + const handleClose = useCallback(() => { + if (activeElement.current) { + activeElement.current.focus({ preventScroll: true }); + activeElement.current = null; + } + + dispatch( + closeModal({ + modalType: 'ACTIONS', + ignoreFocus: false, + }), + ); + + dispatch(closeDropdownMenu({ id: currentId })); + }, [dispatch, currentId]); + + const handleItemClick = useCallback( + (e: React.MouseEvent | React.KeyboardEvent) => { + const i = Number(e.currentTarget.getAttribute('data-index')); + const item = items?.[i]; + + handleClose(); + + if (!item) { + return; + } + + if (typeof onItemClick === 'function') { + e.preventDefault(); + onItemClick(item, i); + } else if (isActionItem(item)) { + e.preventDefault(); + item.action(); + } + }, + [handleClose, onItemClick, items], + ); + + const handleClick = useCallback( + (e: React.MouseEvent | React.KeyboardEvent) => { + const { type } = e; + + if (open) { + handleClose(); + } else { + onOpen?.(); + + if (status) { + dispatch(fetchRelationships([status.getIn(['account', 'id'])])); + } + + if (isUserTouching()) { + dispatch( + openModal({ + modalType: 'ACTIONS', + modalProps: { + status, + actions: items, + onClick: handleItemClick, + }, + }), + ); + } else { + dispatch( + openDropdownMenu({ + id: currentId, + keyboard: type !== 'click', + scrollKey, + }), + ); + } + } + }, + [ + dispatch, + currentId, + scrollKey, + onOpen, + handleItemClick, + open, + status, + items, + handleClose, + ], + ); + + const handleMouseDown = useCallback(() => { + if (!open && document.activeElement instanceof HTMLElement) { + activeElement.current = document.activeElement; + } + }, [open]); + + const handleButtonKeyDown = useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case ' ': + case 'Enter': + handleMouseDown(); + break; + } + }, + [handleMouseDown], + ); + + const handleKeyPress = useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case ' ': + case 'Enter': + handleClick(e); + e.stopPropagation(); + e.preventDefault(); + break; + } + }, + [handleClick], + ); + + useEffect(() => { + return () => { + if (currentId === openDropdownId) { + handleClose(); + } + }; + }, [currentId, openDropdownId, handleClose]); + + let button: React.ReactElement; + + if (children) { + button = cloneElement(Children.only(children), { + onClick: handleClick, + onMouseDown: handleMouseDown, + onKeyDown: handleButtonKeyDown, + onKeyPress: handleKeyPress, + ref: targetRef, + }); + } else if (icon && iconComponent) { + button = ( + + ); + } else { + return null; + } + + return ( + <> + {button} + + + {({ props, arrowProps, placement }) => ( +
    +
    +
    + + +
    +
    + )} + + + ); +}; diff --git a/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js b/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js deleted file mode 100644 index 726fee9076..0000000000 --- a/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js +++ /dev/null @@ -1,32 +0,0 @@ -import { connect } from 'react-redux'; - -import { openDropdownMenu, closeDropdownMenu } from 'mastodon/actions/dropdown_menu'; -import { fetchHistory } from 'mastodon/actions/history'; -import DropdownMenu from 'mastodon/components/dropdown_menu'; - -/** - * - * @param {import('mastodon/store').RootState} state - * @param {*} props - */ -const mapStateToProps = (state, { statusId }) => ({ - openDropdownId: state.dropdownMenu.openId, - openedViaKeyboard: state.dropdownMenu.keyboard, - items: state.getIn(['history', statusId, 'items']), - loading: state.getIn(['history', statusId, 'loading']), -}); - -const mapDispatchToProps = (dispatch, { statusId }) => ({ - - onOpen (id, onItemClick, keyboard) { - dispatch(fetchHistory(statusId)); - dispatch(openDropdownMenu({ id, keyboard })); - }, - - onClose (id) { - dispatch(closeDropdownMenu({ id })); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu); diff --git a/app/javascript/mastodon/components/edited_timestamp/index.jsx b/app/javascript/mastodon/components/edited_timestamp/index.jsx deleted file mode 100644 index f8fa043259..0000000000 --- a/app/javascript/mastodon/components/edited_timestamp/index.jsx +++ /dev/null @@ -1,77 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { FormattedMessage, injectIntl } from 'react-intl'; - -import { connect } from 'react-redux'; - -import { openModal } from 'mastodon/actions/modal'; -import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; -import InlineAccount from 'mastodon/components/inline_account'; -import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; - -import DropdownMenu from './containers/dropdown_menu_container'; - -const mapDispatchToProps = (dispatch, { statusId }) => ({ - - onItemClick (index) { - dispatch(openModal({ - modalType: 'COMPARE_HISTORY', - modalProps: { index, statusId }, - })); - }, - -}); - -class EditedTimestamp extends PureComponent { - - static propTypes = { - statusId: PropTypes.string.isRequired, - timestamp: PropTypes.string.isRequired, - intl: PropTypes.object.isRequired, - onItemClick: PropTypes.func.isRequired, - }; - - handleItemClick = (item, i) => { - const { onItemClick } = this.props; - onItemClick(i); - }; - - renderHeader = items => { - return ( - - ); - }; - - renderItem = (item, index, { onClick, onKeyPress }) => { - const formattedDate = ; - const formattedName = ; - - const label = item.get('original') ? ( - - ) : ( - - ); - - return ( -
  • - -
  • - ); - }; - - render () { - const { timestamp, statusId } = this.props; - - return ( - - - - ); - } - -} - -export default connect(null, mapDispatchToProps)(injectIntl(EditedTimestamp)); diff --git a/app/javascript/mastodon/components/edited_timestamp/index.tsx b/app/javascript/mastodon/components/edited_timestamp/index.tsx new file mode 100644 index 0000000000..4a33210199 --- /dev/null +++ b/app/javascript/mastodon/components/edited_timestamp/index.tsx @@ -0,0 +1,140 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import type { Map as ImmutableMap, List as ImmutableList } from 'immutable'; + +import { fetchHistory } from 'mastodon/actions/history'; +import { openModal } from 'mastodon/actions/modal'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; +import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; +import InlineAccount from 'mastodon/components/inline_account'; +import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +type HistoryItem = ImmutableMap; + +export const EditedTimestamp: React.FC<{ + statusId: string; + timestamp: string; +}> = ({ statusId, timestamp }) => { + const dispatch = useAppDispatch(); + const items = useAppSelector( + (state) => + ( + state.history.getIn([statusId, 'items']) as + | ImmutableList + | undefined + )?.toArray() as HistoryItem[], + ); + const loading = useAppSelector( + (state) => state.history.getIn([statusId, 'loading']) as boolean, + ); + + const handleOpen = useCallback(() => { + dispatch(fetchHistory(statusId)); + }, [dispatch, statusId]); + + const handleItemClick = useCallback( + (_item: HistoryItem, index: number) => { + dispatch( + openModal({ + modalType: 'COMPARE_HISTORY', + modalProps: { index, statusId }, + }), + ); + }, + [dispatch, statusId], + ); + + const renderHeader = useCallback((items: HistoryItem[]) => { + return ( + + ); + }, []); + + const renderItem = useCallback( + ( + item: HistoryItem, + index: number, + { + onClick, + onKeyUp, + }: { + onClick: React.MouseEventHandler; + onKeyUp: React.KeyboardEventHandler; + }, + ) => { + const formattedDate = ( + + ); + const formattedName = ( + + ); + + const label = (item.get('original') as boolean) ? ( + + ) : ( + + ); + + return ( +
  • + +
  • + ); + }, + [], + ); + + return ( + + items={items} + loading={loading} + renderItem={renderItem} + scrollable + renderHeader={renderHeader} + onOpen={handleOpen} + onItemClick={handleItemClick} + > + + + ); +}; diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index 1d3fdd21b0..f21ad60240 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -16,8 +16,7 @@ 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' }, - edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, + editProfile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, }); export const FollowButton: React.FC<{ @@ -73,15 +72,9 @@ export const FollowButton: React.FC<{ if (!signedIn) { label = intl.formatMessage(messages.follow); } else if (accountId === me) { - label = intl.formatMessage(messages.edit_profile); + label = intl.formatMessage(messages.editProfile); } else if (!relationship) { label = ; - } else if ( - relationship.following && - isShowItem('relationships') && - relationship.followed_by - ) { - label = intl.formatMessage(messages.mutual); } else if (relationship.following || relationship.requested) { label = intl.formatMessage(messages.unfollow); } else if (relationship.followed_by && isShowItem('relationships')) { diff --git a/app/javascript/mastodon/components/hashtag.tsx b/app/javascript/mastodon/components/hashtag.tsx index 30c20e0abd..346c95183f 100644 --- a/app/javascript/mastodon/components/hashtag.tsx +++ b/app/javascript/mastodon/components/hashtag.tsx @@ -102,10 +102,11 @@ export interface HashtagProps { description?: React.ReactNode; history?: number[]; name: string; - people: number; + people?: number; to: string; uses?: number; withGraph?: boolean; + children?: React.ReactNode; } export const Hashtag: React.FC = ({ @@ -117,6 +118,7 @@ export const Hashtag: React.FC = ({ className, description, withGraph = true, + children, }) => (
    @@ -158,5 +160,7 @@ export const Hashtag: React.FC = ({
    )} + + {children &&
    {children}
    }
    ); diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index 9e1d74bb74..ce8f17ddb9 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -20,6 +20,7 @@ export type StatusLike = Record<{ contentHTML: string; media_attachments: List; spoiler_text?: string; + account: Record<{ id: string }>; }>; function normalizeHashtag(hashtag: string) { @@ -195,19 +196,36 @@ export function getHashtagBarForStatus(status: StatusLike) { return { statusContentProps, - hashtagBar: , + hashtagBar: ( + + ), }; } -export function getFeaturedHashtagBar(acct: string, tags: string[]) { - return ; +export function getFeaturedHashtagBar( + accountId: string, + acct: string, + tags: string[], +) { + return ( + + ); } const HashtagBar: React.FC<{ hashtags: string[]; + accountId: string; acct?: string; defaultExpanded?: boolean; -}> = ({ hashtags, acct, defaultExpanded }) => { +}> = ({ hashtags, accountId, acct, defaultExpanded }) => { const [expanded, setExpanded] = useState(false); const handleClick = useCallback(() => { setExpanded(true); @@ -228,6 +246,7 @@ const HashtagBar: React.FC<{ #{hashtag} diff --git a/app/javascript/mastodon/components/icon_button.tsx b/app/javascript/mastodon/components/icon_button.tsx index 73fc46d281..7e0b3e7a22 100644 --- a/app/javascript/mastodon/components/icon_button.tsx +++ b/app/javascript/mastodon/components/icon_button.tsx @@ -1,4 +1,4 @@ -import { PureComponent, createRef } from 'react'; +import { useState, useEffect, useCallback, forwardRef } from 'react'; import classNames from 'classnames'; @@ -15,101 +15,110 @@ interface Props { onMouseDown?: React.MouseEventHandler; onKeyDown?: React.KeyboardEventHandler; onKeyPress?: React.KeyboardEventHandler; - active: boolean; + active?: boolean; expanded?: boolean; style?: React.CSSProperties; activeStyle?: React.CSSProperties; - disabled: boolean; + disabled?: boolean; inverted?: boolean; - animate: boolean; - overlay: boolean; - tabIndex: number; + animate?: boolean; + overlay?: boolean; + tabIndex?: number; counter?: number; href?: string; - ariaHidden: boolean; + ariaHidden?: boolean; data_id?: string; } -interface States { - activate: boolean; - deactivate: boolean; -} -export class IconButton extends PureComponent { - buttonRef = createRef(); - static defaultProps = { - active: false, - disabled: false, - animate: false, - overlay: false, - tabIndex: 0, - ariaHidden: false, - }; - - state = { - activate: false, - deactivate: false, - }; - - UNSAFE_componentWillReceiveProps(nextProps: Props) { - if (!nextProps.animate) return; - - if (this.props.active && !nextProps.active) { - this.setState({ activate: false, deactivate: true }); - } else if (!this.props.active && nextProps.active) { - this.setState({ activate: true, deactivate: false }); - } - } - - handleClick: React.MouseEventHandler = (e) => { - e.preventDefault(); - - if (!this.props.disabled && this.props.onClick != null) { - this.props.onClick(e); - } - }; - - handleKeyPress: React.KeyboardEventHandler = (e) => { - if (this.props.onKeyPress && !this.props.disabled) { - this.props.onKeyPress(e); - } - }; - - handleMouseDown: React.MouseEventHandler = (e) => { - if (!this.props.disabled && this.props.onMouseDown) { - this.props.onMouseDown(e); - } - }; - - handleKeyDown: React.KeyboardEventHandler = (e) => { - if (!this.props.disabled && this.props.onKeyDown) { - this.props.onKeyDown(e); - } - }; - - render() { - const style = { - ...this.props.style, - ...(this.props.active ? this.props.activeStyle : {}), - }; - - const { - active, +export const IconButton = forwardRef( + ( + { className, - disabled, expanded, icon, iconComponent, inverted, - overlay, - tabIndex, title, counter, href, - ariaHidden, - data_id, - } = this.props; + style, + activeStyle, + onClick, + onKeyDown, + onKeyPress, + onMouseDown, + active = false, + disabled = false, + animate = false, + overlay = false, + tabIndex = 0, + ariaHidden = false, + data_id = undefined, + }, + buttonRef, + ) => { + const [activate, setActivate] = useState(false); + const [deactivate, setDeactivate] = useState(false); - const { activate, deactivate } = this.state; + useEffect(() => { + if (!animate) { + return; + } + + if (activate && !active) { + setActivate(false); + setDeactivate(true); + } else if (!activate && active) { + setActivate(true); + setDeactivate(false); + } + }, [setActivate, setDeactivate, animate, active, activate]); + + const handleClick: React.MouseEventHandler = useCallback( + (e) => { + e.preventDefault(); + + if (!disabled) { + onClick?.(e); + } + }, + [disabled, onClick], + ); + + const handleKeyPress: React.KeyboardEventHandler = + useCallback( + (e) => { + if (!disabled) { + onKeyPress?.(e); + } + }, + [disabled, onKeyPress], + ); + + const handleMouseDown: React.MouseEventHandler = + useCallback( + (e) => { + if (!disabled) { + onMouseDown?.(e); + } + }, + [disabled, onMouseDown], + ); + + const handleKeyDown: React.KeyboardEventHandler = + useCallback( + (e) => { + if (!disabled) { + onKeyDown?.(e); + } + }, + [disabled, onKeyDown], + ); + + const buttonStyle = { + ...style, + ...(active ? activeStyle : {}), + }; const classes = classNames(className, 'icon-button', { active, @@ -148,19 +157,20 @@ export class IconButton extends PureComponent { aria-hidden={ariaHidden} title={title} className={classes} - onClick={this.handleClick} - onMouseDown={this.handleMouseDown} - onKeyDown={this.handleKeyDown} - // eslint-disable-next-line @typescript-eslint/no-deprecated - onKeyPress={this.handleKeyPress} - style={style} + onClick={handleClick} + onMouseDown={handleMouseDown} + onKeyDown={handleKeyDown} + onKeyPress={handleKeyPress} // eslint-disable-line @typescript-eslint/no-deprecated + style={buttonStyle} tabIndex={tabIndex} disabled={disabled} data-id={data_id} - ref={this.buttonRef} + ref={buttonRef} > {contents} ); - } -} + }, +); + +IconButton.displayName = 'IconButton'; diff --git a/app/javascript/mastodon/components/navigation_portal.tsx b/app/javascript/mastodon/components/navigation_portal.tsx index 08f91ce18a..d3ac8baa6e 100644 --- a/app/javascript/mastodon/components/navigation_portal.tsx +++ b/app/javascript/mastodon/components/navigation_portal.tsx @@ -1,25 +1,6 @@ -import { Switch, Route } from 'react-router-dom'; - -import AccountNavigation from 'mastodon/features/account/navigation'; import Trends from 'mastodon/features/getting_started/containers/trends_container'; import { showTrends } from 'mastodon/initial_state'; -const DefaultNavigation: React.FC = () => (showTrends ? : null); - export const NavigationPortal: React.FC = () => ( -
    - - - - - - - - - -
    +
    {showTrends && }
    ); diff --git a/app/javascript/mastodon/components/remote_hint.tsx b/app/javascript/mastodon/components/remote_hint.tsx new file mode 100644 index 0000000000..772aa805db --- /dev/null +++ b/app/javascript/mastodon/components/remote_hint.tsx @@ -0,0 +1,43 @@ +import { FormattedMessage } from 'react-intl'; + +import { useAppSelector } from 'mastodon/store'; + +import { TimelineHint } from './timeline_hint'; + +interface RemoteHintProps { + accountId?: string; +} + +export const RemoteHint: React.FC = ({ accountId }) => { + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + const domain = account?.acct ? account.acct.split('@')[1] : undefined; + if ( + !account || + !account.url || + account.acct !== account.username || + !domain + ) { + return null; + } + + return ( + + } + label={ + {domain} }} + /> + } + /> + ); +}; diff --git a/app/javascript/mastodon/components/server_banner.jsx b/app/javascript/mastodon/components/server_banner.jsx index 72e4489dc1..989ac7f006 100644 --- a/app/javascript/mastodon/components/server_banner.jsx +++ b/app/javascript/mastodon/components/server_banner.jsx @@ -42,7 +42,7 @@ class ServerBanner extends PureComponent { return (
    - {domain}, mastodon: Mastodon }} /> + {domain}, mastodon: Mastodon }} />
    diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index 5e4593160c..4721e9da93 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -25,9 +25,8 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - -import DropdownMenuContainer from '../containers/dropdown_menu_container'; import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { enableEmojiReaction , bookmarkCategoryNeeded, simpleTimelineMenu, me, isHideItem, boostMenu, boostModal } from '../initial_state'; import { IconButton } from './icon_button'; @@ -349,10 +348,9 @@ class StatusActionBar extends ImmutablePureComponent { if (writtenByMe && pinnableStatus) { menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); + menu.push(null); } - menu.push(null); - if (writtenByMe || withDismiss) { menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); menu.push(null); @@ -498,7 +496,7 @@ class StatusActionBar extends ImmutablePureComponent {
    {reblogMenu.length === 0 ? reblogButton : ( - - {reblogButton} - + /> )}
    @@ -522,7 +518,7 @@ class StatusActionBar extends ImmutablePureComponent {
    {emojiPickerDropdown}
    - ({ - openDropdownId: state.dropdownMenu.openId, - openedViaKeyboard: state.dropdownMenu.keyboard, -}); - -/** - * @param {any} dispatch - * @param {Object} root0 - * @param {any} [root0.status] - * @param {any} root0.items - * @param {any} [root0.scrollKey] - */ -const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({ - onOpen(id, onItemClick, keyboard) { - if (status) { - dispatch(fetchRelationships([status.getIn(['account', 'id'])])); - } - - dispatch(isUserTouching() ? openModal({ - modalType: 'ACTIONS', - modalProps: { - status, - actions: items, - onClick: onItemClick, - }, - }) : openDropdownMenu({ id, keyboard, scrollKey })); - }, - - onClose(id) { - dispatch(closeModal({ - modalType: 'ACTIONS', - ignoreFocus: false, - })); - dispatch(closeDropdownMenu({ id })); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu); diff --git a/app/javascript/mastodon/features/about/index.jsx b/app/javascript/mastodon/features/about/index.jsx index 6acffa3141..8158f47c11 100644 --- a/app/javascript/mastodon/features/about/index.jsx +++ b/app/javascript/mastodon/features/about/index.jsx @@ -14,13 +14,13 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react' import DisabledIcon from '@/material-icons/400-24px/close-fill.svg?react'; import EnabledIcon from '@/material-icons/400-24px/done-fill.svg?react'; import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react'; -import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server'; +import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server'; import { Account } from 'mastodon/components/account'; import Column from 'mastodon/components/column'; -import { Icon } from 'mastodon/components/icon'; +import { Icon } from 'mastodon/components/icon'; import { ServerHeroImage } from 'mastodon/components/server_hero_image'; import { Skeleton } from 'mastodon/components/skeleton'; -import { LinkFooter } from 'mastodon/features/ui/components/link_footer'; +import { LinkFooter} from 'mastodon/features/ui/components/link_footer'; const messages = defineMessages({ title: { id: 'column.about', defaultMessage: 'About' }, @@ -40,10 +40,6 @@ const messages = defineMessages({ enabled: { id: 'about.enabled', defaultMessage: 'Enabled' }, disabled: { id: 'about.disabled', defaultMessage: 'Disabled' }, capabilities: { id: 'about.kmyblue_capabilities', defaultMessage: 'Features available in this server' }, - joinFediverse: { - id: 'about.join_fediverse', - defaultMessage: "Join the Fediverse, become part of a community, and break free from Big Tech™'s stranglehold on public discourse." - }, }); const severityMessages = { @@ -63,13 +59,14 @@ const severityMessages = { }, }; -const mapStateToProps = (state) => ({ +const mapStateToProps = state => ({ server: state.getIn(['server', 'server']), extendedDescription: state.getIn(['server', 'extendedDescription']), domainBlocks: state.getIn(['server', 'domainBlocks']), }); class Section extends PureComponent { + static propTypes = { title: PropTypes.string, children: PropTypes.node, @@ -88,64 +85,49 @@ class Section extends PureComponent { this.setState({ collapsed: !collapsed }, () => onOpen && onOpen()); }; - handleKeyDown = (e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - this.handleClick(); - } - }; - - render() { + render () { const { title, children } = this.props; const { collapsed } = this.state; return (
    -
    +
    {title}
    - {!collapsed &&
    {children}
    } + {!collapsed && ( +
    {children}
    + )}
    ); } + } class CapabilityIcon extends PureComponent { + static propTypes = { intl: PropTypes.object.isRequired, state: PropTypes.bool, }; - render() { + render () { const { intl, state } = this.props; if (state) { return ( - - - {intl.formatMessage(messages.enabled)} - + {intl.formatMessage(messages.enabled)} ); } else { return ( - - - {intl.formatMessage(messages.disabled)} - + {intl.formatMessage(messages.disabled)} ); } } } class About extends PureComponent { + static propTypes = { server: ImmutablePropTypes.map, extendedDescription: ImmutablePropTypes.map, @@ -159,7 +141,7 @@ class About extends PureComponent { multiColumn: PropTypes.bool, }; - componentDidMount() { + componentDidMount () { const { dispatch } = this.props; dispatch(fetchServer()); dispatch(fetchExtendedDescription()); @@ -170,11 +152,11 @@ class About extends PureComponent { dispatch(fetchDomainBlocks()); }; - render() { + render () { const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props; const isLoading = server.get('isLoading'); - const fedibirdCapabilities = server.get('fedibird_capabilities') || []; + const fedibirdCapabilities = server.get('fedibird_capabilities') || []; // thinking about isLoading is true const isPublicUnlistedVisibility = fedibirdCapabilities.includes('kmyblue_visibility_public_unlisted'); const isPublicVisibility = !fedibirdCapabilities.includes('kmyblue_no_public_visibility'); const isEmojiReaction = fedibirdCapabilities.includes('emoji_reaction'); @@ -187,88 +169,59 @@ class About extends PureComponent { return ( -
    -
    - `${value} ${key.replace('@', '')}`) - .join(', ')} - className="about__header__hero" - /> -

    {isLoading ? : server.get('domain')}

    -

    - -

    +
    +
    + `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' /> +

    {isLoading ? : server.get('domain')}

    +

    -
    -
    -

    - -

    +
    +
    +

    -
    +
    -
    -

    - -

    +
    +

    - {isLoading ? ( - - ) : ( - - {server.getIn(['contact', 'email'])} - - )} + {isLoading ? : {server.getIn(['contact', 'email'])}}
    {extendedDescription.get('isLoading') ? ( <> - +
    - +
    - +
    - + - ) : extendedDescription.get('content')?.length > 0 ? ( -
    + ) : (extendedDescription.get('content')?.length > 0 ? ( +
    ) : ( -

    - -

    - )} +

    + ))}
    {!isLoading && (server.get('rules', ImmutableList()).isEmpty() ? ( -

    - -

    +

    ) : ( -
      - {server.get('rules').map((rule) => ( +
        + {server.get('rules').map(rule => (
      1. -
        {rule.get('text')}
        - {!!rule.get('hint') && rule.get('hint').length > 0 && ( -
        {rule.get('hint')}
        - )} +
        {rule.get('text')}
        + {rule.get('hint').length > 0 && (
        {rule.get('hint')}
        )}
      2. ))}
      @@ -276,38 +229,23 @@ class About extends PureComponent {
    -

    - -

    +

    {!isLoading && ( -
      +
      1. - - {intl.formatMessage(messages.emojiReaction)}: - + {intl.formatMessage(messages.emojiReaction)}:
      2. - - {intl.formatMessage(messages.publicVisibility)}: - + {intl.formatMessage(messages.publicVisibility)}:
      3. - - {intl.formatMessage(messages.publicUnlistedVisibility)}: - + {intl.formatMessage(messages.publicUnlistedVisibility)}:
      4. - - {intl.formatMessage(messages.localTimeline)}: - + {intl.formatMessage(messages.localTimeline)}:
      5. - - {intl.formatMessage(messages.fullTextSearch)}: - + {intl.formatMessage(messages.fullTextSearch)}:
      )} @@ -316,75 +254,49 @@ class About extends PureComponent {
      {domainBlocks.get('isLoading') ? ( <> - +
      - + - ) : domainBlocks.get('isAvailable') ? ( + ) : (domainBlocks.get('isAvailable') ? ( <> -

      - -

      +

      {domainBlocks.get('items').size > 0 && ( -
      - {domainBlocks.get('items').map((block) => ( -
      -
      -
      - {block.get('domain')} -
      - - {intl.formatMessage( - severityMessages[block.get('severity_ex') || block.get('severity')].title - )} - +
      + {domainBlocks.get('items').map(block => ( +
      +
      +
      {block.get('domain')}
      + {intl.formatMessage(severityMessages[block.get('severity_ex') || block.get('severity')].title)}
      -

      - {(block.get('comment') || '').length > 0 ? ( - block.get('comment') - ) : ( - - )} -

      +

      {(block.get('comment') || '').length > 0 ? block.get('comment') : }

      ))}
      )} ) : ( -

      - -

      - )} +

      + ))}
      -
      -

      - -

      +
      +

      {intl.formatMessage(messages.title)} - + ); } + } export default connect(mapStateToProps)(injectIntl(About)); diff --git a/app/javascript/mastodon/features/account/components/featured_tags.jsx b/app/javascript/mastodon/features/account/components/featured_tags.jsx deleted file mode 100644 index 56a9efac02..0000000000 --- a/app/javascript/mastodon/features/account/components/featured_tags.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -import { Hashtag } from 'mastodon/components/hashtag'; - -const messages = defineMessages({ - lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' }, - empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' }, -}); - -class FeaturedTags extends ImmutablePureComponent { - - static propTypes = { - account: ImmutablePropTypes.record, - featuredTags: ImmutablePropTypes.list, - tagged: PropTypes.string, - intl: PropTypes.object.isRequired, - }; - - render () { - const { account, featuredTags, intl } = this.props; - - if (!account || account.get('suspended') || featuredTags.isEmpty()) { - return null; - } - - return ( -
      -

      }} />

      - - {featuredTags.take(3).map(featuredTag => ( - 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get('last_status_at'), { month: 'short', day: '2-digit' }) }) : intl.formatMessage(messages.empty)} - /> - ))} -
      - ); - } - -} - -export default injectIntl(FeaturedTags); diff --git a/app/javascript/mastodon/features/account/containers/featured_tags_container.js b/app/javascript/mastodon/features/account/containers/featured_tags_container.js deleted file mode 100644 index 726c805f78..0000000000 --- a/app/javascript/mastodon/features/account/containers/featured_tags_container.js +++ /dev/null @@ -1,17 +0,0 @@ -import { List as ImmutableList } from 'immutable'; -import { connect } from 'react-redux'; - -import { makeGetAccount } from 'mastodon/selectors'; - -import FeaturedTags from '../components/featured_tags'; - -const mapStateToProps = () => { - const getAccount = makeGetAccount(); - - return (state, { accountId }) => ({ - account: getAccount(state, accountId), - featuredTags: state.getIn(['user_lists', 'featured_tags', accountId, 'items'], ImmutableList()), - }); -}; - -export default connect(mapStateToProps)(FeaturedTags); diff --git a/app/javascript/mastodon/features/account/navigation.jsx b/app/javascript/mastodon/features/account/navigation.jsx deleted file mode 100644 index aa78135de2..0000000000 --- a/app/javascript/mastodon/features/account/navigation.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { connect } from 'react-redux'; - -import FeaturedTags from 'mastodon/features/account/containers/featured_tags_container'; -import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; - -const mapStateToProps = (state, { match: { params: { acct } } }) => { - const accountId = state.getIn(['accounts_map', normalizeForLookup(acct)]); - - if (!accountId) { - return { - isLoading: true, - }; - } - - return { - accountId, - isLoading: false, - }; -}; - -class AccountNavigation extends PureComponent { - - static propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape({ - acct: PropTypes.string, - tagged: PropTypes.string, - }).isRequired, - }).isRequired, - - accountId: PropTypes.string, - isLoading: PropTypes.bool, - }; - - render () { - const { accountId, isLoading, match: { params: { tagged } } } = this.props; - - if (isLoading) { - return null; - } - - return ( - - ); - } - -} - -export default connect(mapStateToProps)(AccountNavigation); diff --git a/app/javascript/mastodon/features/account_featured/components/empty_message.tsx b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx new file mode 100644 index 0000000000..9dd8ffdfe0 --- /dev/null +++ b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx @@ -0,0 +1,50 @@ +import { FormattedMessage } from 'react-intl'; + +import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint'; + +interface EmptyMessageProps { + suspended: boolean; + hidden: boolean; + blockedBy: boolean; + accountId?: string; +} + +export const EmptyMessage: React.FC = ({ + accountId, + suspended, + hidden, + blockedBy, +}) => { + if (!accountId) { + return null; + } + + let message: React.ReactNode = null; + + if (suspended) { + message = ( + + ); + } else if (hidden) { + message = ; + } else if (blockedBy) { + message = ( + + ); + } else { + message = ( + + ); + } + + return
      {message}
      ; +}; diff --git a/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx b/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx new file mode 100644 index 0000000000..7b476ba01d --- /dev/null +++ b/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx @@ -0,0 +1,51 @@ +import { defineMessages, useIntl } from 'react-intl'; + +import type { Map as ImmutableMap } from 'immutable'; + +import { Hashtag } from 'mastodon/components/hashtag'; + +export type TagMap = ImmutableMap< + 'id' | 'name' | 'url' | 'statuses_count' | 'last_status_at' | 'accountId', + string | null +>; + +interface FeaturedTagProps { + tag: TagMap; + account: string; +} + +const messages = defineMessages({ + lastStatusAt: { + id: 'account.featured_tags.last_status_at', + defaultMessage: 'Last post on {date}', + }, + empty: { + id: 'account.featured_tags.last_status_never', + defaultMessage: 'No posts', + }, +}); + +export const FeaturedTag: React.FC = ({ tag, account }) => { + const intl = useIntl(); + const name = tag.get('name') ?? ''; + const count = Number.parseInt(tag.get('statuses_count') ?? ''); + return ( + 0 + ? intl.formatMessage(messages.lastStatusAt, { + date: intl.formatDate(tag.get('last_status_at') ?? '', { + month: 'short', + day: '2-digit', + }), + }) + : intl.formatMessage(messages.empty) + } + /> + ); +}; diff --git a/app/javascript/mastodon/features/account_featured/index.tsx b/app/javascript/mastodon/features/account_featured/index.tsx new file mode 100644 index 0000000000..70e411f61a --- /dev/null +++ b/app/javascript/mastodon/features/account_featured/index.tsx @@ -0,0 +1,156 @@ +import { useEffect } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { useParams } from 'react-router'; + +import type { Map as ImmutableMap } from 'immutable'; +import { List as ImmutableList } from 'immutable'; + +import { fetchFeaturedTags } from 'mastodon/actions/featured_tags'; +import { expandAccountFeaturedTimeline } from 'mastodon/actions/timelines'; +import { ColumnBackButton } from 'mastodon/components/column_back_button'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { RemoteHint } from 'mastodon/components/remote_hint'; +import StatusContainer from 'mastodon/containers/status_container'; +import { useAccountId } from 'mastodon/hooks/useAccountId'; +import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +import { AccountHeader } from '../account_timeline/components/account_header'; +import Column from '../ui/components/column'; + +import { EmptyMessage } from './components/empty_message'; +import { FeaturedTag } from './components/featured_tag'; +import type { TagMap } from './components/featured_tag'; + +interface Params { + acct?: string; + id?: string; +} + +const AccountFeatured = () => { + const accountId = useAccountId(); + const { suspended, blockedBy, hidden } = useAccountVisibility(accountId); + const forceEmptyState = suspended || blockedBy || hidden; + const { acct = '' } = useParams(); + + const dispatch = useAppDispatch(); + + useEffect(() => { + if (accountId) { + void dispatch(expandAccountFeaturedTimeline(accountId)); + dispatch(fetchFeaturedTags(accountId)); + } + }, [accountId, dispatch]); + + const isLoading = useAppSelector( + (state) => + !accountId || + !!(state.timelines as ImmutableMap).getIn([ + `account:${accountId}:pinned`, + 'isLoading', + ]) || + !!state.user_lists.getIn(['featured_tags', accountId, 'isLoading']), + ); + const featuredTags = useAppSelector( + (state) => + state.user_lists.getIn( + ['featured_tags', accountId, 'items'], + ImmutableList(), + ) as ImmutableList, + ); + const featuredStatusIds = useAppSelector( + (state) => + (state.timelines as ImmutableMap).getIn( + [`account:${accountId}:pinned`, 'items'], + ImmutableList(), + ) as ImmutableList, + ); + + if (isLoading) { + return ( + +
      + +
      +
      + ); + } + + if (featuredStatusIds.isEmpty() && featuredTags.isEmpty()) { + return ( + + + ); + } + + return ( + + + +
      + {accountId && ( + + )} + {!featuredTags.isEmpty() && ( + <> +

      + +

      + {featuredTags.map((tag) => ( + + ))} + + )} + {!featuredStatusIds.isEmpty() && ( + <> +

      + +

      + {featuredStatusIds.map((statusId) => ( + + ))} + + )} + +
      +
      + ); +}; + +const AccountFeaturedWrapper = ({ + children, + accountId, +}: React.PropsWithChildren<{ accountId?: string }>) => { + return ( + + +
      + {accountId && } + {children} +
      +
      + ); +}; + +// eslint-disable-next-line import/no-default-export +export default AccountFeatured; diff --git a/app/javascript/mastodon/features/account_gallery/index.tsx b/app/javascript/mastodon/features/account_gallery/index.tsx index 60afdadc81..0027329c93 100644 --- a/app/javascript/mastodon/features/account_gallery/index.tsx +++ b/app/javascript/mastodon/features/account_gallery/index.tsx @@ -2,25 +2,22 @@ import { useEffect, useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import { useParams } from 'react-router-dom'; - import { createSelector } from '@reduxjs/toolkit'; import type { Map as ImmutableMap } from 'immutable'; import { List as ImmutableList } from 'immutable'; -import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts'; import { openModal } from 'mastodon/actions/modal'; import { expandAccountMediaTimeline } from 'mastodon/actions/timelines'; import { ColumnBackButton } from 'mastodon/components/column_back_button'; +import { RemoteHint } from 'mastodon/components/remote_hint'; import ScrollableList from 'mastodon/components/scrollable_list'; -import { TimelineHint } from 'mastodon/components/timeline_hint'; import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header'; import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import Column from 'mastodon/features/ui/components/column'; +import { useAccountId } from 'mastodon/hooks/useAccountId'; +import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility'; import type { MediaAttachment } from 'mastodon/models/media_attachment'; -import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; -import { getAccountHidden } from 'mastodon/selectors/accounts'; import type { RootState } from 'mastodon/store'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -56,53 +53,11 @@ const getAccountGallery = createSelector( }, ); -interface Params { - acct?: string; - id?: string; -} - -const RemoteHint: React.FC<{ - accountId: string; -}> = ({ accountId }) => { - const account = useAppSelector((state) => state.accounts.get(accountId)); - const acct = account?.acct; - const url = account?.url; - const domain = acct ? acct.split('@')[1] : undefined; - - if (!url) { - return null; - } - - return ( - - } - label={ - {domain} }} - /> - } - /> - ); -}; - export const AccountGallery: React.FC<{ multiColumn: boolean; }> = ({ multiColumn }) => { - const { acct, id } = useParams(); const dispatch = useAppDispatch(); - const accountId = useAppSelector( - (state) => - id ?? - (state.accounts_map.get(normalizeForLookup(acct)) as string | undefined), - ); + const accountId = useAccountId(); const attachments = useAppSelector((state) => accountId ? getAccountGallery(state, accountId) @@ -123,33 +78,15 @@ export const AccountGallery: React.FC<{ const account = useAppSelector((state) => accountId ? state.accounts.get(accountId) : undefined, ); - const blockedBy = useAppSelector( - (state) => - state.relationships.getIn([accountId, 'blocked_by'], false) as boolean, - ); - const suspended = useAppSelector( - (state) => state.accounts.getIn([accountId, 'suspended'], false) as boolean, - ); const isAccount = !!account; - const remote = account?.acct !== account?.username; - const hidden = useAppSelector((state) => - accountId ? getAccountHidden(state, accountId) : false, - ); + + const { suspended, blockedBy, hidden } = useAccountVisibility(accountId); + const maxId = attachments.last()?.getIn(['status', 'id']) as | string | undefined; useEffect(() => { - if (!accountId) { - dispatch(lookupAccount(acct)); - } - }, [dispatch, accountId, acct]); - - useEffect(() => { - if (accountId && !isAccount) { - dispatch(fetchAccount(accountId)); - } - if (accountId && isAccount) { void dispatch(expandAccountMediaTimeline(accountId)); } @@ -233,7 +170,7 @@ export const AccountGallery: React.FC<{ defaultMessage='Profile unavailable' /> ); - } else if (remote && attachments.isEmpty()) { + } else if (attachments.isEmpty()) { emptyMessage = ; } else { emptyMessage = ( @@ -259,7 +196,7 @@ export const AccountGallery: React.FC<{ ) } alwaysPrepend - append={remote && accountId && } + append={accountId && } scrollKey='account_gallery' isLoading={isLoading} hasMore={!forceEmptyState && hasMore} diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index 0d4f20795c..3f9c7d843a 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -44,27 +44,21 @@ import { FollowingCounter, StatusesCounter, } from 'mastodon/components/counters'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; +import { FollowButton } from 'mastodon/components/follow_button'; import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; import { getFeaturedHashtagBar } from 'mastodon/components/hashtag_bar'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from 'mastodon/components/icon_button'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { ShortNumber } from 'mastodon/components/short_number'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { DomainPill } from 'mastodon/features/account/components/domain_pill'; import AccountNoteContainer from 'mastodon/features/account/containers/account_note_container'; import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container'; import { useLinks } from 'mastodon/hooks/useLinks'; import { useIdentity } from 'mastodon/identity_context'; -import { - autoPlayGif, - me, - domain as localDomain, - isShowItem, -} from 'mastodon/initial_state'; +import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state'; import type { Account } from 'mastodon/models/account'; -import type { DropdownMenu } from 'mastodon/models/dropdown_menu'; -import type { Relationship } from 'mastodon/models/relationship'; +import type { MenuItem } from 'mastodon/models/dropdown_menu'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION, @@ -204,24 +198,6 @@ const titleFromAccount = (account: Account) => { return `${prefix} (@${acct})`; }; -const messageForFollowButton = (relationship?: Relationship) => { - if (!relationship) return messages.follow; - - if ( - relationship.get('following') && - relationship.get('followed_by') && - isShowItem('relationships') - ) { - return messages.mutual; - } else if (relationship.get('following') || relationship.get('requested')) { - return messages.unfollow; - } else if (relationship.get('followed_by') && isShowItem('relationships')) { - return messages.followBack; - } else { - return messages.follow; - } -}; - const dateFormatOptions: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', @@ -249,20 +225,6 @@ export const AccountHeader: React.FC<{ ); const handleLinkClick = useLinks(); - const handleFollow = useCallback(() => { - if (!account) { - return; - } - - if (relationship?.following || relationship?.requested) { - dispatch( - openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), - ); - } else { - dispatch(followAccount(account.id)); - } - }, [dispatch, account, relationship]); - const handleBlock = useCallback(() => { if (!account) { return; @@ -446,23 +408,6 @@ export const AccountHeader: React.FC<{ ); }, [dispatch, account]); - const handleInteractionModal = useCallback(() => { - if (!account) { - return; - } - - dispatch( - openModal({ - modalType: 'INTERACTION', - modalProps: { - type: 'follow', - accountId: account.id, - url: account.uri, - }, - }), - ); - }, [dispatch, account]); - const handleOpenAvatar = useCallback( (e: React.MouseEvent) => { if (e.button !== 0 || e.ctrlKey || e.metaKey) { @@ -498,10 +443,6 @@ export const AccountHeader: React.FC<{ }); }, [account]); - const handleEditProfile = useCallback(() => { - window.open('/settings/profile', '_blank'); - }, []); - const handleMouseEnter = useCallback( ({ currentTarget }: React.MouseEvent) => { if (autoPlayGif) { @@ -537,7 +478,7 @@ export const AccountHeader: React.FC<{ const remoteDomain = isRemote ? account?.acct.split('@')[1] : null; const menu = useMemo(() => { - const arr: DropdownMenu = []; + const arr: MenuItem[] = []; if (!account) { return arr; @@ -626,9 +567,7 @@ export const AccountHeader: React.FC<{ arr.push({ text: intl.formatMessage( - account.getIn(['relationship', 'endorsed']) - ? messages.unendorse - : messages.endorse, + relationship.endorsed ? messages.unendorse : messages.endorse, ), action: handleEndorseToggle, }); @@ -778,9 +717,12 @@ export const AccountHeader: React.FC<{ return null; } - let actionBtn, bellBtn, lockedIcon, shareBtn; + let actionBtn: React.ReactNode, + bellBtn: React.ReactNode, + lockedIcon: React.ReactNode, + shareBtn: React.ReactNode; - const info = []; + const info: React.ReactNode[] = []; if (me !== account.id && relationship?.blocking) { info.push( @@ -848,43 +790,17 @@ export const AccountHeader: React.FC<{ ); } - if (me !== account.id) { - if (signedIn && !relationship) { - // Wait until the relationship is loaded - actionBtn = ( - - ); - } else if (!relationship?.blocking) { - actionBtn = ( -
    @@ -1141,6 +1059,9 @@ export const AccountHeader: React.FC<{ {!(hideTabs || hidden) && (
    + + + diff --git a/app/javascript/mastodon/features/account_timeline/index.jsx b/app/javascript/mastodon/features/account_timeline/index.jsx index 886191e668..a5223275b3 100644 --- a/app/javascript/mastodon/features/account_timeline/index.jsx +++ b/app/javascript/mastodon/features/account_timeline/index.jsx @@ -7,12 +7,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { TimelineHint } from 'mastodon/components/timeline_hint'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import { me } from 'mastodon/initial_state'; import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; import { getAccountHidden } from 'mastodon/selectors/accounts'; -import { useAppSelector } from 'mastodon/store'; import { lookupAccount, fetchAccount } from '../../actions/accounts'; import { fetchFeaturedTags } from '../../actions/featured_tags'; @@ -21,6 +19,7 @@ import { ColumnBackButton } from '../../components/column_back_button'; import { LoadingIndicator } from '../../components/loading_indicator'; import StatusList from '../../components/status_list'; import Column from '../ui/components/column'; +import { RemoteHint } from 'mastodon/components/remote_hint'; import { AccountHeader } from './components/account_header'; import { LimitedAccountHint } from './components/limited_account_hint'; @@ -47,11 +46,8 @@ const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = fa return { accountId, - remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])), - remoteUrl: state.getIn(['accounts', accountId, 'url']), isAccount: !!state.getIn(['accounts', accountId]), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList), - featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, 'items'], emptyList), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), suspended: state.getIn(['accounts', accountId, 'suspended'], false), @@ -60,24 +56,6 @@ const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = fa }; }; -const RemoteHint = ({ accountId, url }) => { - const acct = useAppSelector(state => state.accounts.get(accountId)?.acct); - const domain = acct ? acct.split('@')[1] : undefined; - - return ( - } - label={{domain} }} />} - /> - ); -}; - -RemoteHint.propTypes = { - url: PropTypes.string.isRequired, - accountId: PropTypes.string.isRequired, -}; - class AccountTimeline extends ImmutablePureComponent { static propTypes = { @@ -89,7 +67,6 @@ class AccountTimeline extends ImmutablePureComponent { accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, statusIds: ImmutablePropTypes.list, - featuredStatusIds: ImmutablePropTypes.list, isLoading: PropTypes.bool, hasMore: PropTypes.bool, withReplies: PropTypes.bool, @@ -97,8 +74,6 @@ class AccountTimeline extends ImmutablePureComponent { isAccount: PropTypes.bool, suspended: PropTypes.bool, hidden: PropTypes.bool, - remote: PropTypes.bool, - remoteUrl: PropTypes.string, multiColumn: PropTypes.bool, }; @@ -161,7 +136,7 @@ class AccountTimeline extends ImmutablePureComponent { }; render () { - const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props; + const { accountId, statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props; if (isLoading && statusIds.isEmpty()) { return ( @@ -191,8 +166,6 @@ class AccountTimeline extends ImmutablePureComponent { emptyMessage = ; } - const remoteMessage = remote ? : null; - return ( @@ -200,10 +173,9 @@ class AccountTimeline extends ImmutablePureComponent { } alwaysPrepend - append={remoteMessage} + append={} scrollKey='account_timeline' statusIds={forceEmptyState ? emptyList : statusIds} - featuredStatusIds={featuredStatusIds} isLoading={isLoading} hasMore={!forceEmptyState && hasMore} onLoadMore={this.handleLoadMore} diff --git a/app/javascript/mastodon/features/alt_text_modal/index.tsx b/app/javascript/mastodon/features/alt_text_modal/index.tsx index 8c5e552eb8..e2d05a99ca 100644 --- a/app/javascript/mastodon/features/alt_text_modal/index.tsx +++ b/app/javascript/mastodon/features/alt_text_modal/index.tsx @@ -2,7 +2,6 @@ import { useState, useCallback, useRef, - useEffect, useImperativeHandle, forwardRef, } from 'react'; @@ -13,6 +12,7 @@ import classNames from 'classnames'; import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; +import { useSpring, animated } from '@react-spring/web'; import Textarea from 'react-textarea-autosize'; import { length } from 'stringz'; // eslint-disable-next-line import/extensions @@ -31,7 +31,7 @@ import Audio from 'mastodon/features/audio'; import { CharacterCounter } from 'mastodon/features/compose/components/character_counter'; import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components'; import { Video, getPointerPosition } from 'mastodon/features/video'; -import { me } from 'mastodon/initial_state'; +import { me, reduceMotion } from 'mastodon/initial_state'; import type { MediaAttachment } from 'mastodon/models/media_attachment'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; import { assetHost } from 'mastodon/utils/config'; @@ -105,6 +105,17 @@ const Preview: React.FC<{ position: FocalPoint; onPositionChange: (arg0: FocalPoint) => void; }> = ({ mediaId, position, onPositionChange }) => { + const draggingRef = useRef(false); + const nodeRef = useRef(null); + + const [x, y] = position; + const style = useSpring({ + to: { + left: `${x * 100}%`, + top: `${y * 100}%`, + }, + immediate: reduceMotion || draggingRef.current, + }); const media = useAppSelector((state) => ( (state.compose as ImmutableMap).get( @@ -117,9 +128,6 @@ const Preview: React.FC<{ ); const [dragging, setDragging] = useState(false); - const [x, y] = position; - const nodeRef = useRef(null); - const draggingRef = useRef(false); const setRef = useCallback( (e: HTMLImageElement | HTMLVideoElement | null) => { @@ -134,36 +142,30 @@ const Preview: React.FC<{ return; } + const handleMouseMove = (e: MouseEvent) => { + const { x, y } = getPointerPosition(nodeRef.current, e); + draggingRef.current = true; // This will disable the animation for quicker feedback, only do this if the mouse actually moves + onPositionChange([x, y]); + }; + + const handleMouseUp = () => { + setDragging(false); + draggingRef.current = false; + document.removeEventListener('mouseup', handleMouseUp); + document.removeEventListener('mousemove', handleMouseMove); + }; + const { x, y } = getPointerPosition(nodeRef.current, e.nativeEvent); + setDragging(true); - draggingRef.current = true; onPositionChange([x, y]); + + document.addEventListener('mouseup', handleMouseUp); + document.addEventListener('mousemove', handleMouseMove); }, [setDragging, onPositionChange], ); - useEffect(() => { - const handleMouseUp = () => { - setDragging(false); - draggingRef.current = false; - }; - - const handleMouseMove = (e: MouseEvent) => { - if (draggingRef.current) { - const { x, y } = getPointerPosition(nodeRef.current, e); - onPositionChange([x, y]); - } - }; - - document.addEventListener('mouseup', handleMouseUp); - document.addEventListener('mousemove', handleMouseMove); - - return () => { - document.removeEventListener('mouseup', handleMouseUp); - document.removeEventListener('mousemove', handleMouseMove); - }; - }, [setDragging, onPositionChange]); - if (!media) { return null; } @@ -179,10 +181,7 @@ const Preview: React.FC<{ role='presentation' onMouseDown={handleMouseDown} /> -
    +
    ); } else if (media.get('type') === 'gifv') { @@ -194,10 +193,7 @@ const Preview: React.FC<{ alt='' onMouseDown={handleMouseDown} /> -
    +
    ); } else if (media.get('type') === 'video') { diff --git a/app/javascript/mastodon/features/antennas/index.tsx b/app/javascript/mastodon/features/antennas/index.tsx index 93ad4b6f48..c411cee735 100644 --- a/app/javascript/mastodon/features/antennas/index.tsx +++ b/app/javascript/mastodon/features/antennas/index.tsx @@ -13,9 +13,9 @@ import { fetchAntennas } from 'mastodon/actions/antennas'; import { openModal } from 'mastodon/actions/modal'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { getOrderedAntennas } from 'mastodon/selectors/antennas'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -96,12 +96,11 @@ const AntennaItem: React.FC<{ -
    diff --git a/app/javascript/mastodon/features/bookmark_categories/index.tsx b/app/javascript/mastodon/features/bookmark_categories/index.tsx index 68019b1921..92a2647989 100644 --- a/app/javascript/mastodon/features/bookmark_categories/index.tsx +++ b/app/javascript/mastodon/features/bookmark_categories/index.tsx @@ -14,9 +14,9 @@ import { fetchBookmarkCategories } from 'mastodon/actions/bookmark_categories'; import { openModal } from 'mastodon/actions/modal'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { getOrderedBookmarkCategories } from 'mastodon/selectors/bookmark_categories'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -76,12 +76,11 @@ const BookmarkCategoryItem: React.FC<{ {title} -
    diff --git a/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx b/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx deleted file mode 100644 index 6a3feb46a4..0000000000 --- a/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - - -import { Helmet } from 'react-helmet'; -import { withRouter } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - - -import { debounce } from 'lodash'; - -import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg'; -import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; -import EditIcon from '@/material-icons/400-24px/edit.svg?react'; -import { deleteBookmarkCategory, expandBookmarkCategoryStatuses, fetchBookmarkCategory, fetchBookmarkCategoryStatuses } from 'mastodon/actions/bookmark_categories'; -import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; -import { openModal } from 'mastodon/actions/modal'; -import Column from 'mastodon/components/column'; -import ColumnHeader from 'mastodon/components/column_header'; -import { Icon } from 'mastodon/components/icon'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import StatusList from 'mastodon/components/status_list'; -import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; -import { getBookmarkCategoryStatusList } from 'mastodon/selectors'; -import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - - -const messages = defineMessages({ - deleteMessage: { id: 'confirmations.delete_bookmark_category.message', defaultMessage: 'Are you sure you want to permanently delete this category?' }, - deleteConfirm: { id: 'confirmations.delete_bookmark_category.confirm', defaultMessage: 'Delete' }, - heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, -}); - -const mapStateToProps = (state, { params }) => ({ - bookmarkCategory: state.getIn(['bookmark_categories', params.id]), - statusIds: getBookmarkCategoryStatusList(state, params.id), - isLoading: state.getIn(['status_lists', 'bookmark_category_statuses', params.id, 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'bookmark_category_statuses', params.id, 'next']), -}); - -class BookmarkCategoryStatuses extends ImmutablePureComponent { - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - bookmarkCategory: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - ...WithRouterPropTypes, - }; - - UNSAFE_componentWillMount () { - this.props.dispatch(fetchBookmarkCategory(this.props.params.id)); - this.props.dispatch(fetchBookmarkCategoryStatuses(this.props.params.id)); - } - - UNSAFE_componentWillReceiveProps (nextProps) { - const { dispatch } = this.props; - const { id } = nextProps.params; - - if (id !== this.props.params.id) { - dispatch(fetchBookmarkCategory(id)); - dispatch(fetchBookmarkCategoryStatuses(id)); - } - } - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('BOOKMARKS_EX', { id: this.props.params.id })); - this.props.history.push('/'); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - handleEditClick = () => { - this.props.history.push(`/bookmark_categories/${this.props.params.id}/edit`); - }; - - handleDeleteClick = () => { - const { dispatch, columnId, intl } = this.props; - const { id } = this.props.params; - - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => { - dispatch(deleteBookmarkCategory(id)); - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - this.props.history.push('/bookmark_categories'); - } - }, - }, - })); - }; - - setRef = c => { - this.column = c; - }; - - handleLoadMore = debounce(() => { - this.props.dispatch(expandBookmarkCategoryStatuses(this.props.params.id)); - }, 300, { leading: true }); - - render () { - const { intl, bookmarkCategory, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; - const pinned = !!columnId; - - if (typeof bookmarkCategory === 'undefined') { - return ( - -
    - -
    -
    - ); - } else if (bookmarkCategory === false) { - return ( - - ); - } - - const emptyMessage = ; - - return ( - - -
    -
    - - - -
    -
    -
    - - - - - {intl.formatMessage(messages.heading)} - - -
    - ); - } - -} - -export default withRouter(connect(mapStateToProps)(injectIntl(BookmarkCategoryStatuses))); diff --git a/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx b/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx new file mode 100644 index 0000000000..7facdcc449 --- /dev/null +++ b/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx @@ -0,0 +1,121 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; +import { useParams } from 'react-router'; + +import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; +import { + expandBookmarkCategoryStatuses, + fetchBookmarkCategory, + fetchBookmarkCategoryStatuses, +} from 'mastodon/actions/bookmark_categories'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getSubStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const BookmarkCategoryStatuses: React.FC<{ + columnId: string; + multiColumn: boolean; +}> = ({ columnId, multiColumn }) => { + const dispatch = useAppDispatch(); + const { id } = useParams<{ id: string }>(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getSubStatusList(state, 'bookmark_category', id), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn( + ['bookmark_category_statuses', id, 'isLoading'], + true, + ) as boolean, + ); + const hasMore = useAppSelector( + (state) => + !!state.status_lists.getIn(['bookmark_category_statuses', id, 'next']), + ); + const bookmarkCategory = useAppSelector((state) => + state.bookmark_categories.get(id), + ); + + useEffect(() => { + dispatch(fetchBookmarkCategory(id)); + dispatch(fetchBookmarkCategoryStatuses(id)); + }, [dispatch, id]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('BOOKMARKS_EX', { id })); + } + }, [dispatch, columnId, id]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandBookmarkCategoryStatuses(id)); + }, [dispatch, id]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {bookmarkCategory?.get('title')} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default BookmarkCategoryStatuses; diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx deleted file mode 100644 index 284342d4ee..0000000000 --- a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx +++ /dev/null @@ -1,117 +0,0 @@ -// Kmyblue tracking marker: copied bookmark_category_statuses, circle_statuses - -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -import { Helmet } from 'react-helmet'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - -import { debounce } from 'lodash'; - -import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; -import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'mastodon/actions/bookmarks'; -import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; -import Column from 'mastodon/components/column'; -import ColumnHeader from 'mastodon/components/column_header'; -import StatusList from 'mastodon/components/status_list'; -import { getStatusList } from 'mastodon/selectors'; - -const messages = defineMessages({ - heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, -}); - -const mapStateToProps = state => ({ - statusIds: getStatusList(state, 'bookmarks'), - isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']), -}); - -class Bookmarks extends ImmutablePureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - }; - - UNSAFE_componentWillMount () { - this.props.dispatch(fetchBookmarkedStatuses()); - } - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('BOOKMARKS', {})); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - setRef = c => { - this.column = c; - }; - - handleLoadMore = debounce(() => { - this.props.dispatch(expandBookmarkedStatuses()); - }, 300, { leading: true }); - - render () { - const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; - const pinned = !!columnId; - - const emptyMessage = ; - - return ( - - - - - - - {intl.formatMessage(messages.heading)} - - - - ); - } - -} - -export default connect(mapStateToProps)(injectIntl(Bookmarks)); diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.tsx b/app/javascript/mastodon/features/bookmarked_statuses/index.tsx new file mode 100644 index 0000000000..5d4574b05b --- /dev/null +++ b/app/javascript/mastodon/features/bookmarked_statuses/index.tsx @@ -0,0 +1,116 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; +import { + fetchBookmarkedStatuses, + expandBookmarkedStatuses, +} from 'mastodon/actions/bookmarks'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, +}); + +const Bookmarks: React.FC<{ + columnId: string; + multiColumn: boolean; +}> = ({ columnId, multiColumn }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getStatusList(state, 'bookmarks'), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn(['bookmarks', 'isLoading'], true) as boolean, + ); + const hasMore = useAppSelector( + (state) => !!state.status_lists.getIn(['bookmarks', 'next']), + ); + + useEffect(() => { + dispatch(fetchBookmarkedStatuses()); + }, [dispatch]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('BOOKMARKS', {})); + } + }, [dispatch, columnId]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandBookmarkedStatuses()); + }, [dispatch]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {intl.formatMessage(messages.heading)} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default Bookmarks; diff --git a/app/javascript/mastodon/features/circle_statuses/index.jsx b/app/javascript/mastodon/features/circle_statuses/index.jsx deleted file mode 100644 index 1c0ee455e8..0000000000 --- a/app/javascript/mastodon/features/circle_statuses/index.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - - -import { Helmet } from 'react-helmet'; -import { withRouter } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - - -import { debounce } from 'lodash'; - -import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react'; -import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; -import EditIcon from '@/material-icons/400-24px/edit.svg?react'; -import { deleteCircle, expandCircleStatuses, fetchCircle, fetchCircleStatuses } from 'mastodon/actions/circles'; -import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; -import { openModal } from 'mastodon/actions/modal'; -import Column from 'mastodon/components/column'; -import ColumnHeader from 'mastodon/components/column_header'; -import { Icon } from 'mastodon/components/icon'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import StatusList from 'mastodon/components/status_list'; -import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; -import { getCircleStatusList } from 'mastodon/selectors'; -import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - - -const messages = defineMessages({ - deleteMessage: { id: 'confirmations.delete_circle.message', defaultMessage: 'Are you sure you want to permanently delete this circle?' }, - deleteConfirm: { id: 'confirmations.delete_circle.confirm', defaultMessage: 'Delete' }, - heading: { id: 'column.circles', defaultMessage: 'Circles' }, -}); - -const mapStateToProps = (state, { params }) => ({ - circle: state.getIn(['circles', params.id]), - statusIds: getCircleStatusList(state, params.id), - isLoading: state.getIn(['status_lists', 'circle_statuses', params.id, 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'circle_statuses', params.id, 'next']), -}); - -class CircleStatuses extends ImmutablePureComponent { - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - circle: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - ...WithRouterPropTypes, - }; - - UNSAFE_componentWillMount () { - this.props.dispatch(fetchCircle(this.props.params.id)); - this.props.dispatch(fetchCircleStatuses(this.props.params.id)); - } - - UNSAFE_componentWillReceiveProps (nextProps) { - const { dispatch } = this.props; - const { id } = nextProps.params; - - if (id !== this.props.params.id) { - dispatch(fetchCircle(id)); - dispatch(fetchCircleStatuses(id)); - } - } - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('CIRCLE_STATUSES', { id: this.props.params.id })); - this.props.history.push('/'); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - handleEditClick = () => { - this.props.history.push(`/circles/${this.props.params.id}/edit`); - }; - - handleDeleteClick = () => { - const { dispatch, columnId, intl } = this.props; - const { id } = this.props.params; - - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => { - dispatch(deleteCircle(id)); - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - this.props.history.push('/circles'); - } - }, - }, - })); - }; - - setRef = c => { - this.column = c; - }; - - handleLoadMore = debounce(() => { - this.props.dispatch(expandCircleStatuses(this.props.params.id)); - }, 300, { leading: true }); - - render () { - const { intl, circle, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; - const pinned = !!columnId; - - if (typeof circle === 'undefined') { - return ( - -
    - -
    -
    - ); - } else if (circle === false) { - return ( - - ); - } - - const emptyMessage = ; - - return ( - - -
    -
    - - - -
    -
    -
    - - - - - {intl.formatMessage(messages.heading)} - - -
    - ); - } - -} - -export default withRouter(connect(mapStateToProps)(injectIntl(CircleStatuses))); diff --git a/app/javascript/mastodon/features/circle_statuses/index.tsx b/app/javascript/mastodon/features/circle_statuses/index.tsx new file mode 100644 index 0000000000..2f962a5f13 --- /dev/null +++ b/app/javascript/mastodon/features/circle_statuses/index.tsx @@ -0,0 +1,118 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; +import { useParams } from 'react-router'; + +import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react'; +import { + expandCircleStatuses, + fetchCircle, + fetchCircleStatuses, +} from 'mastodon/actions/circles'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getSubStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const CircleStatuses: React.FC<{ + columnId: string; + multiColumn: boolean; +}> = ({ columnId, multiColumn }) => { + const dispatch = useAppDispatch(); + const { id } = useParams<{ id: string }>(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getSubStatusList(state, 'circle', id), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn( + ['circle_statuses', id, 'isLoading'], + true, + ) as boolean, + ); + const hasMore = useAppSelector( + (state) => !!state.status_lists.getIn(['circle_statuses', id, 'next']), + ); + const circle = useAppSelector((state) => state.circles.get(id)); + + useEffect(() => { + dispatch(fetchCircle(id)); + dispatch(fetchCircleStatuses(id)); + }, [dispatch, id]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('CIRCLE_STATUSES', { id })); + } + }, [dispatch, columnId, id]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandCircleStatuses(id)); + }, [dispatch, id]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {circle?.get('title')} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default CircleStatuses; diff --git a/app/javascript/mastodon/features/circles/index.tsx b/app/javascript/mastodon/features/circles/index.tsx index e1d02e1633..38291febc7 100644 --- a/app/javascript/mastodon/features/circles/index.tsx +++ b/app/javascript/mastodon/features/circles/index.tsx @@ -13,9 +13,9 @@ import { fetchCircles } from 'mastodon/actions/circles'; import { openModal } from 'mastodon/actions/modal'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { getOrderedCircles } from 'mastodon/selectors/circles'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -60,12 +60,11 @@ const CircleItem: React.FC<{ {title} -
    diff --git a/app/javascript/mastodon/features/compose/components/action_bar.jsx b/app/javascript/mastodon/features/compose/components/action_bar.tsx similarity index 50% rename from app/javascript/mastodon/features/compose/components/action_bar.jsx rename to app/javascript/mastodon/features/compose/components/action_bar.tsx index 5abc93d877..4a92485209 100644 --- a/app/javascript/mastodon/features/compose/components/action_bar.jsx +++ b/app/javascript/mastodon/features/compose/components/action_bar.tsx @@ -2,67 +2,94 @@ import { useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; - import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { openModal } from 'mastodon/actions/modal'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; +import { useAppDispatch } from 'mastodon/store'; const messages = defineMessages({ edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' }, - preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - reaction_deck: { id: 'navigation_bar.reaction_deck', defaultMessage: 'Reaction deck' }, - follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, + preferences: { + id: 'navigation_bar.preferences', + defaultMessage: 'Preferences', + }, + reaction_deck: { + id: 'navigation_bar.reaction_deck', + defaultMessage: 'Reaction deck', + }, + follow_requests: { + id: 'navigation_bar.follow_requests', + defaultMessage: 'Follow requests', + }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' }, - emoji_reactions: { id: 'navigation_bar.emoji_reactions', defaultMessage: 'Stamps' }, + emoji_reactions: { + id: 'navigation_bar.emoji_reactions', + defaultMessage: 'Stamps', + }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, - followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' }, + followed_tags: { + id: 'navigation_bar.followed_tags', + defaultMessage: 'Followed hashtags', + }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, - domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' }, + domain_blocks: { + id: 'navigation_bar.domain_blocks', + defaultMessage: 'Blocked domains', + }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, }); -export const ActionBar = () => { - const dispatch = useDispatch(); +export const ActionBar: React.FC = () => { + const dispatch = useAppDispatch(); const intl = useIntl(); const menu = useMemo(() => { const handleLogoutClick = () => { - dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' })); + dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} })); }; - return ([ - { text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }, - { text: intl.formatMessage(messages.preferences), href: '/settings/preferences' }, + return [ + { + text: intl.formatMessage(messages.edit_profile), + href: '/settings/profile', + }, + { + text: intl.formatMessage(messages.preferences), + href: '/settings/preferences', + }, { text: intl.formatMessage(messages.pins), to: '/pinned' }, null, - { text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' }, + { + text: intl.formatMessage(messages.follow_requests), + to: '/follow_requests', + }, { text: intl.formatMessage(messages.favourites), to: '/favourites' }, { text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' }, - { text: intl.formatMessage(messages.emoji_reactions), to: '/emoji_reactions' }, + { + text: intl.formatMessage(messages.emoji_reactions), + to: '/emoji_reactions', + }, { text: intl.formatMessage(messages.lists), to: '/lists' }, - { text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' }, + { + text: intl.formatMessage(messages.followed_tags), + to: '/followed_tags', + }, null, { text: intl.formatMessage(messages.mutes), to: '/mutes' }, { text: intl.formatMessage(messages.blocks), to: '/blocks' }, - { text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }, + { + text: intl.formatMessage(messages.domain_blocks), + to: '/domain_blocks', + }, { text: intl.formatMessage(messages.filters), href: '/filters' }, null, { text: intl.formatMessage(messages.logout), action: handleLogoutClick }, - ]); + ]; }, [intl, dispatch]); - return ( - - ); + return ; }; diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx index 0d154db1e1..c27cd3727f 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx @@ -24,7 +24,7 @@ import AvatarComposite from 'mastodon/components/avatar_composite'; import { IconButton } from 'mastodon/components/icon_button'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import StatusContent from 'mastodon/components/status_content'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { autoPlayGif } from 'mastodon/initial_state'; import { makeGetStatus } from 'mastodon/selectors'; @@ -205,7 +205,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
    - ({ - statusIds: state.getIn(['status_lists', 'emoji_reactions', 'items']), - isLoading: state.getIn(['status_lists', 'emoji_reactions', 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'emoji_reactions', 'next']), -}); - -class EmojiReactions extends ImmutablePureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - }; - - componentWillMount () { - this.props.dispatch(fetchEmojiReactedStatuses()); - } - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('EMOJI_REACTIONS', {})); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - setRef = c => { - this.column = c; - }; - - handleLoadMore = debounce(() => { - this.props.dispatch(expandEmojiReactedStatuses()); - }, 300, { leading: true }); - - render () { - const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; - const pinned = !!columnId; - - const emptyMessage = ; - - return ( - - - - - - - {intl.formatMessage(messages.heading)} - - - - ); - } - -} - -export default connect(mapStateToProps)(injectIntl(EmojiReactions)); diff --git a/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx b/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx new file mode 100644 index 0000000000..58186cbbd7 --- /dev/null +++ b/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx @@ -0,0 +1,118 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { + fetchEmojiReactedStatuses, + expandEmojiReactedStatuses, +} from 'mastodon/actions/emoji_reactions'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + heading: { id: 'column.emoji_reactions', defaultMessage: 'Stamps' }, +}); + +const Favourites: React.FC<{ columnId: string; multiColumn: boolean }> = ({ + columnId, + multiColumn, +}) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getStatusList(state, 'emoji_reactions'), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn( + ['emoji_reactions', 'isLoading'], + true, + ) as boolean, + ); + const hasMore = useAppSelector( + (state) => !!state.status_lists.getIn(['emoji_reactions', 'next']), + ); + + useEffect(() => { + dispatch(fetchEmojiReactedStatuses()); + }, [dispatch]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('EMOJI_REACTIONS', {})); + } + }, [dispatch, columnId]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandEmojiReactedStatuses()); + }, [dispatch]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {intl.formatMessage(messages.heading)} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default Favourites; diff --git a/app/javascript/mastodon/features/explore/components/card.jsx b/app/javascript/mastodon/features/explore/components/card.jsx index 15470ec24c..9617781b53 100644 --- a/app/javascript/mastodon/features/explore/components/card.jsx +++ b/app/javascript/mastodon/features/explore/components/card.jsx @@ -25,7 +25,7 @@ export const Card = ({ id, source }) => { const dispatch = useDispatch(); const handleDismiss = useCallback(() => { - dispatch(dismissSuggestion(id)); + dispatch(dismissSuggestion({ accountId: id })); }, [id, dispatch]); let label; diff --git a/app/javascript/mastodon/features/favourited_statuses/index.tsx b/app/javascript/mastodon/features/favourited_statuses/index.tsx new file mode 100644 index 0000000000..908a8ae4a1 --- /dev/null +++ b/app/javascript/mastodon/features/favourited_statuses/index.tsx @@ -0,0 +1,116 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { + fetchFavouritedStatuses, + expandFavouritedStatuses, +} from 'mastodon/actions/favourites'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + heading: { id: 'column.favourites', defaultMessage: 'Favorites' }, +}); + +const Favourites: React.FC<{ columnId: string; multiColumn: boolean }> = ({ + columnId, + multiColumn, +}) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getStatusList(state, 'favourites'), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn(['favourites', 'isLoading'], true) as boolean, + ); + const hasMore = useAppSelector( + (state) => !!state.status_lists.getIn(['favourites', 'next']), + ); + + useEffect(() => { + dispatch(fetchFavouritedStatuses()); + }, [dispatch]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('FAVOURITES', {})); + } + }, [dispatch, columnId]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandFavouritedStatuses()); + }, [dispatch]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {intl.formatMessage(messages.heading)} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default Favourites; diff --git a/app/javascript/mastodon/features/followed_tags/index.jsx b/app/javascript/mastodon/features/followed_tags/index.jsx deleted file mode 100644 index 21248e6de9..0000000000 --- a/app/javascript/mastodon/features/followed_tags/index.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -import { Helmet } from 'react-helmet'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - -import { debounce } from 'lodash'; - -import TagIcon from '@/material-icons/400-24px/tag.svg?react'; -import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags'; -import ColumnHeader from 'mastodon/components/column_header'; -import { Hashtag } from 'mastodon/components/hashtag'; -import ScrollableList from 'mastodon/components/scrollable_list'; -import Column from 'mastodon/features/ui/components/column'; - -const messages = defineMessages({ - heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' }, -}); - -const mapStateToProps = state => ({ - hashtags: state.getIn(['followed_tags', 'items']), - isLoading: state.getIn(['followed_tags', 'isLoading'], true), - hasMore: !!state.getIn(['followed_tags', 'next']), -}); - -class FollowedTags extends ImmutablePureComponent { - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - hashtags: ImmutablePropTypes.list, - isLoading: PropTypes.bool, - hasMore: PropTypes.bool, - multiColumn: PropTypes.bool, - }; - - componentDidMount() { - this.props.dispatch(fetchFollowedHashtags()); - } - - handleLoadMore = debounce(() => { - this.props.dispatch(expandFollowedHashtags()); - }, 300, { leading: true }); - - render () { - const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props; - - const emptyMessage = ; - - return ( - - - - - {hashtags.map((hashtag) => ( - day.get('uses')).toArray()} - /> - ))} - - - - - - - ); - } - -} - -export default connect(mapStateToProps)(injectIntl(FollowedTags)); diff --git a/app/javascript/mastodon/features/followed_tags/index.tsx b/app/javascript/mastodon/features/followed_tags/index.tsx new file mode 100644 index 0000000000..21d63a6fec --- /dev/null +++ b/app/javascript/mastodon/features/followed_tags/index.tsx @@ -0,0 +1,161 @@ +import { useEffect, useState, useCallback, useRef } from 'react'; + +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import { isFulfilled } from '@reduxjs/toolkit'; + +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; +import { unfollowHashtag } from 'mastodon/actions/tags_typed'; +import { apiGetFollowedTags } from 'mastodon/api/tags'; +import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; +import { Button } from 'mastodon/components/button'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import { Hashtag } from 'mastodon/components/hashtag'; +import ScrollableList from 'mastodon/components/scrollable_list'; +import { useAppDispatch } from 'mastodon/store'; + +const messages = defineMessages({ + heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' }, +}); + +const FollowedTag: React.FC<{ + tag: ApiHashtagJSON; + onUnfollow: (arg0: string) => void; +}> = ({ tag, onUnfollow }) => { + const dispatch = useAppDispatch(); + const tagId = tag.name; + + const handleClick = useCallback(() => { + void dispatch(unfollowHashtag({ tagId })).then((result) => { + if (isFulfilled(result)) { + onUnfollow(tagId); + } + + return ''; + }); + }, [dispatch, onUnfollow, tagId]); + + const people = + parseInt(tag.history[0].accounts) + + parseInt(tag.history[1]?.accounts ?? ''); + + return ( + + + + ); +}; + +const FollowedTags: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { + const intl = useIntl(); + const [tags, setTags] = useState([]); + const [loading, setLoading] = useState(false); + const [next, setNext] = useState(); + const hasMore = !!next; + const columnRef = useRef(null); + + useEffect(() => { + setLoading(true); + + void apiGetFollowedTags() + .then(({ tags, links }) => { + const next = links.refs.find((link) => link.rel === 'next'); + + setTags(tags); + setLoading(false); + setNext(next?.uri); + + return ''; + }) + .catch(() => { + setLoading(false); + }); + }, [setTags, setLoading, setNext]); + + const handleLoadMore = useCallback(() => { + setLoading(true); + + void apiGetFollowedTags(next) + .then(({ tags, links }) => { + const next = links.refs.find((link) => link.rel === 'next'); + + setLoading(false); + setTags((previousTags) => [...previousTags, ...tags]); + setNext(next?.uri); + + return ''; + }) + .catch(() => { + setLoading(false); + }); + }, [setTags, setLoading, setNext, next]); + + const handleUnfollow = useCallback( + (tagId: string) => { + setTags((tags) => tags.filter((tag) => tag.name !== tagId)); + }, + [setTags], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const emptyMessage = ( + + ); + + return ( + + + + + {tags.map((tag) => ( + + ))} + + + + {intl.formatMessage(messages.heading)} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default FollowedTags; diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx index 7372fe5284..b7c1ea02d9 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx +++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx @@ -12,8 +12,8 @@ import { } from 'mastodon/actions/tags_typed'; import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; import { Button } from 'mastodon/components/button'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { ShortNumber } from 'mastodon/components/short_number'; -import DropdownMenu from 'mastodon/containers/dropdown_menu_container'; import { useIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_TAXONOMIES } from 'mastodon/permissions'; import { useAppDispatch } from 'mastodon/store'; @@ -153,13 +153,11 @@ export const HashtagHeader: React.FC<{
    {menu.length > 0 && ( - )} diff --git a/app/javascript/mastodon/features/lists/index.tsx b/app/javascript/mastodon/features/lists/index.tsx index ad172908bd..e8f55bc0ad 100644 --- a/app/javascript/mastodon/features/lists/index.tsx +++ b/app/javascript/mastodon/features/lists/index.tsx @@ -13,9 +13,9 @@ import { fetchLists } from 'mastodon/actions/lists'; import { openModal } from 'mastodon/actions/modal'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { getOrderedLists } from 'mastodon/selectors/lists'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -72,12 +72,11 @@ const ListItem: React.FC<{ -
    diff --git a/app/javascript/mastodon/features/notifications/components/notification_request.jsx b/app/javascript/mastodon/features/notifications/components/notification_request.jsx index 626929ae50..381bb1153f 100644 --- a/app/javascript/mastodon/features/notifications/components/notification_request.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification_request.jsx @@ -17,7 +17,7 @@ import { initReport } from 'mastodon/actions/reports'; import { Avatar } from 'mastodon/components/avatar'; import { CheckBox } from 'mastodon/components/check_box'; import { IconButton } from 'mastodon/components/icon_button'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { makeGetAccount } from 'mastodon/selectors'; import { toCappedNumber } from 'mastodon/utils/numbers'; @@ -105,11 +105,10 @@ export const NotificationRequest = ({ id, accountId, notificationsCount, checked
    -
    diff --git a/app/javascript/mastodon/features/notifications/requests.jsx b/app/javascript/mastodon/features/notifications/requests.jsx index ccaed312b4..b2bdd0ec77 100644 --- a/app/javascript/mastodon/features/notifications/requests.jsx +++ b/app/javascript/mastodon/features/notifications/requests.jsx @@ -23,7 +23,7 @@ import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { NotificationRequest } from './components/notification_request'; import { PolicyControls } from './components/policy_controls'; @@ -126,7 +126,7 @@ const SelectRow = ({selectAllChecked, toggleSelectAll, selectedItems, selectionM
    0 && !selectAllChecked} onChange={handleSelectAll} />
    - - +
    ); diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx index 406847382a..e0a68ad263 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.tsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.tsx @@ -14,7 +14,7 @@ import { Link } from 'react-router-dom'; import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import { AnimatedNumber } from 'mastodon/components/animated_number'; import { ContentWarning } from 'mastodon/components/content_warning'; -import EditedTimestamp from 'mastodon/components/edited_timestamp'; +import { EditedTimestamp } from 'mastodon/components/edited_timestamp'; import { FilterWarning } from 'mastodon/components/filter_warning'; import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; import type { StatusLike } from 'mastodon/components/hashtag_bar'; diff --git a/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx b/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx new file mode 100644 index 0000000000..6707b24672 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx @@ -0,0 +1,157 @@ +import { useEffect, useRef, useState, useCallback, useMemo } from 'react'; + +import { useIntl, defineMessages } from 'react-intl'; + +import { useLocation } from 'react-router-dom'; + +import Overlay from 'react-overlays/Overlay'; +import type { + OffsetValue, + UsePopperOptions, +} from 'react-overlays/esm/usePopper'; + +import { DropdownMenu } from 'mastodon/components/dropdown_menu'; +import { useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + browseHashtag: { + id: 'hashtag.browse', + defaultMessage: 'Browse posts in #{hashtag}', + }, + browseHashtagFromAccount: { + id: 'hashtag.browse_from_account', + defaultMessage: 'Browse posts from @{name} in #{hashtag}', + }, + muteHashtag: { id: 'hashtag.mute', defaultMessage: 'Mute #{hashtag}' }, +}); + +const offset = [5, 5] as OffsetValue; +const popperConfig = { strategy: 'fixed' } as UsePopperOptions; + +const isHashtagLink = ( + element: HTMLAnchorElement | null, +): element is HTMLAnchorElement => { + if (!element) { + return false; + } + + return element.matches('[data-menu-hashtag]'); +}; + +interface TargetParams { + hashtag?: string; + accountId?: string; +} + +export const HashtagMenuController: React.FC = () => { + const intl = useIntl(); + const [open, setOpen] = useState(false); + const [{ accountId, hashtag }, setTargetParams] = useState({}); + const targetRef = useRef(null); + const location = useLocation(); + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + + useEffect(() => { + setOpen(false); + targetRef.current = null; + }, [setOpen, location]); + + useEffect(() => { + const handleClick = (e: MouseEvent) => { + const target = (e.target as HTMLElement).closest('a'); + + if (e.button !== 0 || e.ctrlKey || e.metaKey) { + return; + } + + if (!isHashtagLink(target)) { + return; + } + + const hashtag = target.text.replace(/^#/, ''); + const accountId = target.getAttribute('data-menu-hashtag'); + + if (!hashtag || !accountId) { + return; + } + + e.preventDefault(); + e.stopPropagation(); + targetRef.current = target; + setOpen(true); + setTargetParams({ hashtag, accountId }); + }; + + document.addEventListener('click', handleClick, { capture: true }); + + return () => { + document.removeEventListener('click', handleClick); + }; + }, [setTargetParams, setOpen]); + + const handleClose = useCallback(() => { + setOpen(false); + targetRef.current = null; + }, [setOpen]); + + const menu = useMemo( + () => [ + { + text: intl.formatMessage(messages.browseHashtag, { + hashtag, + }), + to: `/tags/${hashtag}`, + }, + { + text: intl.formatMessage(messages.browseHashtagFromAccount, { + hashtag, + name: account?.username, + }), + to: `/@${account?.acct}/tagged/${hashtag}`, + }, + null, + { + text: intl.formatMessage(messages.muteHashtag, { + hashtag, + }), + href: '/filters', + dangerous: true, + }, + ], + [intl, hashtag, account], + ); + + if (!open) { + return null; + } + + return ( + + {({ props, arrowProps, placement }) => ( +
    +
    +
    + + +
    +
    + )} + + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/link_footer.tsx b/app/javascript/mastodon/features/ui/components/link_footer.tsx index a70111fce6..cbfb6a3114 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.tsx +++ b/app/javascript/mastodon/features/ui/components/link_footer.tsx @@ -1,63 +1,101 @@ import { FormattedMessage } from 'react-intl'; + import { Link } from 'react-router-dom'; import { domain, + version, source_url, statusPageUrl, + profile_directory as canProfileDirectory, termsOfServiceEnabled, } from 'mastodon/initial_state'; -const DividingCircle: React.FC = () => {' · '}; +const DividingCircle: React.FC = () => {' · '}; -export const LinkFooter: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { +export const LinkFooter: React.FC<{ + multiColumn: boolean; +}> = ({ multiColumn }) => { return ( -
    +

    {domain}:{' '} - {statusPageUrl && ( <> - + )} - - - - - - - {termsOfServiceEnabled && ( + {canProfileDirectory && ( <> - - + + )} - - - + + - - - - - + {termsOfServiceEnabled && ( + <> + + + + + + )}

    - - Made with ❤️ - + Mastodon:{' '} + + + + + + + + + + + + + + + + + v{version}

    -
    +
    ); }; diff --git a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx index b7a57a4d9a..74a8fdb841 100644 --- a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx +++ b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx @@ -22,7 +22,7 @@ const SignInBanner = () => { if (sso_redirect) { return (
    -

    +

    @@ -45,7 +45,7 @@ const SignInBanner = () => { return (
    -

    +

    {signupButton} diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 883b863be0..7d190b3ce7 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -31,6 +31,7 @@ import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding import BundleColumnError from './components/bundle_column_error'; import Header from './components/header'; import { UploadArea } from './components/upload_area'; +import { HashtagMenuController } from './components/hashtag_menu_controller'; import ColumnsAreaContainer from './containers/columns_area_container'; import LoadingBarContainer from './containers/loading_bar_container'; import ModalContainer from './containers/modal_container'; @@ -91,6 +92,7 @@ import { BookmarkCategoryEdit, ReactionDeck, TermsOfService, + AccountFeatured, } from './util/async-components'; import { ColumnsContextProvider } from './util/columns_context'; import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; @@ -272,6 +274,7 @@ class SwitchingColumnsArea extends PureComponent { + @@ -658,6 +661,7 @@ class UI extends PureComponent { {layout !== 'mobile' && } {!disableHoverCards && } + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index d8a1641c07..21dc902ae5 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -82,6 +82,10 @@ export function AccountGallery () { return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery'); } +export function AccountFeatured() { + return import(/* webpackChunkName: "features/account_featured" */'../../account_featured'); +} + export function Followers () { return import(/* webpackChunkName: "features/followers" */'../../followers'); } diff --git a/app/javascript/mastodon/hooks/useAccountId.ts b/app/javascript/mastodon/hooks/useAccountId.ts new file mode 100644 index 0000000000..1cc819ca59 --- /dev/null +++ b/app/javascript/mastodon/hooks/useAccountId.ts @@ -0,0 +1,37 @@ +import { useEffect } from 'react'; + +import { useParams } from 'react-router'; + +import { fetchAccount, lookupAccount } from 'mastodon/actions/accounts'; +import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +interface Params { + acct?: string; + id?: string; +} + +export function useAccountId() { + const { acct, id } = useParams(); + const accountId = useAppSelector( + (state) => + id ?? + (state.accounts_map.get(normalizeForLookup(acct)) as string | undefined), + ); + + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + const isAccount = !!account; + + const dispatch = useAppDispatch(); + useEffect(() => { + if (!accountId) { + dispatch(lookupAccount(acct)); + } else if (!isAccount) { + dispatch(fetchAccount(accountId)); + } + }, [dispatch, accountId, acct, isAccount]); + + return accountId; +} diff --git a/app/javascript/mastodon/hooks/useAccountVisibility.ts b/app/javascript/mastodon/hooks/useAccountVisibility.ts new file mode 100644 index 0000000000..55651af5a0 --- /dev/null +++ b/app/javascript/mastodon/hooks/useAccountVisibility.ts @@ -0,0 +1,20 @@ +import { getAccountHidden } from 'mastodon/selectors/accounts'; +import { useAppSelector } from 'mastodon/store'; + +export function useAccountVisibility(accountId?: string) { + const blockedBy = useAppSelector( + (state) => !!state.relationships.getIn([accountId, 'blocked_by'], false), + ); + const suspended = useAppSelector( + (state) => !!state.accounts.getIn([accountId, 'suspended'], false), + ); + const hidden = useAppSelector((state) => + accountId ? Boolean(getAccountHidden(state, accountId)) : false, + ); + + return { + blockedBy, + suspended, + hidden, + }; +} diff --git a/app/javascript/mastodon/locales/an.json b/app/javascript/mastodon/locales/an.json index 605c86f73e..49b6f41eac 100644 --- a/app/javascript/mastodon/locales/an.json +++ b/app/javascript/mastodon/locales/an.json @@ -25,7 +25,6 @@ "account.endorse": "Amostrar en perfil", "account.featured_tags.last_status_at": "Zaguera publicación lo {date}", "account.featured_tags.last_status_never": "Sin publicacions", - "account.featured_tags.title": "Etiquetas destacadas de {name}", "account.follow": "Seguir", "account.followers": "Seguidores", "account.followers.empty": "Encara no sigue dengún a este usuario.", diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index fc8e559dfc..326dd8fbc5 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -29,7 +29,6 @@ "account.endorse": "أوصِ به على صفحتك الشخصية", "account.featured_tags.last_status_at": "آخر منشور في {date}", "account.featured_tags.last_status_never": "لا توجد رسائل", - "account.featured_tags.title": "وسوم {name} المميَّزة", "account.follow": "متابعة", "account.follow_back": "تابعه بالمثل", "account.followers": "مُتابِعون", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index d43a0276dc..5edce9a4d8 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -27,7 +27,6 @@ "account.enable_notifications": "Avisame cuando @{name} espublice artículos", "account.endorse": "Destacar nel perfil", "account.featured_tags.last_status_never": "Nun hai nenguna publicación", - "account.featured_tags.title": "Etiquetes destacaes de: {name}", "account.follow": "Siguir", "account.follow_back": "Siguir tamién", "account.followers": "Siguidores", diff --git a/app/javascript/mastodon/locales/az.json b/app/javascript/mastodon/locales/az.json index 6a52c706b4..550312f31d 100644 --- a/app/javascript/mastodon/locales/az.json +++ b/app/javascript/mastodon/locales/az.json @@ -29,7 +29,6 @@ "account.endorse": "Profildə seçilmişlərə əlavə et", "account.featured_tags.last_status_at": "Son paylaşım {date} tarixində olub", "account.featured_tags.last_status_never": "Paylaşım yoxdur", - "account.featured_tags.title": "{name} istifadəçisinin seçilmiş heşteqləri", "account.follow": "İzlə", "account.follow_back": "Sən də izlə", "account.followers": "İzləyicilər", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 6c6e10270f..9011fdfd63 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -29,7 +29,6 @@ "account.endorse": "Паказваць у профілі", "account.featured_tags.last_status_at": "Апошні допіс ад {date}", "account.featured_tags.last_status_never": "Няма допісаў", - "account.featured_tags.title": "Тэгі, выбраныя {name}", "account.follow": "Падпісацца", "account.follow_back": "Падпісацца ў адказ", "account.followers": "Падпісчыкі", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index bd2d37c681..2e4c8593d4 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -27,9 +27,11 @@ "account.edit_profile": "Редактиране на профила", "account.enable_notifications": "Известяване при публикуване от @{name}", "account.endorse": "Представи в профила", + "account.featured": "Препоръчано", + "account.featured.hashtags": "Хаштагове", + "account.featured.posts": "Публикации", "account.featured_tags.last_status_at": "Последна публикация на {date}", "account.featured_tags.last_status_never": "Няма публикации", - "account.featured_tags.title": "Главни хаштагове на {name}", "account.follow": "Последване", "account.follow_back": "Последване взаимно", "account.followers": "Последователи", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} публикация} other {{counter} публикации}}", "account.unblock": "Отблокиране на @{name}", "account.unblock_domain": "Отблокиране на домейн {domain}", + "account.unblock_domain_short": "Отблокиране", "account.unblock_short": "Отблокиране", "account.unendorse": "Не включвайте в профила", "account.unfollow": "Стоп на следването", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Резултати от търсене", "emoji_button.symbols": "Символи", "emoji_button.travel": "Пътуване и места", + "empty_column.account_featured": "Списъкът е празен", "empty_column.account_hides_collections": "Този потребител е избрал да не дава тази информация", "empty_column.account_suspended": "Спрян акаунт", "empty_column.account_timeline": "Тук няма публикации!", @@ -377,6 +381,8 @@ "generic.saved": "Запазено", "getting_started.heading": "Първи стъпки", "hashtag.admin_moderation": "Отваряне на модериращия интерфейс за #{name}", + "hashtag.browse": "Разглеждане на публикации в #{hashtag}", + "hashtag.browse_from_account": "Разглеждане на публикации от @{name} из #{hashtag}", "hashtag.column_header.tag_mode.all": "и {additional}", "hashtag.column_header.tag_mode.any": "или {additional}", "hashtag.column_header.tag_mode.none": "без {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} публикация} other {{counter} публикации}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} публикация} other {{counter} публикации}} днес", "hashtag.follow": "Следване на хаштаг", + "hashtag.mute": "Заглушаване на #{hashtag}", "hashtag.unfollow": "Спиране на следване на хаштаг", "hashtags.and_other": "…и {count, plural, other {# още}}", "hints.profiles.followers_may_be_missing": "Последователи за този профил може да липсват.", @@ -674,7 +681,7 @@ "onboarding.follows.title": "Последвайте хора, за да започнете", "onboarding.profile.discoverable": "Правене на моя профил откриваем", "onboarding.profile.discoverable_hint": "Включвайки откриваемостта в Mastodon, вашите публикации може да се появят при резултатите от търсене и изгряващи неща, и вашия профил може да бъде предложен на хора с подобни интереси като вашите.", - "onboarding.profile.display_name": "Името на показ", + "onboarding.profile.display_name": "Показвано име", "onboarding.profile.display_name_hint": "Вашето пълно име или псевдоним…", "onboarding.profile.note": "Биография", "onboarding.profile.note_hint": "Може да @споменавате други хора или #хаштагове…", diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json index ed2b06289f..ec0f4eb447 100644 --- a/app/javascript/mastodon/locales/bn.json +++ b/app/javascript/mastodon/locales/bn.json @@ -29,7 +29,6 @@ "account.endorse": "প্রোফাইলে ফিচার করুন", "account.featured_tags.last_status_at": "{date} এ সর্বশেষ পোস্ট", "account.featured_tags.last_status_never": "কোনো পোস্ট নেই", - "account.featured_tags.title": "{name} এর ফিচার করা Hashtag সমূহ", "account.follow": "অনুসরণ", "account.follow_back": "তাকে অনুসরণ করো", "account.followers": "অনুসরণকারী", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index fad38721b0..51e3723d19 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -28,7 +28,6 @@ "account.endorse": "Lakaat war-wel war ar profil", "account.featured_tags.last_status_at": "Toud diwezhañ : {date}", "account.featured_tags.last_status_never": "Embannadur ebet", - "account.featured_tags.title": "Hashtagoù pennañ {name}", "account.follow": "Heuliañ", "account.follow_back": "Heuliañ d'ho tro", "account.followers": "Tud koumanantet", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index cdf79c8edc..60e3066d0e 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -27,9 +27,11 @@ "account.edit_profile": "Edita el perfil", "account.enable_notifications": "Notifica'm els tuts de @{name}", "account.endorse": "Recomana en el perfil", + "account.featured": "Destacat", + "account.featured.hashtags": "Etiquetes", + "account.featured.posts": "Publicacions", "account.featured_tags.last_status_at": "Darrer tut el {date}", "account.featured_tags.last_status_never": "No hi ha tuts", - "account.featured_tags.title": "etiquetes destacades de {name}", "account.follow": "Segueix", "account.follow_back": "Segueix tu també", "account.followers": "Seguidors", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}", "account.unblock": "Desbloca @{name}", "account.unblock_domain": "Desbloca el domini {domain}", + "account.unblock_domain_short": "Desbloca", "account.unblock_short": "Desbloca", "account.unendorse": "No recomanis en el perfil", "account.unfollow": "Deixa de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultats de la cerca", "emoji_button.symbols": "Símbols", "emoji_button.travel": "Viatges i llocs", + "empty_column.account_featured": "Aquesta llista està buida", "empty_column.account_hides_collections": "Aquest usuari ha decidit no mostrar aquesta informació", "empty_column.account_suspended": "Compte suspès", "empty_column.account_timeline": "No hi ha tuts aquí!", @@ -390,6 +394,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} tut} other {{counter} tuts}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} tut} other {{counter} tuts}} avui", "hashtag.follow": "Segueix l'etiqueta", + "hashtag.mute": "Silencia #{hashtag}", "hashtag.unfollow": "Deixa de seguir l'etiqueta", "hashtags.and_other": "…i {count, plural, other {# més}}", "hints.profiles.followers_may_be_missing": "Es poden haver perdut seguidors d'aquest perfil.", diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json index 765eacd080..31f2dbbc11 100644 --- a/app/javascript/mastodon/locales/ckb.json +++ b/app/javascript/mastodon/locales/ckb.json @@ -28,7 +28,6 @@ "account.endorse": "ناساندن لە پرۆفایل", "account.featured_tags.last_status_at": "دوایین پۆست لە {date}", "account.featured_tags.last_status_never": "هیچ پۆستێک نییە", - "account.featured_tags.title": "هاشتاگە تایبەتەکانی {name}", "account.follow": "بەدواداچوون", "account.follow_back": "فۆڵۆو بکەنەوە", "account.followers": "شوێنکەوتووان", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index deced039c2..0d8653d412 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -27,9 +27,11 @@ "account.edit_profile": "Upravit profil", "account.enable_notifications": "Oznamovat mi příspěvky @{name}", "account.endorse": "Zvýraznit na profilu", + "account.featured": "Doporučené", + "account.featured.hashtags": "Hashtagy", + "account.featured.posts": "Příspěvky", "account.featured_tags.last_status_at": "Poslední příspěvek {date}", "account.featured_tags.last_status_never": "Žádné příspěvky", - "account.featured_tags.title": "Hlavní hashtagy uživatele {name}", "account.follow": "Sledovat", "account.follow_back": "Také sledovat", "account.followers": "Sledující", @@ -65,6 +67,7 @@ "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_domain_short": "Odblokovat", "account.unblock_short": "Odblokovat", "account.unendorse": "Nezvýrazňovat na profilu", "account.unfollow": "Přestat sledovat", @@ -79,7 +82,7 @@ "admin.dashboard.retention.cohort_size": "Noví uživatelé", "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.instance_follows": "Sledující, o které by jejich 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", @@ -101,7 +104,7 @@ "annual_report.summary.archetype.replier": "Sociální motýlek", "annual_report.summary.followers.followers": "sledujících", "annual_report.summary.followers.total": "{count} celkem", - "annual_report.summary.here_it_is": "Zde je tvůj {year} v přehledu:", + "annual_report.summary.here_it_is": "Zde je tvůj rok {year} v přehledu:", "annual_report.summary.highlighted_post.by_favourites": "nejvíce oblíbený příspěvek", "annual_report.summary.highlighted_post.by_reblogs": "nejvíce boostovaný příspěvek", "annual_report.summary.highlighted_post.by_replies": "příspěvek s nejvíce odpověďmi", @@ -267,7 +270,7 @@ "domain_pill.activitypub_like_language": "ActivityPub je jako jazyk, kterým Mastodon mluví s jinými sociálními sítěmi.", "domain_pill.server": "Server", "domain_pill.their_handle": "Handle:", - "domain_pill.their_server": "Jejich digitální domov, kde žijí jejich všechny příspěvky.", + "domain_pill.their_server": "Jejich digitální domov, kde žijí všechny jejich příspěvky.", "domain_pill.their_username": "Jejich jedinečný identifikátor na jejich serveru. Je možné, že na jiných serverech jsou uživatelé se stejným uživatelským jménem.", "domain_pill.username": "Uživatelské jméno", "domain_pill.whats_in_a_handle": "Co obsahuje handle?", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Výsledky hledání", "emoji_button.symbols": "Symboly", "emoji_button.travel": "Cestování a místa", + "empty_column.account_featured": "Tento seznam je prázdný", "empty_column.account_hides_collections": "Tento uživatel se rozhodl tuto informaci nezveřejňovat", "empty_column.account_suspended": "Účet je pozastaven", "empty_column.account_timeline": "Nejsou tu žádné příspěvky!", @@ -377,6 +381,8 @@ "generic.saved": "Uloženo", "getting_started.heading": "Začínáme", "hashtag.admin_moderation": "Otevřít moderátorské rozhraní pro #{name}", + "hashtag.browse": "Procházet příspěvky na #{hashtag}", + "hashtag.browse_from_account": "Procházet příspěvky od @{name} v #{hashtag}", "hashtag.column_header.tag_mode.all": "a {additional}", "hashtag.column_header.tag_mode.any": "nebo {additional}", "hashtag.column_header.tag_mode.none": "bez {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} other {{counter} příspěvků}}", "hashtag.counter_by_uses_today": "Dnes {count, plural, one {{counter} příspěvek} few {{counter} příspěvky} other {{counter} příspěvků}}", "hashtag.follow": "Sledovat hashtag", + "hashtag.mute": "Skrýt #{hashtag}", "hashtag.unfollow": "Přestat sledovat hashtag", "hashtags.and_other": "…a {count, plural, one {# další} few {# další} other {# dalších}}", "hints.profiles.followers_may_be_missing": "Sledující mohou pro tento profil chybět.", @@ -559,7 +566,7 @@ "notification.admin.sign_up.name_and_others": "{name} a {count, plural, one {# další} few {# další} many {# dalších} other {# dalších}} se zaregistrovali", "notification.annual_report.message": "Váš #Wrapstodon {year} na Vás čeká! Podívejte se, jak vypadal tento Váš rok na Mastodonu!", "notification.annual_report.view": "Zobrazit #Wrapstodon", - "notification.favourite": "{name} si oblíbil*a váš příspěvek", + "notification.favourite": "{name} si oblíbil váš příspěvek", "notification.favourite.name_and_others_with_link": "{name} a {count, plural, one {# další si oblíbil} few {# další si oblíbili} other {# dalších si oblíbilo}} Váš příspěvek", "notification.favourite_pm": "{name} si oblíbil vaši soukromou zmínku", "notification.favourite_pm.name_and_others_with_link": "{name} a {count, plural, one {# další si oblíbil} few {# další si oblíbili} other {# dalších si oblíbilo}} Vaši soukromou zmínku", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index c107cbebe3..3bf10be7fb 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -27,9 +27,11 @@ "account.edit_profile": "Golygu proffil", "account.enable_notifications": "Rhowch wybod i fi pan fydd @{name} yn postio", "account.endorse": "Dangos ar fy mhroffil", + "account.featured": "Dethol", + "account.featured.hashtags": "Hashnodau", + "account.featured.posts": "Postiadau", "account.featured_tags.last_status_at": "Y postiad olaf ar {date}", "account.featured_tags.last_status_never": "Dim postiadau", - "account.featured_tags.title": "Prif hashnodau {name}", "account.follow": "Dilyn", "account.follow_back": "Dilyn nôl", "account.followers": "Dilynwyr", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} postiad} two {{counter} bostiad} few {{counter} phostiad} many {{counter} postiad} other {{counter} postiad}}", "account.unblock": "Dadrwystro @{name}", "account.unblock_domain": "Dadrwystro parth {domain}", + "account.unblock_domain_short": "Dadrwystro", "account.unblock_short": "Dadrwystro", "account.unendorse": "Peidio a'i ddangos ar fy mhroffil", "account.unfollow": "Dad-ddilyn", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Canlyniadau chwilio", "emoji_button.symbols": "Symbolau", "emoji_button.travel": "Teithio a Llefydd", + "empty_column.account_featured": "Mae'r rhestr hon yn wag", "empty_column.account_hides_collections": "Mae'r defnyddiwr wedi dewis i beidio rhannu'r wybodaeth yma", "empty_column.account_suspended": "Cyfrif wedi'i atal", "empty_column.account_timeline": "Dim postiadau yma!", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index c5d7fc66f5..e1d8e7aec2 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -27,9 +27,11 @@ "account.edit_profile": "Redigér profil", "account.enable_notifications": "Advisér mig, når @{name} poster", "account.endorse": "Fremhæv på profil", + "account.featured": "Fremhævet", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Indlæg", "account.featured_tags.last_status_at": "Seneste indlæg {date}", "account.featured_tags.last_status_never": "Ingen indlæg", - "account.featured_tags.title": "{name}s fremhævede etiketter", "account.follow": "Følg", "account.follow_back": "Følg tilbage", "account.followers": "Følgere", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}", "account.unblock": "Fjern blokering af @{name}", "account.unblock_domain": "Fjern blokering af domænet {domain}", + "account.unblock_domain_short": "Afblokér", "account.unblock_short": "Fjern blokering", "account.unendorse": "Fjern visning på din profil", "account.unfollow": "Følg ikke længere", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Søgeresultater", "emoji_button.symbols": "Symboler", "emoji_button.travel": "Rejser og steder", + "empty_column.account_featured": "Denne liste er tom", "empty_column.account_hides_collections": "Brugeren har valgt ikke at gøre denne information tilgængelig", "empty_column.account_suspended": "Konto suspenderet", "empty_column.account_timeline": "Ingen indlæg her!", @@ -377,6 +381,8 @@ "generic.saved": "Gemt", "getting_started.heading": "Startmenu", "hashtag.admin_moderation": "Åbn modereringsbrugerflade for #{name}", + "hashtag.browse": "Gennemse indlæg i #{hashtag}", + "hashtag.browse_from_account": "Gennemse indlæg fra @{name} i #{hashtag}", "hashtag.column_header.tag_mode.all": "og {additional}", "hashtag.column_header.tag_mode.any": "eller {additional}", "hashtag.column_header.tag_mode.none": "uden {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}} i dag", "hashtag.follow": "Følg etiket", + "hashtag.mute": "Tavsgør #{hashtag}", "hashtag.unfollow": "Stop med at følge etiket", "hashtags.and_other": "…og {count, plural, one {}other {# flere}}", "hints.profiles.followers_may_be_missing": "Der kan mangle følgere for denne profil.", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index debb2db480..0d5f2f2104 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -27,9 +27,11 @@ "account.edit_profile": "Profil bearbeiten", "account.enable_notifications": "Benachrichtige mich wenn @{name} etwas postet", "account.endorse": "Im Profil empfehlen", + "account.featured": "Empfohlen", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Beiträge", "account.featured_tags.last_status_at": "Letzter Beitrag am {date}", "account.featured_tags.last_status_never": "Keine Beiträge", - "account.featured_tags.title": "Von {name} vorgestellte Hashtags", "account.follow": "Folgen", "account.follow_back": "Ebenfalls folgen", "account.followers": "Follower", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}", "account.unblock": "{name} nicht mehr blockieren", "account.unblock_domain": "Blockierung von {domain} aufheben", + "account.unblock_domain_short": "Entsperren", "account.unblock_short": "Blockierung aufheben", "account.unendorse": "Im Profil nicht mehr empfehlen", "account.unfollow": "Entfolgen", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Suchergebnisse", "emoji_button.symbols": "Symbole", "emoji_button.travel": "Reisen & Orte", + "empty_column.account_featured": "Diese Liste ist leer", "empty_column.account_hides_collections": "Das Konto hat sich dazu entschieden, diese Information nicht zu veröffentlichen", "empty_column.account_suspended": "Konto gesperrt", "empty_column.account_timeline": "Keine Beiträge vorhanden!", @@ -377,6 +381,8 @@ "generic.saved": "Gespeichert", "getting_started.heading": "Auf gehts!", "hashtag.admin_moderation": "#{name} moderieren", + "hashtag.browse": "Beiträge mit #{hashtag} suchen", + "hashtag.browse_from_account": "Beiträge von @{name} mit #{hashtag} suchen", "hashtag.column_header.tag_mode.all": "und {additional}", "hashtag.column_header.tag_mode.any": "oder {additional}", "hashtag.column_header.tag_mode.none": "ohne {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}} heute", "hashtag.follow": "Hashtag folgen", + "hashtag.mute": "#{hashtag} stummschalten", "hashtag.unfollow": "Hashtag entfolgen", "hashtags.and_other": "… und {count, plural, one{# weiterer} other {# weitere}}", "hints.profiles.followers_may_be_missing": "Möglicherweise werden für dieses Profil nicht alle Follower angezeigt.", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 1918c4371e..0b9e42cbe9 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -29,7 +29,6 @@ "account.endorse": "Προβολή στο προφίλ", "account.featured_tags.last_status_at": "Τελευταία ανάρτηση στις {date}", "account.featured_tags.last_status_never": "Καμία ανάρτηση", - "account.featured_tags.title": "προβεβλημένες ετικέτες του/της {name}", "account.follow": "Ακολούθησε", "account.follow_back": "Ακολούθησε και εσύ", "account.followers": "Ακόλουθοι", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index f9d07793c1..b46d02baa9 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -1,7 +1,7 @@ { "about.blocks": "Moderated servers", "about.contact": "Contact:", - "about.disclaimer": "Join the Fediverse, become part of a community, and break free from Big Tech™'s stranglehold on public discourse.", + "about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Reason not available", "about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the Fediverse. These are the exceptions that have been made on this particular server.", "about.domain_blocks.silenced.explanation": "You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.", @@ -29,7 +29,6 @@ "account.endorse": "Feature on profile", "account.featured_tags.last_status_at": "Last post on {date}", "account.featured_tags.last_status_never": "No posts", - "account.featured_tags.title": "{name}'s featured hashtags", "account.follow": "Follow", "account.follow_back": "Follow back", "account.followers": "Followers", @@ -802,11 +801,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 servers you can use to participate in the fediverse.", + "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": "Join the Fediverse, become part of a community, and break free from Big Tech™'s stranglehold on public discourse.", + "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/en.json b/app/javascript/mastodon/locales/en.json index 4f5cdce223..cccae96dad 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -2,7 +2,7 @@ "about.blocks": "Moderated servers", "about.contact": "Contact:", "about.disabled": "Disabled", - "about.disclaimer": "Join the Fediverse, become part of a community, and break free from Big Tech™'s stranglehold on public discourse.", + "about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Reason not available", "about.domain_blocks.noop.explanation": "This server is limited partically.", "about.domain_blocks.noop.title": "Soft limited", @@ -14,9 +14,9 @@ "about.enabled": "Enabled", "about.full_text_search": "Full text search", "about.kmyblue_capabilities": "Features available in this server", - "about.kmyblue_capability": "Server unique features are configured as follows.", + "about.kmyblue_capability": "This server is using kmyblue, a fork of Mastodon. On this server, kmyblues unique features are configured as follows.", "about.not_available": "This information has not been made available on this server.", - "about.powered_by": "Social media powered by You!", + "about.powered_by": "Decentralized social media powered by {domain}", "about.public_visibility": "Public visibility", "about.rules": "Server rules", "account.account_note_header": "Personal note", @@ -38,9 +38,11 @@ "account.edit_profile": "Edit profile", "account.enable_notifications": "Notify me when @{name} posts", "account.endorse": "Feature on profile", + "account.featured": "Featured", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Posts", "account.featured_tags.last_status_at": "Last post on {date}", "account.featured_tags.last_status_never": "No posts", - "account.featured_tags.title": "{name}'s featured hashtags", "account.follow": "Follow", "account.follow_back": "Follow back", "account.followers": "Followers", @@ -433,6 +435,7 @@ "emoji_button.search_results": "Search results", "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", + "empty_column.account_featured": "This list is empty", "empty_column.account_hides_collections": "This user has chosen to not make this information available", "empty_column.account_suspended": "Account suspended", "empty_column.account_timeline": "No posts here!", @@ -523,6 +526,8 @@ "generic.saved": "Saved", "getting_started.heading": "Getting started", "hashtag.admin_moderation": "Open moderation interface for #{name}", + "hashtag.browse": "Browse posts in #{hashtag}", + "hashtag.browse_from_account": "Browse posts from @{name} in #{hashtag}", "hashtag.column_header.tag_mode.all": "and {additional}", "hashtag.column_header.tag_mode.any": "or {additional}", "hashtag.column_header.tag_mode.none": "without {additional}", @@ -536,6 +541,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} post} other {{counter} posts}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} post} other {{counter} posts}} today", "hashtag.follow": "Follow hashtag", + "hashtag.mute": "Mute #{hashtag}", "hashtag.unfollow": "Unfollow hashtag", "hashtags.and_other": "…and {count, plural, other {# more}}", "hints.profiles.followers_may_be_missing": "Followers for this profile may be missing.", @@ -1000,11 +1006,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 servers you can use to participate in the fediverse.", + "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": "Join the Fediverse, become part of a community, and break free from Big Tech™'s stranglehold on public discourse.", + "sign_in_banner.mastodon_is": "Mastodon is the best way to keep up with what's happening.", "sign_in_banner.sign_in": "Login", "sign_in_banner.sso_redirect": "Login or Register", "status.admin_account": "Open moderation interface for @{name}", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index accc2b8052..1d360e59d7 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -27,9 +27,10 @@ "account.edit_profile": "Redakti la profilon", "account.enable_notifications": "Sciigu min kiam @{name} afiŝos", "account.endorse": "Prezenti ĉe via profilo", + "account.featured.hashtags": "Kradvortoj", + "account.featured.posts": "Afiŝoj", "account.featured_tags.last_status_at": "Lasta afîŝo je {date}", "account.featured_tags.last_status_never": "Neniu afiŝo", - "account.featured_tags.title": "Rekomendataj kradvortoj de {name}", "account.follow": "Sekvi", "account.follow_back": "Sekvu reen", "account.followers": "Sekvantoj", @@ -65,6 +66,7 @@ "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_domain_short": "Malbloki", "account.unblock_short": "Malbloki", "account.unendorse": "Ne plu rekomendi ĉe la profilo", "account.unfollow": "Ĉesi sekvi", @@ -293,6 +295,7 @@ "emoji_button.search_results": "Serĉaj rezultoj", "emoji_button.symbols": "Simboloj", "emoji_button.travel": "Vojaĝoj kaj lokoj", + "empty_column.account_featured": "Ĉi tiu listo estas malplena", "empty_column.account_hides_collections": "Ĉi tiu uzanto elektis ne disponebligi ĉi tiu informon", "empty_column.account_suspended": "Konto suspendita", "empty_column.account_timeline": "Neniuj afiŝoj ĉi tie!", @@ -905,6 +908,8 @@ "video.expand": "Pligrandigi la videon", "video.fullscreen": "Igi plenekrana", "video.hide": "Kaŝu la filmeton", + "video.mute": "Silentigi", "video.pause": "Paŭzigi", - "video.play": "Ekigi" + "video.play": "Ekigi", + "video.unmute": "Ne plu silentigi" } diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 724fd5d274..bf6d620474 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -27,9 +27,11 @@ "account.edit_profile": "Editar perfil", "account.enable_notifications": "Notificarme cuando @{name} envíe mensajes", "account.endorse": "Destacar en el perfil", + "account.featured": "Destacados", + "account.featured.hashtags": "Etiquetas", + "account.featured.posts": "Mensajes", "account.featured_tags.last_status_at": "Último mensaje: {date}", "account.featured_tags.last_status_never": "Sin mensajes", - "account.featured_tags.title": "Etiquetas destacadas de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir", "account.followers": "Seguidores", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Desbloquear dominio {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "No destacar en el perfil", "account.unfollow": "Dejar de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", + "empty_column.account_featured": "Esta lista está vacía", "empty_column.account_hides_collections": "Este usuario eligió no publicar esta información", "empty_column.account_suspended": "Cuenta suspendida", "empty_column.account_timeline": "¡No hay mensajes acá!", @@ -377,6 +381,8 @@ "generic.saved": "Guardado", "getting_started.heading": "Inicio de Mastodon", "hashtag.admin_moderation": "Abrir interface de moderación para #{name}", + "hashtag.browse": "Ver publicaciones con #{hashtag}", + "hashtag.browse_from_account": "Ver publicaciones de @{name} con #{hashtag}", "hashtag.column_header.tag_mode.all": "y {additional}", "hashtag.column_header.tag_mode.any": "o {additional}", "hashtag.column_header.tag_mode.none": "sin {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}} hoy", "hashtag.follow": "Seguir etiqueta", + "hashtag.mute": "Silenciar #{hashtag}", "hashtag.unfollow": "Dejar de seguir etiqueta", "hashtags.and_other": "…y {count, plural, other {# más}}", "hints.profiles.followers_may_be_missing": "Es posible que falten seguidores de este perfil.", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 0fe0f1cad3..69e20fc016 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -27,9 +27,11 @@ "account.edit_profile": "Editar perfil", "account.enable_notifications": "Notificarme cuando @{name} publique algo", "account.endorse": "Destacar en mi perfil", + "account.featured": "Destacado", + "account.featured.hashtags": "Etiquetas", + "account.featured.posts": "Publicaciones", "account.featured_tags.last_status_at": "Última publicación el {date}", "account.featured_tags.last_status_never": "Sin publicaciones", - "account.featured_tags.title": "Etiquetas destacadas de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir también", "account.followers": "Seguidores", @@ -65,6 +67,7 @@ "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_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "No mostrar en el perfil", "account.unfollow": "Dejar de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", + "empty_column.account_featured": "Esta lista está vacía", "empty_column.account_hides_collections": "Este usuario ha elegido no hacer disponible esta información", "empty_column.account_suspended": "Cuenta suspendida", "empty_column.account_timeline": "¡No hay publicaciones aquí!", @@ -377,6 +381,8 @@ "generic.saved": "Guardado", "getting_started.heading": "Primeros pasos", "hashtag.admin_moderation": "Abrir interfaz de moderación para #{name}", + "hashtag.browse": "Explorar publicaciones en #{hashtag}", + "hashtag.browse_from_account": "Explorar publicaciones desde @{name} en #{hashtag}", "hashtag.column_header.tag_mode.all": "y {additional}", "hashtag.column_header.tag_mode.any": "o {additional}", "hashtag.column_header.tag_mode.none": "sin {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}} hoy", "hashtag.follow": "Seguir etiqueta", + "hashtag.mute": "Silenciar #{hashtag}", "hashtag.unfollow": "Dejar de seguir etiqueta", "hashtags.and_other": "…y {count, plural, other {# más}}", "hints.profiles.followers_may_be_missing": "Puede que no se muestren todos los seguidores de este perfil.", @@ -909,8 +916,8 @@ "video.pause": "Pausar", "video.play": "Reproducir", "video.skip_backward": "Saltar atrás", - "video.skip_forward": "Adelantar", + "video.skip_forward": "Saltar adelante", "video.unmute": "Dejar de silenciar", - "video.volume_down": "Bajar volumen", - "video.volume_up": "Subir volumen" + "video.volume_down": "Bajar el volumen", + "video.volume_up": "Subir el volumen" } diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 8d6bb4cdbe..4f9d91ce11 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -27,9 +27,11 @@ "account.edit_profile": "Editar perfil", "account.enable_notifications": "Notificarme cuando @{name} publique algo", "account.endorse": "Destacar en el perfil", + "account.featured": "Destacado", + "account.featured.hashtags": "Etiquetas", + "account.featured.posts": "Publicaciones", "account.featured_tags.last_status_at": "Última publicación el {date}", "account.featured_tags.last_status_never": "Sin publicaciones", - "account.featured_tags.title": "Etiquetas destacadas de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir también", "account.followers": "Seguidores", @@ -65,6 +67,7 @@ "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_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "No mostrar en el perfil", "account.unfollow": "Dejar de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", + "empty_column.account_featured": "Esta lista está vacía", "empty_column.account_hides_collections": "Este usuario ha decidido no mostrar esta información", "empty_column.account_suspended": "Cuenta suspendida", "empty_column.account_timeline": "¡No hay publicaciones aquí!", @@ -377,6 +381,8 @@ "generic.saved": "Guardado", "getting_started.heading": "Primeros pasos", "hashtag.admin_moderation": "Abrir interfaz de moderación para #{name}", + "hashtag.browse": "Explorar publicaciones en #{hashtag}", + "hashtag.browse_from_account": "Explorar publicaciones desde @{name} en #{hashtag}", "hashtag.column_header.tag_mode.all": "y {additional}", "hashtag.column_header.tag_mode.any": "o {additional}", "hashtag.column_header.tag_mode.none": "sin {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}} hoy", "hashtag.follow": "Seguir etiqueta", + "hashtag.mute": "Silenciar #{hashtag}", "hashtag.unfollow": "Dejar de seguir etiqueta", "hashtags.and_other": "…y {count, plural, other {# más}}", "hints.profiles.followers_may_be_missing": "Puede que no se muestren todos los seguidores de este perfil.", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index d2fb81bee6..3e0610126e 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -29,7 +29,6 @@ "account.endorse": "Too profiilil esile", "account.featured_tags.last_status_at": "Viimane postitus {date}", "account.featured_tags.last_status_never": "Postitusi pole", - "account.featured_tags.title": "{name} esiletõstetud sildid", "account.follow": "Jälgi", "account.follow_back": "Jälgi vastu", "account.followers": "Jälgijad", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 5507e7f343..0c73c9f540 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -29,7 +29,6 @@ "account.endorse": "Nabarmendu profilean", "account.featured_tags.last_status_at": "Azken bidalketa {date} datan", "account.featured_tags.last_status_never": "Bidalketarik ez", - "account.featured_tags.title": "{name} erabiltzailearen nabarmendutako traolak", "account.follow": "Jarraitu", "account.follow_back": "Jarraitu bueltan", "account.followers": "Jarraitzaileak", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index f4758e5afb..3e31eb8a15 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -29,7 +29,6 @@ "account.endorse": "معرّفی در نمایه", "account.featured_tags.last_status_at": "آخرین فرسته در {date}", "account.featured_tags.last_status_never": "بدون فرسته", - "account.featured_tags.title": "برچسب‌های برگزیدهٔ {name}", "account.follow": "پی‌گرفتن", "account.follow_back": "دنبال کردن متقابل", "account.followers": "پی‌گیرندگان", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}", "account.unblock": "رفع مسدودیت ‎@{name}", "account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}", + "account.unblock_domain_short": "آنبلاک", "account.unblock_short": "رفع مسدودیت", "account.unendorse": "معرّفی نکردن در نمایه", "account.unfollow": "پی‌نگرفتن", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 3e5a327301..cc42780f94 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -27,9 +27,10 @@ "account.edit_profile": "Muokkaa profiilia", "account.enable_notifications": "Ilmoita minulle, kun @{name} julkaisee", "account.endorse": "Suosittele profiilissasi", + "account.featured.hashtags": "Aihetunnisteet", + "account.featured.posts": "Julkaisut", "account.featured_tags.last_status_at": "Viimeisin julkaisu {date}", "account.featured_tags.last_status_never": "Ei julkaisuja", - "account.featured_tags.title": "Käyttäjän {name} suosittelemat aihetunnisteet", "account.follow": "Seuraa", "account.follow_back": "Seuraa takaisin", "account.followers": "Seuraajat", @@ -65,6 +66,7 @@ "account.statuses_counter": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", "account.unblock": "Kumoa käyttäjän @{name} esto", "account.unblock_domain": "Kumoa verkkotunnuksen {domain} esto", + "account.unblock_domain_short": "Kumoa esto", "account.unblock_short": "Kumoa esto", "account.unendorse": "Kumoa suosittelu profiilissasi", "account.unfollow": "Älä seuraa", @@ -239,7 +241,7 @@ "conversation.mark_as_read": "Merkitse luetuksi", "conversation.open": "Näytä keskustelu", "conversation.with": "{names} kanssa", - "copy_icon_button.copied": "Sisältö kopioitiin leikepöydälle", + "copy_icon_button.copied": "Kopioitu leikepöydälle", "copypaste.copied": "Kopioitu", "copypaste.copy_to_clipboard": "Kopioi leikepöydälle", "directory.federated": "Tunnetusta fediversumista", @@ -293,6 +295,7 @@ "emoji_button.search_results": "Hakutulokset", "emoji_button.symbols": "Symbolit", "emoji_button.travel": "Matkailu ja paikat", + "empty_column.account_featured": "Tämä lista on tyhjä", "empty_column.account_hides_collections": "Käyttäjä on päättänyt pitää nämä tiedot yksityisinä", "empty_column.account_suspended": "Tili jäädytetty", "empty_column.account_timeline": "Ei viestejä täällä.", @@ -377,6 +380,8 @@ "generic.saved": "Tallennettu", "getting_started.heading": "Näin pääset alkuun", "hashtag.admin_moderation": "Avaa tunnisteen #{name} moderointinäkymä", + "hashtag.browse": "Selaa julkaisuja tunnisteella #{hashtag}", + "hashtag.browse_from_account": "Selaa julkaisuja käyttäjältä @{name} tunnisteella #{hashtag}", "hashtag.column_header.tag_mode.all": "ja {additional}", "hashtag.column_header.tag_mode.any": "tai {additional}", "hashtag.column_header.tag_mode.none": "ilman {additional}", @@ -390,6 +395,7 @@ "hashtag.counter_by_uses": "{count, plural, one{{counter} julkaisu} other {{counter} julkaisua}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}} tänään", "hashtag.follow": "Seuraa aihetunnistetta", + "hashtag.mute": "Mykistä #{hashtag}", "hashtag.unfollow": "Lopeta aihetunnisteen seuraaminen", "hashtags.and_other": "…ja {count, plural, other {# lisää}}", "hints.profiles.followers_may_be_missing": "Tämän profiilin seuraajia saattaa puuttua.", diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json index 4c33ac01a1..c13d0a8afe 100644 --- a/app/javascript/mastodon/locales/fil.json +++ b/app/javascript/mastodon/locales/fil.json @@ -29,7 +29,6 @@ "account.endorse": "I-tampok sa profile", "account.featured_tags.last_status_at": "Huling post noong {date}", "account.featured_tags.last_status_never": "Walang mga post", - "account.featured_tags.title": "Nakatampok na hashtag ni {name}", "account.follow": "Sundan", "account.follow_back": "Sundan pabalik", "account.followers": "Mga tagasunod", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 06ab07023e..b472fd8bd2 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -27,9 +27,11 @@ "account.edit_profile": "Broyt vanga", "account.enable_notifications": "Boða mær frá, tá @{name} skrivar", "account.endorse": "Víst á vangamyndini", + "account.featured": "Tikin fram", + "account.featured.hashtags": "Frámerki", + "account.featured.posts": "Postar", "account.featured_tags.last_status_at": "Seinasta strongur skrivaður {date}", "account.featured_tags.last_status_never": "Einki uppslag", - "account.featured_tags.title": "Tvíkrossar hjá {name}", "account.follow": "Fylg", "account.follow_back": "Fylg aftur", "account.followers": "Fylgjarar", @@ -65,6 +67,7 @@ "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_domain_short": "Banna ikki", "account.unblock_short": "Banna ikki", "account.unendorse": "Vís ikki á vanga", "account.unfollow": "Fylg ikki", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Leitiúrslit", "emoji_button.symbols": "Ímyndir", "emoji_button.travel": "Ferðing og støð", + "empty_column.account_featured": "Hesin listin er tómur", "empty_column.account_hides_collections": "Hesin brúkarin hevur valt, at hesar upplýsingarnar ikki skulu vera tøkar", "empty_column.account_suspended": "Kontan gjørd óvirkin", "empty_column.account_timeline": "Einki uppslag her!", @@ -377,6 +381,8 @@ "generic.saved": "Goymt", "getting_started.heading": "At byrja", "hashtag.admin_moderation": "Lat umsjónarmarkamót upp fyri #{name}", + "hashtag.browse": "Blaða gjøgnum postar í #{hashtag}", + "hashtag.browse_from_account": "Blaða gjøgnum postar frá @{name} í #{hashtag}", "hashtag.column_header.tag_mode.all": "og {additional}", "hashtag.column_header.tag_mode.any": "ella {additional}", "hashtag.column_header.tag_mode.none": "uttan {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} postur} other {{counter} postar}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} postur} other {{counter} postar}} í dag", "hashtag.follow": "Fylg frámerki", + "hashtag.mute": "Doyv @#{hashtag}", "hashtag.unfollow": "Gevst at fylgja frámerki", "hashtags.and_other": "…og {count, plural, other {# afturat}}", "hints.profiles.followers_may_be_missing": "Fylgjarar hjá hesum vanganum kunnu mangla.", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index dd497adbbb..f63a1d2cba 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -27,9 +27,11 @@ "account.edit_profile": "Modifier le profil", "account.enable_notifications": "Me notifier quand @{name} publie", "account.endorse": "Inclure sur profil", + "account.featured": "En vedette", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Messages", "account.featured_tags.last_status_at": "Dernière publication {date}", "account.featured_tags.last_status_never": "Aucune publication", - "account.featured_tags.title": "Hashtags inclus de {name}", "account.follow": "Suivre", "account.follow_back": "Suivre en retour", "account.followers": "abonné·e·s", @@ -65,6 +67,7 @@ "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_domain_short": "Débloquer", "account.unblock_short": "Débloquer", "account.unendorse": "Ne pas inclure sur profil", "account.unfollow": "Ne plus suivre", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Résultats", "emoji_button.symbols": "Symboles", "emoji_button.travel": "Voyage et lieux", + "empty_column.account_featured": "Cette liste est vide", "empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations", "empty_column.account_suspended": "Compte suspendu", "empty_column.account_timeline": "Aucune publication ici!", @@ -905,6 +909,10 @@ "video.expand": "Agrandir la vidéo", "video.fullscreen": "Plein écran", "video.hide": "Masquer la vidéo", + "video.mute": "Couper le son", "video.pause": "Pause", - "video.play": "Lecture" + "video.play": "Lecture", + "video.unmute": "Rétablir le son", + "video.volume_down": "Baisser le volume", + "video.volume_up": "Augmenter le volume" } diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 5b76cbfde6..f9c616627f 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -27,9 +27,11 @@ "account.edit_profile": "Modifier le profil", "account.enable_notifications": "Me notifier quand @{name} publie quelque chose", "account.endorse": "Recommander sur votre profil", + "account.featured": "En vedette", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Messages", "account.featured_tags.last_status_at": "Dernier message le {date}", "account.featured_tags.last_status_never": "Aucun message", - "account.featured_tags.title": "Les hashtags en vedette de {name}", "account.follow": "Suivre", "account.follow_back": "Suivre en retour", "account.followers": "Abonné·e·s", @@ -65,6 +67,7 @@ "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_domain_short": "Débloquer", "account.unblock_short": "Débloquer", "account.unendorse": "Ne plus recommander sur le profil", "account.unfollow": "Ne plus suivre", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Résultats de la recherche", "emoji_button.symbols": "Symboles", "emoji_button.travel": "Voyage et lieux", + "empty_column.account_featured": "Cette liste est vide", "empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations", "empty_column.account_suspended": "Compte suspendu", "empty_column.account_timeline": "Aucun message ici !", @@ -905,6 +909,10 @@ "video.expand": "Agrandir la vidéo", "video.fullscreen": "Plein écran", "video.hide": "Masquer la vidéo", + "video.mute": "Couper le son", "video.pause": "Pause", - "video.play": "Lecture" + "video.play": "Lecture", + "video.unmute": "Rétablir le son", + "video.volume_down": "Baisser le volume", + "video.volume_up": "Augmenter le volume" } diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 46da3ea09d..e3c3222868 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -29,7 +29,6 @@ "account.endorse": "Op profyl werjaan", "account.featured_tags.last_status_at": "Lêste berjocht op {date}", "account.featured_tags.last_status_never": "Gjin berjochten", - "account.featured_tags.title": "Utljochte hashtags fan {name}", "account.follow": "Folgje", "account.follow_back": "Weromfolgje", "account.followers": "Folgers", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index d3af400c64..5935b39e2d 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -29,7 +29,6 @@ "account.endorse": "Cuir ar an phróifíl mar ghné", "account.featured_tags.last_status_at": "Postáil is déanaí ar {date}", "account.featured_tags.last_status_never": "Gan aon phoist", - "account.featured_tags.title": "Haischlib faoi thrácht {name}", "account.follow": "Lean", "account.follow_back": "Leanúint ar ais", "account.followers": "Leantóirí", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} poist}}", "account.unblock": "Bain bac de @{name}", "account.unblock_domain": "Bain bac den ainm fearainn {domain}", + "account.unblock_domain_short": "Díbhlocáil", "account.unblock_short": "Díbhlocáil", "account.unendorse": "Ná chuir ar an phróifíl mar ghné", "account.unfollow": "Ná lean a thuilleadh", @@ -905,6 +905,12 @@ "video.expand": "Leath físeán", "video.fullscreen": "Lánscáileán", "video.hide": "Cuir físeán i bhfolach", + "video.mute": "Balbhaigh", "video.pause": "Cuir ar sos", - "video.play": "Cuir ar siúl" + "video.play": "Cuir ar siúl", + "video.skip_backward": "Scipeáil siar", + "video.skip_forward": "Scipeáil ar aghaidh", + "video.unmute": "Díbhalbhú", + "video.volume_down": "Toirt síos", + "video.volume_up": "Toirt suas" } diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 69aa5f69ef..f295db0b43 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -29,7 +29,6 @@ "account.endorse": "Brosnaich air a’ phròifil", "account.featured_tags.last_status_at": "Am post mu dheireadh {date}", "account.featured_tags.last_status_never": "Gun phost", - "account.featured_tags.title": "Na tagaichean hais brosnaichte aig {name}", "account.follow": "Lean", "account.follow_back": "Lean air ais", "account.followers": "Luchd-leantainn", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 8106c86714..50dc2437c6 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -27,9 +27,11 @@ "account.edit_profile": "Editar perfil", "account.enable_notifications": "Noficarme cando @{name} publique", "account.endorse": "Amosar no perfil", + "account.featured": "Destacado", + "account.featured.hashtags": "Cancelos", + "account.featured.posts": "Publicacións", "account.featured_tags.last_status_at": "Última publicación o {date}", "account.featured_tags.last_status_never": "Sen publicacións", - "account.featured_tags.title": "Cancelos destacados de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir tamén", "account.followers": "Seguidoras", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Amosar {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "Non amosar no perfil", "account.unfollow": "Deixar de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultados da procura", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viaxes e Lugares", + "empty_column.account_featured": "A lista está baleira", "empty_column.account_hides_collections": "A usuaria decideu non facer pública esta información", "empty_column.account_suspended": "Conta suspendida", "empty_column.account_timeline": "Non hai publicacións aquí!", @@ -377,6 +381,8 @@ "generic.saved": "Gardado", "getting_started.heading": "Primeiros pasos", "hashtag.admin_moderation": "Abrir interface de moderación para ##{name}", + "hashtag.browse": "Ver publicacións con #{hashtag}", + "hashtag.browse_from_account": "Ver as publicacións de @{name} con #{hashtag}", "hashtag.column_header.tag_mode.all": "e {additional}", "hashtag.column_header.tag_mode.any": "ou {additional}", "hashtag.column_header.tag_mode.none": "sen {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicación} other {{counter} publicacións}} hoxe", "hashtag.follow": "Seguir cancelo", + "hashtag.mute": "Acalar a #{hashtag}", "hashtag.unfollow": "Deixar de seguir cancelo", "hashtags.and_other": "…e {count, plural, one {}other {# máis}}", "hints.profiles.followers_may_be_missing": "Poderían faltar seguidoras deste perfil.", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index f31b862a03..d1a2c014a5 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -27,9 +27,11 @@ "account.edit_profile": "עריכת פרופיל", "account.enable_notifications": "שלח לי התראות כש@{name} מפרסם", "account.endorse": "קדם את החשבון בפרופיל", + "account.featured": "מומלץ", + "account.featured.hashtags": "תגיות", + "account.featured.posts": "הודעות", "account.featured_tags.last_status_at": "חצרוץ אחרון בתאריך {date}", "account.featured_tags.last_status_never": "אין חצרוצים", - "account.featured_tags.title": "התגיות המועדפות של {name}", "account.follow": "לעקוב", "account.follow_back": "לעקוב בחזרה", "account.followers": "עוקבים", @@ -64,8 +66,9 @@ "account.show_reblogs": "הצג הדהודים מאת @{name}", "account.statuses_counter": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}}", "account.unblock": "להסיר חסימה ל- @{name}", - "account.unblock_domain": "הסירי את החסימה של קהילת {domain}", - "account.unblock_short": "הסר חסימה", + "account.unblock_domain": "הסרת החסימה של קהילת {domain}", + "account.unblock_domain_short": "הסרת חסימה", + "account.unblock_short": "הסרת חסימה", "account.unendorse": "אל תקדם בפרופיל", "account.unfollow": "הפסקת מעקב", "account.unmute": "הפסקת השתקת @{name}", @@ -293,6 +296,7 @@ "emoji_button.search_results": "תוצאות חיפוש", "emoji_button.symbols": "סמלים", "emoji_button.travel": "טיולים ואתרים", + "empty_column.account_featured": "הרשימה ריקה", "empty_column.account_hides_collections": "המשתמש.ת בחר.ה להסתיר מידע זה", "empty_column.account_suspended": "חשבון מושעה", "empty_column.account_timeline": "אין עדיין אף הודעה!", @@ -377,6 +381,8 @@ "generic.saved": "נשמר", "getting_started.heading": "בואו נתחיל", "hashtag.admin_moderation": "פתיחת ממשק פיקוח דיון עבור #{name}", + "hashtag.browse": "קריאת הודעות תחת #{hashtag}", + "hashtag.browse_from_account": "קריאת הודעות מאת @{name} תחת #{hashtag}", "hashtag.column_header.tag_mode.all": "ו- {additional}", "hashtag.column_header.tag_mode.any": "או {additional}", "hashtag.column_header.tag_mode.none": "ללא {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}}", "hashtag.counter_by_uses_today": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}} היום", "hashtag.follow": "לעקוב אחרי תגית", + "hashtag.mute": "להשתיק את #{hashtag}", "hashtag.unfollow": "להפסיק לעקוב אחרי תגית", "hashtags.and_other": "…{count, plural,other {ועוד #}}", "hints.profiles.followers_may_be_missing": "יתכן כי עוקבים של פרופיל זה חסרים.", @@ -905,6 +912,12 @@ "video.expand": "להרחיב וידאו", "video.fullscreen": "מסך מלא", "video.hide": "להסתיר וידאו", + "video.mute": "השתקה", "video.pause": "השהיה", - "video.play": "ניגון" + "video.play": "ניגון", + "video.skip_backward": "דילוג אחורה", + "video.skip_forward": "דילוג קדימה", + "video.unmute": "ביטול השתקה", + "video.volume_down": "הנמכת עוצמת השמע", + "video.volume_up": "הגברת עוצמת שמע" } diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json index 10c5356719..a3eec9544c 100644 --- a/app/javascript/mastodon/locales/hi.json +++ b/app/javascript/mastodon/locales/hi.json @@ -28,7 +28,6 @@ "account.endorse": "प्रोफ़ाइल पर दिखाए", "account.featured_tags.last_status_at": "{date} का अंतिम पोस्ट", "account.featured_tags.last_status_never": "कोई पोस्ट नहीं है", - "account.featured_tags.title": "{name} के चुनिंदा हैशटैग", "account.follow": "फॉलो करें", "account.follow_back": "फॉलो करें", "account.followers": "फॉलोवर", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 092647bd31..38807b28b2 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -28,7 +28,6 @@ "account.endorse": "Istakni na profilu", "account.featured_tags.last_status_at": "Zadnji post {date}", "account.featured_tags.last_status_never": "Nema postova", - "account.featured_tags.title": "Istaknuti hashtagovi {name}", "account.follow": "Prati", "account.follow_back": "Slijedi natrag", "account.followers": "Pratitelji", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 942c96ffec..b311dffa72 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -27,9 +27,11 @@ "account.edit_profile": "Profil szerkesztése", "account.enable_notifications": "Figyelmeztessen, ha @{name} bejegyzést tesz közzé", "account.endorse": "Kiemelés a profilodon", + "account.featured": "Kiemelt", + "account.featured.hashtags": "Hashtagek", + "account.featured.posts": "Bejegyzések", "account.featured_tags.last_status_at": "Legutolsó bejegyzés ideje: {date}", "account.featured_tags.last_status_never": "Nincs bejegyzés", - "account.featured_tags.title": "{name} kiemelt hashtagjei", "account.follow": "Követés", "account.follow_back": "Viszontkövetés", "account.followers": "Követő", @@ -65,6 +67,7 @@ "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_domain_short": "Tiltás feloldása", "account.unblock_short": "Tiltás feloldása", "account.unendorse": "Ne jelenjen meg a profilodon", "account.unfollow": "Követés megszüntetése", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Keresési találatok", "emoji_button.symbols": "Szimbólumok", "emoji_button.travel": "Utazás és helyek", + "empty_column.account_featured": "Ez a lista üres", "empty_column.account_hides_collections": "Ez a felhasználó úgy döntött, hogy nem teszi elérhetővé ezt az információt.", "empty_column.account_suspended": "Fiók felfüggesztve", "empty_column.account_timeline": "Itt nincs bejegyzés!", @@ -377,6 +381,8 @@ "generic.saved": "Elmentve", "getting_started.heading": "Első lépések", "hashtag.admin_moderation": "Moderációs felület megnyitása a következőhöz: #{name}", + "hashtag.browse": "Bejegyzések ebben: #{hashtag}", + "hashtag.browse_from_account": "@{name} bejegyzéseinek tallózása ebben: #{hashtag}", "hashtag.column_header.tag_mode.all": "és {additional}", "hashtag.column_header.tag_mode.any": "vagy {additional}", "hashtag.column_header.tag_mode.none": "{additional} nélkül", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}} ma", "hashtag.follow": "Hashtag követése", + "hashtag.mute": "#{hashtag} némítása", "hashtag.unfollow": "Hashtag követésének megszüntetése", "hashtags.and_other": "…és {count, plural, other {# további}}", "hints.profiles.followers_may_be_missing": "A profil követői lehet, hogy hiányoznak.", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index d4706dbf8c..7f4f66796e 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -29,7 +29,6 @@ "account.endorse": "Evidentiar sur le profilo", "account.featured_tags.last_status_at": "Ultime message publicate le {date}", "account.featured_tags.last_status_never": "Necun message", - "account.featured_tags.title": "Hashtags eminente de {name}", "account.follow": "Sequer", "account.follow_back": "Sequer in retorno", "account.followers": "Sequitores", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index f4724b6f4f..102e547d40 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -29,7 +29,6 @@ "account.endorse": "Tampilkan di profil", "account.featured_tags.last_status_at": "Kiriman terakhir pada {date}", "account.featured_tags.last_status_never": "Tidak ada kiriman", - "account.featured_tags.title": "Tagar {name} yang difiturkan", "account.follow": "Ikuti", "account.follow_back": "Ikuti balik", "account.followers": "Pengikut", diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index ba5ad494ce..7cd463727f 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -28,7 +28,6 @@ "account.endorse": "Recomandar sur profil", "account.featured_tags.last_status_at": "Ultim posta ye {date}", "account.featured_tags.last_status_never": "Null postas", - "account.featured_tags.title": "Recomandat hashtags de {name}", "account.follow": "Sequer", "account.follow_back": "Sequer reciprocmen", "account.followers": "Sequitores", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 21723e10b8..596ca4c3fe 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -29,7 +29,6 @@ "account.endorse": "Traito di profilo", "account.featured_tags.last_status_at": "Antea posto ye {date}", "account.featured_tags.last_status_never": "Nula posti", - "account.featured_tags.title": "Ekstaca gretvorti di {name}", "account.follow": "Sequar", "account.follow_back": "Anke sequez", "account.followers": "Sequanti", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 47cd31fd6b..50f31dd623 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -27,9 +27,11 @@ "account.edit_profile": "Breyta notandasniði", "account.enable_notifications": "Láta mig vita þegar @{name} sendir inn", "account.endorse": "Birta á notandasniði", + "account.featured": "Með aukið vægi", + "account.featured.hashtags": "Myllumerki", + "account.featured.posts": "Færslur", "account.featured_tags.last_status_at": "Síðasta færsla þann {date}", "account.featured_tags.last_status_never": "Engar færslur", - "account.featured_tags.title": "Myllumerki hjá {name} með aukið vægi", "account.follow": "Fylgjast með", "account.follow_back": "Fylgjast með til baka", "account.followers": "Fylgjendur", @@ -65,6 +67,7 @@ "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_domain_short": "Aflétta útilokun", "account.unblock_short": "Hætta að loka á", "account.unendorse": "Ekki birta á notandasniði", "account.unfollow": "Hætta að fylgja", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Leitarniðurstöður", "emoji_button.symbols": "Tákn", "emoji_button.travel": "Ferðalög og staðir", + "empty_column.account_featured": "Þessi listi er tómur", "empty_column.account_hides_collections": "Notandinn hefur valið að gera ekki tiltækar þessar upplýsingar", "empty_column.account_suspended": "Notandaaðgangur í frysti", "empty_column.account_timeline": "Engar færslur hér!", @@ -377,6 +381,8 @@ "generic.saved": "Vistað", "getting_started.heading": "Komast í gang", "hashtag.admin_moderation": "Opna umsjónarviðmót fyrir #{name}", + "hashtag.browse": "Skoða færslur með #{hashtag}", + "hashtag.browse_from_account": "Skoða færslur frá @{name} í #{hashtag}", "hashtag.column_header.tag_mode.all": "og {additional}", "hashtag.column_header.tag_mode.any": "eða {additional}", "hashtag.column_header.tag_mode.none": "án {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} færsla} other {{counter} færslur}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} færsla} other {{counter} færslur}} í dag", "hashtag.follow": "Fylgjast með myllumerki", + "hashtag.mute": "Þagga #{hashtag}", "hashtag.unfollow": "Hætta að fylgjast með myllumerki", "hashtags.and_other": "…og {count, plural, other {# til viðbótar}}", "hints.profiles.followers_may_be_missing": "Fylgjendur frá þessum notanda gæti vantað.", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 9fa5c725b6..36770c675f 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -27,9 +27,11 @@ "account.edit_profile": "Modifica profilo", "account.enable_notifications": "Avvisami quando @{name} pubblica un post", "account.endorse": "In evidenza sul profilo", + "account.featured": "In primo piano", + "account.featured.hashtags": "Hashtag", + "account.featured.posts": "Post", "account.featured_tags.last_status_at": "Ultimo post il {date}", "account.featured_tags.last_status_never": "Nessun post", - "account.featured_tags.title": "Hashtag in evidenza di {name}", "account.follow": "Segui", "account.follow_back": "Segui a tua volta", "account.followers": "Follower", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} post}}", "account.unblock": "Sblocca @{name}", "account.unblock_domain": "Sblocca il dominio {domain}", + "account.unblock_domain_short": "Sblocca", "account.unblock_short": "Sblocca", "account.unendorse": "Non mostrare sul profilo", "account.unfollow": "Smetti di seguire", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Risultati della ricerca", "emoji_button.symbols": "Simboli", "emoji_button.travel": "Viaggi & Luoghi", + "empty_column.account_featured": "Questa lista è vuota", "empty_column.account_hides_collections": "Questo utente ha scelto di non rendere disponibili queste informazioni", "empty_column.account_suspended": "Profilo sospeso", "empty_column.account_timeline": "Nessun post qui!", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 457aa84628..a2328401f4 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -40,7 +40,6 @@ "account.endorse": "プロフィールで紹介する", "account.featured_tags.last_status_at": "最終投稿 {date}", "account.featured_tags.last_status_never": "投稿がありません", - "account.featured_tags.title": "{name}の注目ハッシュタグ", "account.follow": "フォロー", "account.follow_back": "フォローバック", "account.followers": "フォロワー", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index f62d32226f..bb97a18bd2 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -20,10 +20,10 @@ "account.cancel_follow_request": "Sefsex taḍfart", "account.copy": "Nɣel assaɣ ɣer umaɣnu", "account.direct": "Bder-d @{name} weḥd-s", - "account.disable_notifications": "Ḥbes ur iyi-d-ttazen ara alɣuten mi ara d-isuffeɣ @{name}", + "account.disable_notifications": "Ḥbes ur iyi-d-ttazen ara ilɣa mi ara d-isuffeɣ @{name}", "account.domain_blocked": "Taɣult yeffren", "account.edit_profile": "Ẓreg amaɣnu", - "account.enable_notifications": "Azen-iyi-d alɣuten mi ara d-isuffeɣ @{name}", + "account.enable_notifications": "Azen-iyi-d ilɣa mi ara d-isuffeɣ @{name}", "account.endorse": "Welleh fell-as deg umaɣnu-inek", "account.featured_tags.last_status_at": "Tasuffeɣt taneggarut ass n {date}", "account.featured_tags.last_status_never": "Ulac tisuffaɣ", @@ -45,7 +45,7 @@ "account.mention": "Bder-d @{name}", "account.moved_to": "{name} yenna-d dakken amiḍan-is amaynut yuɣal :", "account.mute": "Sgugem @{name}", - "account.mute_notifications_short": "Susem alɣuten", + "account.mute_notifications_short": "Susem ilɣa", "account.mute_short": "Sgugem", "account.muted": "Yettwasgugem", "account.mutual": "Temṭafarem", @@ -61,6 +61,7 @@ "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_domain_short": "Serreḥ", "account.unblock_short": "Serreḥ", "account.unendorse": "Ur ttwellih ara fell-as deg umaɣnu-inek", "account.unfollow": "Ur ṭṭafaṛ ara", @@ -73,7 +74,9 @@ "alert.rate_limited.title": "Aktum s talast", "alert.unexpected.message": "Yeḍra-d unezri ur netturaǧu ara.", "alert.unexpected.title": "Ayhuh!", - "alt_text_badge.title": "Aḍris asegzan", + "alt_text_badge.title": "Aḍris amlellay", + "alt_text_modal.add_alt_text": "Rnu aḍris amlellay", + "alt_text_modal.add_text_from_image": "Rnu aḍris amlellay seg tugna", "alt_text_modal.cancel": "Semmet", "alt_text_modal.done": "Immed", "announcement.announcement": "Ulɣu", @@ -115,7 +118,7 @@ "column.home": "Agejdan", "column.lists": "Tibdarin", "column.mutes": "Imiḍanen yettwasgugmen", - "column.notifications": "Alɣuten", + "column.notifications": "Ilɣa", "column.pins": "Tisuffaɣ yettwasenṭḍen", "column.public": "Tasuddemt tamatut", "column_back_button.label": "Tuɣalin", @@ -167,7 +170,9 @@ "confirmations.logout.confirm": "Ffeɣ", "confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?", "confirmations.logout.title": "Tebɣiḍ ad teffɣeḍ ssya?", + "confirmations.missing_alt_text.confirm": "Rnu aḍris amlellay", "confirmations.missing_alt_text.secondary": "Suffeɣ akken yebɣu yili", + "confirmations.missing_alt_text.title": "Rnu aḍris amlellay?", "confirmations.mute.confirm": "Sgugem", "confirmations.redraft.confirm": "Kkes sakin ɛiwed tira", "confirmations.reply.confirm": "Err", @@ -228,7 +233,7 @@ "empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neɣ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.", "empty_column.list": "Ar tura ur yelli kra deg umuɣ-a. Ad d-yettwasken da ticki iɛeggalen n wumuɣ-a suffɣen-d kra.", "empty_column.mutes": "Ulac ɣur-k·m imseqdacen i yettwasgugmen.", - "empty_column.notifications": "Ulac ɣur-k·m alɣuten. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.", + "empty_column.notifications": "Ulac ɣur-k·m ilɣa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.", "empty_column.public": "Ulac kra da! Aru kra, neɣ ḍfeṛ imdanen i yellan deg yiqeddacen-nniḍen akken ad d-teččar tsuddemt tazayezt", "error.unexpected_crash.next_steps": "Smiren asebter-a, ma ur yekkis ara wugur, ẓer d akken tzemreḍ ad tesqedceḍ Maṣṭudun deg yiminig-nniḍen neɣ deg usnas anaṣli.", "errors.unexpected_crash.copy_stacktrace": "Nɣel stacktrace ɣef wafus", @@ -247,6 +252,7 @@ "filter_modal.select_filter.search": "Nadi neɣ snulfu-d", "filter_modal.select_filter.title": "Sizdeg tassufeɣt-a", "filter_modal.title.status": "Sizdeg tassufeɣt", + "filtered_notifications_banner.title": "Ilɣa yettwasizdgen", "firehose.all": "Akk", "firehose.local": "Deg uqeddac-ayi", "firehose.remote": "Iqeddacen nniḍen", @@ -254,8 +260,11 @@ "follow_request.reject": "Agi", "follow_suggestions.dismiss": "Dayen ur t-id-skan ara", "follow_suggestions.featured_longer": "Yettwafraned s ufus sɣur agraw n {domain}", + "follow_suggestions.friends_of_friends_longer": "D aɣeṛfan ar wid i teṭṭafareḍ", "follow_suggestions.hints.featured": "Amaɣnu-a ifren-it-id wegraw n {domain} s ufus.", - "follow_suggestions.popular_suggestion_longer": "Yettwassen deg {domain}", + "follow_suggestions.hints.friends_of_friends": "Amaɣnu-a d aɣeṛfan ɣer wid i teṭṭafaṛeḍ.", + "follow_suggestions.popular_suggestion": "Asumer aɣeṛfan", + "follow_suggestions.popular_suggestion_longer": "D aɣeṛfan deg {domain}", "follow_suggestions.view_all": "Wali-ten akk", "follow_suggestions.who_to_follow": "Ad tḍefreḍ?", "followed_tags": "Ihacṭagen yettwaḍfaren", @@ -326,7 +335,7 @@ "keyboard_shortcuts.mention": "akken ad d-bedreḍ ameskar", "keyboard_shortcuts.muted": "akken ad teldiḍ tabdart n yimseqdacen yettwasgugmen", "keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaɣnu-ik", - "keyboard_shortcuts.notifications": "akken ad d-teldiḍ ajgu n walɣuten", + "keyboard_shortcuts.notifications": "Ad d-yeldi ajgu n yilɣa", "keyboard_shortcuts.open_media": "i tiɣwalin yeldin", "keyboard_shortcuts.pinned": "akken ad teldiḍ tabdart n tjewwiqin yettwasentḍen", "keyboard_shortcuts.profile": "akken ad d-teldiḍ amaɣnu n umeskar", @@ -408,6 +417,7 @@ "notification.admin.sign_up": "Ijerred {name}", "notification.favourite": "{name} yesmenyaf addad-ik·im", "notification.follow": "iṭṭafar-ik·em-id {name}", + "notification.follow.name_and_others": "{name} akked {count, plural, one {# nniḍen} other {# nniḍen}} iḍfeṛ-k·m-id", "notification.follow_request": "{name} yessuter-d ad k·m-yeḍfeṛ", "notification.label.mention": "Abdar", "notification.label.private_mention": "Abdar uslig", @@ -424,11 +434,12 @@ "notification_requests.dismiss": "Agi", "notification_requests.edit_selection": "Ẓreg", "notification_requests.exit_selection": "Immed", - "notification_requests.notifications_from": "Alɣuten sɣur {name}", - "notifications.clear": "Sfeḍ alɣuten", - "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk alɣuten-inek·em i lebda?", + "notification_requests.notifications_from": "Ilɣa sɣur {name}", + "notification_requests.title": "Ilɣa yettwasizdgen", + "notifications.clear": "Sfeḍ ilɣa", + "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk ilɣa-inek·em i lebda?", "notifications.column_settings.admin.report": "Ineqqisen imaynuten:", - "notifications.column_settings.alert": "Alɣuten n tnarit", + "notifications.column_settings.alert": "Ilɣa n tnarit", "notifications.column_settings.favourite": "Imenyafen:", "notifications.column_settings.filter_bar.advanced": "Sken-d akk taggayin", "notifications.column_settings.filter_bar.category": "Iri n usizdeg uzrib", @@ -437,12 +448,12 @@ "notifications.column_settings.group": "Agraw", "notifications.column_settings.mention": "Abdar:", "notifications.column_settings.poll": "Igemmaḍ n usenqed:", - "notifications.column_settings.push": "Alɣuten yettudemmren", + "notifications.column_settings.push": "Ilɣa yettudemmren", "notifications.column_settings.reblog": "Seǧhed:", "notifications.column_settings.show": "Ssken-d tilɣa deg ujgu", "notifications.column_settings.sound": "Rmed imesli", "notifications.column_settings.status": "Tisuffaɣ timaynutin :", - "notifications.column_settings.unread_notifications.category": "Alɣuten ur nettwaɣra", + "notifications.column_settings.unread_notifications.category": "Ilɣa ur nettwaɣra", "notifications.column_settings.update": "Iẓreg:", "notifications.filter.all": "Akk", "notifications.filter.boosts": "Seǧhed", @@ -452,9 +463,9 @@ "notifications.filter.polls": "Igemmaḍ n usenqed", "notifications.filter.statuses": "Ileqman n yimdanen i teṭṭafareḍ", "notifications.grant_permission": "Mudd tasiregt.", - "notifications.group": "{count} n walɣuten", - "notifications.mark_as_read": "Creḍ meṛṛa alɣuten am wakken ttwaɣran", - "notifications.permission_denied": "D awezɣi ad yili wermad n walɣuten n tnarit axateṛ turagt tettwagdel", + "notifications.group": "{count} n yilɣa", + "notifications.mark_as_read": "Creḍ akk ilɣa am wakken ttwaɣran", + "notifications.permission_denied": "D awezɣi ad yili wermad n yilɣa n tnarit axateṛ turagt tettwagdel", "notifications.policy.drop": "Anef-as", "notifications.policy.filter": "Sizdeg", "notifications.policy.filter_new_accounts.hint": "Imiḍanen imaynuten i d-yennulfan deg {days, plural, one {yiwen n wass} other {# n wussan}} yezrin", @@ -464,7 +475,7 @@ "notifications.policy.filter_not_following_hint": "Alamma tqebleḍ-ten s ufus", "notifications.policy.filter_not_following_title": "Wid akked tid ur tettḍafareḍ ara", "notifications.policy.filter_private_mentions_title": "Abdar uslig ur yettwasferken ara", - "notifications_permission_banner.enable": "Rmed alɣuten n tnarit", + "notifications_permission_banner.enable": "Rmed ilɣa n tnarit", "notifications_permission_banner.title": "Ur zeggel acemma", "onboarding.follows.back": "Uɣal", "onboarding.follows.done": "Immed", @@ -632,7 +643,7 @@ "status.unpin": "Kkes asenteḍ seg umaɣnu", "subscribed_languages.save": "Sekles ibeddilen", "tabs_bar.home": "Agejdan", - "tabs_bar.notifications": "Alɣuten", + "tabs_bar.notifications": "Ilɣa", "terms_of_service.title": "Tiwtilin n useqdec", "time_remaining.days": "Mazal {number, plural, one {# wass} other {# wussan}}", "time_remaining.hours": "Mazal {number, plural, one {# usarag} other {# yisragen}}", diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json index 120e2415b5..adc3cdc230 100644 --- a/app/javascript/mastodon/locales/kk.json +++ b/app/javascript/mastodon/locales/kk.json @@ -25,7 +25,6 @@ "account.enable_notifications": "@{name} постары туралы ескерту", "account.endorse": "Профильде ұсыну", "account.featured_tags.last_status_never": "Пост жоқ", - "account.featured_tags.title": "{name} таңдаулы хэштегтері", "account.follow": "Жазылу", "account.followers": "Жазылушы", "account.followers.empty": "Бұл қолданушыға әлі ешкім жазылмаған.", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 76f94fb43f..544278e519 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -27,9 +27,11 @@ "account.edit_profile": "프로필 편집", "account.enable_notifications": "@{name} 의 게시물 알림 켜기", "account.endorse": "프로필에 추천하기", + "account.featured": "추천", + "account.featured.hashtags": "해시태그", + "account.featured.posts": "게시물", "account.featured_tags.last_status_at": "{date}에 마지막으로 게시", "account.featured_tags.last_status_never": "게시물 없음", - "account.featured_tags.title": "{name} 님의 추천 해시태그", "account.follow": "팔로우", "account.follow_back": "맞팔로우", "account.followers": "팔로워", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, other {게시물 {counter}개}}", "account.unblock": "차단 해제", "account.unblock_domain": "도메인 {domain} 차단 해제", + "account.unblock_domain_short": "차단 해제", "account.unblock_short": "차단 해제", "account.unendorse": "프로필에 추천하지 않기", "account.unfollow": "언팔로우", @@ -293,6 +296,7 @@ "emoji_button.search_results": "검색 결과", "emoji_button.symbols": "기호", "emoji_button.travel": "여행과 장소", + "empty_column.account_featured": "목록이 비어있습니다", "empty_column.account_hides_collections": "이 사용자는 이 정보를 사용할 수 없도록 설정했습니다", "empty_column.account_suspended": "계정 정지됨", "empty_column.account_timeline": "이곳에는 게시물이 없습니다!", @@ -377,6 +381,8 @@ "generic.saved": "저장됨", "getting_started.heading": "시작하기", "hashtag.admin_moderation": "#{name}에 대한 중재화면 열기", + "hashtag.browse": "#{hashtag}의 게시물 둘러보기", + "hashtag.browse_from_account": "@{name}의 #{hashtag} 게시물 둘러보기", "hashtag.column_header.tag_mode.all": "및 {additional}", "hashtag.column_header.tag_mode.any": "또는 {additional}", "hashtag.column_header.tag_mode.none": "{additional}를 제외하고", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, other {게시물 {counter}개}}", "hashtag.counter_by_uses_today": "오늘 {count, plural, other {{counter} 개의 게시물}}", "hashtag.follow": "해시태그 팔로우", + "hashtag.mute": "#{hashtag} 뮤트", "hashtag.unfollow": "해시태그 팔로우 해제", "hashtags.and_other": "…및 {count, plural,other {#개}}", "hints.profiles.followers_may_be_missing": "이 프로필의 팔로워 목록은 일부 누락되었을 수 있습니다.", diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json index f867908f37..23ee9fc932 100644 --- a/app/javascript/mastodon/locales/ku.json +++ b/app/javascript/mastodon/locales/ku.json @@ -29,7 +29,6 @@ "account.endorse": "Taybetiyên li ser profîl", "account.featured_tags.last_status_at": "Şandiya dawî di {date} de", "account.featured_tags.last_status_never": "Şandî tune ne", - "account.featured_tags.title": "{name}'s hashtagên taybet", "account.follow": "Bişopîne", "account.follow_back": "Bişopîne", "account.followers": "Şopîner", diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json index f422230cb8..c6e5d85c07 100644 --- a/app/javascript/mastodon/locales/la.json +++ b/app/javascript/mastodon/locales/la.json @@ -23,7 +23,6 @@ "account.domain_blocked": "Dominium impeditum", "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} sectans} other {{counter} sectans}}", "account.moved_to": "{name} significavit eum suam rationem novam nunc esse:", diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index ab410dc2a8..f67ef676ad 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -29,7 +29,6 @@ "account.endorse": "Avalia en profil", "account.featured_tags.last_status_at": "Ultima publikasyon de {date}", "account.featured_tags.last_status_never": "No ay publikasyones", - "account.featured_tags.title": "Etiketas avaliadas de {name}", "account.follow": "Sige", "account.follow_back": "Sige tamyen", "account.followers": "Suivantes", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}", "account.unblock": "Dezbloka a @{name}", "account.unblock_domain": "Dezbloka domeno {domain}", + "account.unblock_domain_short": "Dezbloka", "account.unblock_short": "Dezbloka", "account.unendorse": "No avalia en profil", "account.unfollow": "Desige", @@ -86,6 +86,7 @@ "alert.unexpected.message": "Afito un yerro no asperado.", "alert.unexpected.title": "Atyo!", "alt_text_badge.title": "Teksto alternativo", + "alt_text_modal.add_alt_text": "Adjusta teksto alternativo", "alt_text_modal.cancel": "Anula", "alt_text_modal.change_thumbnail": "Troka minyatura", "alt_text_modal.done": "Fecho", @@ -195,9 +196,12 @@ "confirmations.discard_edit_media.message": "Tienes trokamientos no guadrados en la deskripsion o vista previa. Keres efasarlos entanto?", "confirmations.edit.confirm": "Edita", "confirmations.edit.message": "Si edites agora, kitaras el mesaj kualo estas eskriviendo aktualmente. Estas siguro ke keres fazerlo?", + "confirmations.follow_to_list.title": "Segir utilizador?", "confirmations.logout.confirm": "Sal", "confirmations.logout.message": "Estas siguro ke keres salir de tu kuento?", "confirmations.logout.title": "Salir?", + "confirmations.missing_alt_text.confirm": "Adjusta teksto alternativo", + "confirmations.missing_alt_text.title": "Adjustar teksto alternativo?", "confirmations.mute.confirm": "Silensia", "confirmations.redraft.confirm": "Efasa i reeskrive", "confirmations.redraft.message": "Estas siguro ke keres efasar esta publikasyon i reeskrivirla? Pedreras todos los favoritos i repartajasyones asosiados kon esta publikasyon i repuestas a eya seran guerfanadas.", @@ -372,6 +376,7 @@ "ignore_notifications_modal.not_followers_title": "Inyorar avizos de personas a las kualas no te sigen?", "ignore_notifications_modal.not_following_title": "Inyorar avizos de personas a las kualas no siges?", "ignore_notifications_modal.private_mentions_title": "Ignorar avizos de mensyones privadas no solisitadas?", + "info_button.label": "Ayuda", "interaction_modal.on_another_server": "En otro sirvidor", "interaction_modal.on_this_server": "En este sirvidor", "interaction_modal.title.favourite": "Endika ke te plaze publikasyon de {name}", @@ -426,6 +431,7 @@ "link_preview.shares": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}", "lists.add_member": "Adjusta", "lists.add_to_list": "Adjusta a lista", + "lists.create": "Kriya", "lists.create_list": "Kriya lista", "lists.delete": "Efasa lista", "lists.done": "Fecho", @@ -588,6 +594,7 @@ "poll_button.remove_poll": "Kita anketa", "privacy.change": "Troka privasita de publikasyon", "privacy.direct.long": "Todos enmentados en la publikasyon", + "privacy.direct.short": "Enmentadura privada", "privacy.private.long": "Solo para tus suivantes", "privacy.private.short": "Suivantes", "privacy.public.long": "Todos en i afuera de Mastodon", @@ -682,6 +689,7 @@ "search_results.accounts": "Profiles", "search_results.all": "Todos", "search_results.hashtags": "Etiketas", + "search_results.no_results": "No ay rezultados.", "search_results.see_all": "Ve todo", "search_results.statuses": "Publikasyones", "search_results.title": "Bushka por \"{q}\"", @@ -775,6 +783,8 @@ "video.expand": "Espande video", "video.fullscreen": "Ekran kompleto", "video.hide": "Eskonde video", + "video.mute": "Silensia", "video.pause": "Pauza", - "video.play": "Reproduze" + "video.play": "Reproduze", + "video.unmute": "Desilensia" } diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index f112bc73d9..7110e809c1 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -29,7 +29,6 @@ "account.endorse": "Rodyti profilyje", "account.featured_tags.last_status_at": "Paskutinis įrašas {date}", "account.featured_tags.last_status_never": "Nėra įrašų", - "account.featured_tags.title": "{name} rodomi saitažodžiai", "account.follow": "Sekti", "account.follow_back": "Sekti atgal", "account.followers": "Sekėjai", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 203b321574..27760c59ff 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -29,7 +29,6 @@ "account.endorse": "Izcelts profilā", "account.featured_tags.last_status_at": "Beidzamā ziņa {date}", "account.featured_tags.last_status_never": "Ierakstu nav", - "account.featured_tags.title": "{name} izceltie tēmturi", "account.follow": "Sekot", "account.follow_back": "Sekot atpakaļ", "account.followers": "Sekotāji", @@ -54,7 +53,7 @@ "account.muted": "Apklusināts", "account.mutual": "Abpusēji", "account.no_bio": "Apraksts nav sniegts.", - "account.open_original_page": "Atvērt oriģinālo lapu", + "account.open_original_page": "Atvērt pirmavota lapu", "account.posts": "Ieraksti", "account.posts_with_replies": "Ieraksti un atbildes", "account.report": "Sūdzēties par @{name}", @@ -214,7 +213,7 @@ "confirmations.missing_alt_text.secondary": "Vienalga iesūtīt", "confirmations.mute.confirm": "Apklusināt", "confirmations.redraft.confirm": "Dzēst un pārrakstīt", - "confirmations.redraft.message": "Vai tiešām vēlies dzēst šo ziņu un no jauna noformēt to? Izlase un pastiprinājumi tiks zaudēti, un atbildes uz sākotnējo ziņu tiks atstātas bez autoratlīdzības.", + "confirmations.redraft.message": "Vai tiešām vēlies izdzēst šo ierakstu un veidot jaunu tā uzmetumu? Pievienošana izlasēs un pastiprinājumi tiks zaudēti, un sākotnējā ieraksta atbildes paliks bez saiknes ar to.", "confirmations.redraft.title": "Dzēst un rakstīt vēlreiz?", "confirmations.reply.confirm": "Atbildēt", "confirmations.reply.message": "Tūlītēja atbildēšana pārrakstīs pašlaik sastādīto ziņu. Vai tiešām turpināt?", @@ -597,7 +596,7 @@ "report.close": "Darīts", "report.comment.title": "Vai, tavuprāt, mums vēl būtu kas jāzina?", "report.forward": "Pārsūtīt {target}", - "report.forward_hint": "Konts ir no cita servera. Vai nosūtīt anonimizētu sūdzības kopiju arī tam?", + "report.forward_hint": "Konts ir no cita servera. Vai nosūtīt anonimizētu ziņojuma kopiju arī tur?", "report.mute": "Apklusināt", "report.mute_explanation": "Tu neredzēsi viņu ierakstus. Viņi joprojām var Tev sekot un redzēt Tavus ierakstus un nezinās, ka viņi ir apklusināti.", "report.next": "Tālāk", @@ -692,7 +691,7 @@ "status.pinned": "Piesprausts ieraksts", "status.read_more": "Lasīt vairāk", "status.reblog": "Pastiprināt", - "status.reblog_private": "Pastiprināt, nemainot redzamību", + "status.reblog_private": "Pastiprināt ar sākotnējo redzamību", "status.reblogged_by": "{name} pastiprināja", "status.reblogs": "{count, plural, zero {pastiprinājumu} one {pastiprinājums} other {pastiprinājumi}}", "status.reblogs.empty": "Neviens šo ierakstu vēl nav pastiprinājis. Kad būs, tie parādīsies šeit.", @@ -706,7 +705,7 @@ "status.share": "Kopīgot", "status.show_less_all": "Rādīt mazāk visiem", "status.show_more_all": "Rādīt vairāk visiem", - "status.show_original": "Rādīt oriģinālu", + "status.show_original": "Rādīt pirmavotu", "status.title.with_attachments": "{user} publicējis {attachmentCount, plural, one {pielikumu} other {{attachmentCount} pielikumus}}", "status.translate": "Tulkot", "status.translated_from_with": "Tulkots no {lang} izmantojot {provider}", diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json index 94f9aef261..919a34532f 100644 --- a/app/javascript/mastodon/locales/mr.json +++ b/app/javascript/mastodon/locales/mr.json @@ -28,7 +28,6 @@ "account.endorse": "प्रोफाइलवरील वैशिष्ट्य", "account.featured_tags.last_status_at": "शेवटचे पोस्ट {date} रोजी", "account.featured_tags.last_status_never": "पोस्ट नाहीत", - "account.featured_tags.title": "{name} चे वैशिष्ट्यीकृत हॅशटॅग", "account.follow": "अनुयायी व्हा", "account.follow_back": "आपणही अनुसरण करा", "account.followers": "अनुयायी", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index af80cc20cf..483261da6b 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -1,5 +1,5 @@ { - "about.blocks": "Pelayan yang disederhanakan", + "about.blocks": "Pelayan yang diselaraskan", "about.contact": "Hubungi:", "about.disclaimer": "Mastodon ialah perisian sumber terbuka percuma, dan merupakan tanda dagangan Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Sebab tidak tersedia", @@ -11,13 +11,13 @@ "about.not_available": "Maklumat ini belum tersedia pada pelayan ini.", "about.powered_by": "Media sosial terpencar yang dikuasakan oleh {mastodon}", "about.rules": "Peraturan pelayan", - "account.account_note_header": "Personal note", + "account.account_note_header": "Catatan peribadi", "account.add_or_remove_from_list": "Tambah atau Buang dari senarai", - "account.badges.bot": "Bot", + "account.badges.bot": "Automatik", "account.badges.group": "Kumpulan", "account.block": "Sekat @{name}", "account.block_domain": "Sekat domain {domain}", - "account.block_short": "Malay", + "account.block_short": "Sekat", "account.blocked": "Disekat", "account.cancel_follow_request": "Batalkan permintaan ikut", "account.copy": "Salin pautan ke profil", @@ -29,29 +29,28 @@ "account.endorse": "Tampilkan di profil", "account.featured_tags.last_status_at": "Hantaran terakhir pada {date}", "account.featured_tags.last_status_never": "Tiada hantaran", - "account.featured_tags.title": "Tanda pagar pilihan {name}", "account.follow": "Ikuti", "account.follow_back": "Ikut balik", "account.followers": "Pengikut", "account.followers.empty": "Belum ada yang mengikuti pengguna ini.", - "account.followers_counter": "{count, plural, one {{counter} Diikuti} other {{counter} Diikuti}}", - "account.following": "Mengikuti", - "account.following_counter": "{count, plural, other {{counter} following}}", + "account.followers_counter": "{count, plural, one {{counter} pengikut} other {{counter} pengikut}}", + "account.following": "Ikutan", + "account.following_counter": "{count, plural, other {{counter} ikutan}}", "account.follows.empty": "Pengguna ini belum mengikuti sesiapa.", "account.go_to_profile": "Pergi ke profil", "account.hide_reblogs": "Sembunyikan galakan daripada @{name}", - "account.in_memoriam": "Dalam Memoriam.", - "account.joined_short": "Menyertai", - "account.languages": "Tukar bahasa yang dilanggan", + "account.in_memoriam": "Dalam kenangan.", + "account.joined_short": "Tarikh penyertaan", + "account.languages": "Tukar bahasa langganan", "account.link_verified_on": "Pemilikan pautan ini telah disemak pada {date}", - "account.locked_info": "Status privasi akaun ini dikunci. Pemiliknya menyaring sendiri siapa yang boleh mengikutinya.", + "account.locked_info": "Taraf privasi akaun ini dikunci. Pemiliknya menyaring sendiri siapa yang boleh mengikutinya.", "account.media": "Media", "account.mention": "Sebut @{name}", "account.moved_to": "{name} telah menandakan bahawa akaun baru mereka sekarang ialah:", - "account.mute": "Bisukan @{name}", - "account.mute_notifications_short": "Redam pemberitahuan", + "account.mute": "Redamkan @{name}", + "account.mute_notifications_short": "Redamkan pemberitahuan", "account.mute_short": "Redam", - "account.muted": "Dibisukan", + "account.muted": "Diredamkan", "account.mutual": "Rakan kongsi", "account.no_bio": "Tiada penerangan diberikan.", "account.open_original_page": "Buka halaman asal", @@ -65,12 +64,13 @@ "account.statuses_counter": "{count, plural, other {{counter} siaran}}", "account.unblock": "Nyahsekat @{name}", "account.unblock_domain": "Nyahsekat domain {domain}", + "account.unblock_domain_short": "Nyahsekat", "account.unblock_short": "Nyahsekat", "account.unendorse": "Jangan tampilkan di profil", "account.unfollow": "Nyahikut", "account.unmute": "Nyahbisukan @{name}", - "account.unmute_notifications_short": "Nyahredam notifikasi", - "account.unmute_short": "Nyahbisu", + "account.unmute_notifications_short": "Nyahredamkan pemberitahuan", + "account.unmute_short": "Nyahredam", "account_note.placeholder": "Klik untuk menambah catatan", "admin.dashboard.daily_retention": "Kadar pengekalan pengguna mengikut hari selepas mendaftar", "admin.dashboard.monthly_retention": "Kadar pengekalan pengguna mengikut bulan selepas mendaftar", @@ -116,6 +116,8 @@ "attachments_list.unprocessed": "(belum diproses)", "audio.hide": "Sembunyikan audio", "block_modal.remote_users_caveat": "Kami akan meminta pelayan {domain} untuk menghormati keputusan anda. Bagaimanapun, pematuhan tidak dijamin kerana ada pelayan yang mungkin menangani sekatan dengan cara berbeza. Hantaran awam mungkin masih tampak kepada pengguna yang tidak log masuk.", + "block_modal.show_less": "Tunjuk kurang", + "block_modal.show_more": "Tunjuk lebih", "block_modal.they_cant_mention": "Dia tidak boleh menyebut tentang anda atau mengikut anda.", "block_modal.they_cant_see_posts": "Dia tidak boleh melihat hantaran anda dan sebaliknya.", "block_modal.they_will_know": "Dia boleh lihat bahawa dia disekat.", @@ -142,13 +144,13 @@ "closed_registrations_modal.preamble": "Mastodon adalah terpencar, oleh itu di mana-mana anda mencipta akaun anda, anda boleh mengikut dan berinteraksi dengan sesiapa pada pelayan ini. Anda juga boleh hos sendiri!", "closed_registrations_modal.title": "Mendaftar pada Mastodon", "column.about": "Perihal", - "column.blocks": "Pengguna yang disekat", + "column.blocks": "Pengguna tersekat", "column.bookmarks": "Tanda buku", "column.community": "Garis masa tempatan", "column.create_list": "Cipta senarai", "column.direct": "Sebutan peribadi", "column.directory": "Layari profil", - "column.domain_blocks": "Domain disekat", + "column.domain_blocks": "Domain tersekat", "column.edit_list": "Sunting senarai", "column.favourites": "Sukaan", "column.firehose": "Suapan langsung", @@ -156,7 +158,7 @@ "column.home": "Laman Utama", "column.list_members": "Urus ahli senarai", "column.lists": "Senarai", - "column.mutes": "Pengguna yang dibisukan", + "column.mutes": "Pengguna teredam", "column.notifications": "Pemberitahuan", "column.pins": "Hantaran disemat", "column.public": "Garis masa bersekutu", @@ -218,13 +220,21 @@ "confirmations.logout.title": "Log keluar?", "confirmations.missing_alt_text.confirm": "Tambah teks alternatif", "confirmations.missing_alt_text.message": "Hantaran anda mempunyai media tanpa teks alternatif. Kandungan anda akan lebih mudah tercapai jika anda menambah keterangan.", - "confirmations.mute.confirm": "Bisukan", + "confirmations.missing_alt_text.secondary": "Hantar saja", + "confirmations.missing_alt_text.title": "Tambah teks alternatif?", + "confirmations.mute.confirm": "Redamkan", "confirmations.redraft.confirm": "Padam & rangka semula", "confirmations.redraft.message": "Adakah anda pasti anda ingin memadam hantaran ini dan gubal semula? Sukaan dan galakan akan hilang, dan balasan ke hantaran asal akan menjadi yatim.", + "confirmations.redraft.title": "Padam & gubah semula hantaran?", "confirmations.reply.confirm": "Balas", "confirmations.reply.message": "Membalas sekarang akan menulis ganti mesej yang anda sedang karang. Adakah anda pasti anda ingin teruskan?", + "confirmations.reply.title": "Tulis ganti hantaran?", "confirmations.unfollow.confirm": "Nyahikut", "confirmations.unfollow.message": "Adakah anda pasti anda ingin nyahikuti {name}?", + "confirmations.unfollow.title": "Berhenti mengikut pengguna?", + "content_warning.hide": "Sorok hantaran", + "content_warning.show": "Tunjuk saja", + "content_warning.show_more": "Tunjuk lebih", "conversation.delete": "Padam perbualan", "conversation.mark_as_read": "Tanda sudah dibaca", "conversation.open": "Lihat perbualan", @@ -241,6 +251,22 @@ "dismissable_banner.community_timeline": "Inilah hantaran awam terkini daripada orang yang akaun dihos oleh {domain}.", "dismissable_banner.dismiss": "Ketepikan", "dismissable_banner.explore_statuses": "Hantaran-hantaran dari seluruh alam bersekutu ini sedang sohor. Hantaran terbaharu dengan lebih banyak galakan dan sukaan diberi kedudukan lebih tinggi.", + "dismissable_banner.public_timeline": "Hantaran-hantaran awam terkini ini dari pengguna alam bersekutu yang diikuti oleh pengguna dari {domain}.", + "domain_block_modal.block": "Sekat pelayan", + "domain_block_modal.block_account_instead": "Sekat @{name} sahaja", + "domain_block_modal.they_can_interact_with_old_posts": "Pengguna dari pelayan ini boleh berinteraksi dengan hantaran lama anda.", + "domain_block_modal.they_cant_follow": "Pengguna dari pelayan ini tidak boleh mengikuti anda.", + "domain_block_modal.they_wont_know": "Dia tidak akan tahu bahawa dia telah disekat.", + "domain_block_modal.title": "Sekat domain?", + "domain_block_modal.you_will_lose_num_followers": "Anda akan kehilangan {followersCount, plural, other {{followersCountDisplay} pengikut}} dan {followingCount, plural, other {{followingCountDisplay} ikutan}}.", + "domain_block_modal.you_will_lose_relationships": "Anda akan kehilangan semua pengikut dan ikutan anda dari pelayan ini.", + "domain_block_modal.you_wont_see_posts": "Anda tidak akan melihat hantaran atau pemberitahuan dari pengguna pada pelayan ini.", + "domain_pill.activitypub_lets_connect": "Hal ini membolehkan anda berhubung dan berinteraksi bukan sahaja dengan pengguna Mastodon tetapi melintasi pelbagai aplikasi sosial juga.", + "domain_pill.activitypub_like_language": "ActivityPub adalah seperti bahasa yang digunakan oleh Mastodon untuk berhubung dengan jaringan sosial lain.", + "domain_pill.server": "Pelayan", + "domain_pill.your_handle": "Pemegang anda:", + "domain_pill.your_server": "Rumah maya anda, tempatnya hantaran anda disimpan. Tidak berkenan dengan yang ini? Pindah antara pelayan pada bila-bila masa dan bawa pengikut anda sekali.", + "domain_pill.your_username": "Pengenal unik anda pada pelayan ini. Anda mungkin akan berkongsi nama pengguna dengan pengguna daripada pelayan lain.", "embed.instructions": "Benam hantaran ini di laman sesawang anda dengan menyalin kod berikut.", "embed.preview": "Begini rupanya nanti:", "emoji_button.activity": "Aktiviti", @@ -258,6 +284,7 @@ "emoji_button.search_results": "Hasil carian", "emoji_button.symbols": "Simbol", "emoji_button.travel": "Kembara & Tempat", + "empty_column.account_featured": "Senarai ini kosong", "empty_column.account_hides_collections": "Pengguna ini telah memilih untuk tidak menyediakan informasi tersebut", "empty_column.account_suspended": "Akaun digantung", "empty_column.account_timeline": "Tiada hantaran di sini!", @@ -275,7 +302,7 @@ "empty_column.hashtag": "Belum ada apa-apa dengan tanda pagar ini.", "empty_column.home": "Garis masa laman utama anda kosong! Ikuti lebih ramai orang untuk mengisinya. {suggestions}", "empty_column.list": "Tiada apa-apa di senarai ini lagi. Apabila ahli senarai ini menerbitkan hantaran baharu, ia akan dipaparkan di sini.", - "empty_column.mutes": "Anda belum membisukan sesiapa.", + "empty_column.mutes": "Anda belum meredamkan sesiapa.", "empty_column.notifications": "Anda belum ada sebarang pemberitahuan. Apabila orang lain berinteraksi dengan anda, ia akan muncul di sini.", "empty_column.public": "Tiada apa-apa di sini! Tulis sesuatu secara awam, atau ikuti pengguna daripada pelayan lain secara manual untuk mengisinya", "error.unexpected_crash.explanation": "Disebabkan pepijat dalam kod kami atau masalah keserasian pelayar, halaman ini tidak dapat dipaparkan dengan betulnya.", @@ -342,6 +369,7 @@ "hashtag.counter_by_uses": "{count, plural, other {{counter} siaran}}", "hashtag.counter_by_uses_today": "{count, plural, other {{counter} siaran}} hari ini", "hashtag.follow": "Ikuti hashtag", + "hashtag.mute": "Redamkan #{hashtag}", "hashtag.unfollow": "Nyahikut tanda pagar", "hashtags.and_other": "…dan {count, plural, other {# more}}", "home.column_settings.show_reblogs": "Tunjukkan galakan", @@ -362,7 +390,7 @@ "intervals.full.hours": "{number, plural, other {# jam}}", "intervals.full.minutes": "{number, plural, other {# minit}}", "keyboard_shortcuts.back": "to navigate back", - "keyboard_shortcuts.blocked": "to open blocked users list", + "keyboard_shortcuts.blocked": "Buka senarai pengguna tersekat", "keyboard_shortcuts.boost": "to boost", "keyboard_shortcuts.column": "Tumpu pada lajur", "keyboard_shortcuts.compose": "to focus the compose textarea", @@ -379,7 +407,7 @@ "keyboard_shortcuts.legend": "to display this legend", "keyboard_shortcuts.local": "to open local timeline", "keyboard_shortcuts.mention": "to mention author", - "keyboard_shortcuts.muted": "to open muted users list", + "keyboard_shortcuts.muted": "Buka senarai pengguna teredam", "keyboard_shortcuts.my_profile": "to open your profile", "keyboard_shortcuts.notifications": "to open notifications column", "keyboard_shortcuts.open_media": "to open media", @@ -409,24 +437,27 @@ "load_pending": "{count, plural, one {# item baharu} other {# item baharu}}", "loading_indicator.label": "Memuatkan…", "moved_to_account_banner.text": "Akaun anda {disabledAccount} kini dinyahdayakan kerana anda berpindah ke {movedToAccount}.", + "mute_modal.indefinite": "Sehingga dinyahredamkan", + "mute_modal.they_wont_know": "Dia tidak akan tahu bahawa dia telah diredam.", + "mute_modal.title": "Redamkan pengguna?", "navigation_bar.about": "Perihal", "navigation_bar.advanced_interface": "Buka dalam antara muka web lanjutan", - "navigation_bar.blocks": "Pengguna yang disekat", + "navigation_bar.blocks": "Pengguna tersekat", "navigation_bar.bookmarks": "Tanda buku", "navigation_bar.community_timeline": "Garis masa tempatan", "navigation_bar.compose": "Karang hantaran baharu", "navigation_bar.direct": "Sebutan peribadi", "navigation_bar.discover": "Teroka", - "navigation_bar.domain_blocks": "Domain disekat", + "navigation_bar.domain_blocks": "Domain tersekat", "navigation_bar.explore": "Teroka", "navigation_bar.favourites": "Sukaan", - "navigation_bar.filters": "Perkataan yang dibisukan", + "navigation_bar.filters": "Perkataan teredam", "navigation_bar.follow_requests": "Permintaan ikutan", "navigation_bar.followed_tags": "Ikuti hashtag", "navigation_bar.follows_and_followers": "Ikutan dan pengikut", "navigation_bar.lists": "Senarai", "navigation_bar.logout": "Log keluar", - "navigation_bar.mutes": "Pengguna yang dibisukan", + "navigation_bar.mutes": "Pengguna teredam", "navigation_bar.opened_in_classic_interface": "Kiriman, akaun dan halaman tertentu yang lain dibuka secara lalai di antara muka web klasik.", "navigation_bar.personal": "Peribadi", "navigation_bar.pins": "Hantaran disemat", @@ -446,6 +477,8 @@ "notification.own_poll": "Undian anda telah tamat", "notification.reblog": "{name} menggalak hantaran anda", "notification.reblog.name_and_others_with_link": "{name} dan {count, plural, other {# orang lain}} telah galakkan hantaran anda", + "notification.relationships_severance_event.domain_block": "Pentadbir dari {from} telah menyekat {target} termasuk {followersCount} pengikut anda dan {followingCount, plural, other {# akaun}} ikutan anda.", + "notification.relationships_severance_event.user_domain_block": "Anda telah menyekat {target} termasuk {followersCount} pengikut anda dan {followingCount, plural, other {# akaun}} ikutan anda.", "notification.status": "{name} baru sahaja mengirim hantaran", "notification.update": "{name} menyunting hantaran", "notifications.clear": "Buang pemberitahuan", @@ -525,7 +558,7 @@ "reply_indicator.cancel": "Batal", "reply_indicator.poll": "Undian", "report.block": "Sekat", - "report.block_explanation": "Anda tidak akan melihat hantaran mereka. Mereka tidak dapat melihat hantaran anda atau mengikuti anda. Mereka akan sedar bahawa mereka disekat.", + "report.block_explanation": "Anda tidak akan melihat hantarannya. Dia tidak akan dapat melihat hantaran anda atau mengikuti anda. Dia akan sedar bahawa dia disekat.", "report.categories.legal": "Sah", "report.categories.other": "Lain-lain", "report.categories.spam": "Spam", @@ -538,7 +571,7 @@ "report.comment.title": "Adakah ada hal-hal lain yang perlu kita ketahui?", "report.forward": "Panjangkan ke {target}", "report.forward_hint": "Akaun ini daripada pelayan lain. Hantar salinan laporan yang ditanpanamakan ke sana juga?", - "report.mute": "Bisukan", + "report.mute": "Redam", "report.mute_explanation": "Anda tidak akan melihat siaran mereka. Mereka masih boleh mengikuti dan melihat siaran anda dan tidak akan mengetahui bahawa mereka telah dibisukan.", "report.next": "Seterusnya", "report.placeholder": "Ulasan tambahan", @@ -622,8 +655,8 @@ "status.media_hidden": "Media disembunyikan", "status.mention": "Sebut @{name}", "status.more": "Lagi", - "status.mute": "Bisukan @{name}", - "status.mute_conversation": "Bisukan perbualan", + "status.mute": "Redamkan @{name}", + "status.mute_conversation": "Redamkan perbualan", "status.open": "Kembangkan hantaran ini", "status.pin": "Semat di profil", "status.pinned": "Hantaran disemat", @@ -649,7 +682,7 @@ "status.translate": "Menterjemah", "status.translated_from_with": "Diterjemah daripada {lang} dengan {provider}", "status.uncached_media_warning": "Pratonton tidak tersedia", - "status.unmute_conversation": "Nyahbisukan perbualan", + "status.unmute_conversation": "Nyahredamkan perbualan", "status.unpin": "Nyahsemat daripada profil", "subscribed_languages.lead": "Hanya hantaran dalam bahasa-bahasa terpilih akan dipaparkan pada garis masa rumah dan senarai selepas perubahan. Pilih tiada untuk menerima hantaran dalam semua bahasa.", "subscribed_languages.save": "Simpan perubahan", @@ -683,6 +716,8 @@ "video.expand": "Besarkan video", "video.fullscreen": "Skrin penuh", "video.hide": "Sembunyikan video", + "video.mute": "Redam", "video.pause": "Jeda", - "video.play": "Main" + "video.play": "Main", + "video.unmute": "Nyahredam" } diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json index 51cba22153..362537edeb 100644 --- a/app/javascript/mastodon/locales/my.json +++ b/app/javascript/mastodon/locales/my.json @@ -28,7 +28,6 @@ "account.endorse": "အကောင့်ပရိုဖိုင်တွင်ဖော်ပြပါ", "account.featured_tags.last_status_at": "နောက်ဆုံးပို့စ်ကို {date} တွင် တင်ခဲ့သည်။", "account.featured_tags.last_status_never": "ပို့စ်တင်ထားခြင်းမရှိပါ", - "account.featured_tags.title": "ဖော်ပြထားသောဟက်ရှ်တက်ခ်များ", "account.follow": "စောင့်ကြည့်", "account.followers": "စောင့်ကြည့်သူများ", "account.followers.empty": "ဤသူကို စောင့်ကြည့်သူ မရှိသေးပါ။", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 57eef0a874..2c5af1d406 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -27,9 +27,11 @@ "account.edit_profile": "編輯個人資料", "account.enable_notifications": "佇 {name} PO文ê時通知我", "account.endorse": "用個人資料推薦對方", + "account.featured": "精選ê", + "account.featured.hashtags": "Hashtag", + "account.featured.posts": "PO文", "account.featured_tags.last_status_at": "頂kái tī {date} Po文", "account.featured_tags.last_status_never": "無PO文", - "account.featured_tags.title": "{name} ê推薦hashtag", "account.follow": "跟tuè", "account.follow_back": "Tuè tńg去", "account.followers": "跟tuè lí ê", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, other {{count} ê PO文}}", "account.unblock": "取消封鎖 @{name}", "account.unblock_domain": "Kā域名 {domain} 取消封鎖", + "account.unblock_domain_short": "取消封鎖", "account.unblock_short": "取消封鎖", "account.unendorse": "Mài tī個人資料推薦伊", "account.unfollow": "取消跟tuè", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Tshiau-tshuē ê結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅行kap地點", + "empty_column.account_featured": "Tsit ê列單是空ê", "empty_column.account_hides_collections": "Tsit位用者選擇無愛公開tsit ê資訊", "empty_column.account_suspended": "口座已經受停止", "empty_column.account_timeline": "Tsia無PO文!", @@ -377,6 +381,8 @@ "generic.saved": "儲存ah", "getting_started.heading": "開始用", "hashtag.admin_moderation": "Phah開 #{name} ê管理界面", + "hashtag.browse": "瀏覽佇 #{hashtag} ê PO文", + "hashtag.browse_from_account": "瀏覽 @{name} 佇 #{hashtag} 所寫ê PO文", "hashtag.column_header.tag_mode.all": "kap {additional}", "hashtag.column_header.tag_mode.any": "á是 {additional}", "hashtag.column_header.tag_mode.none": "無需要 {additional}", @@ -387,9 +393,10 @@ "hashtag.column_settings.tag_mode.none": "Lóng mài", "hashtag.column_settings.tag_toggle": "Kā追加ê標籤加添kàu tsit ê欄", "hashtag.counter_by_accounts": "{count, plural, one {{counter} ê} other {{counter} ê}}參與ê", - "hashtag.counter_by_uses": "{count, plural, one {{counter} ê} other {{counter} ê}} PO文", - "hashtag.counter_by_uses_today": "Kin-á日有 {count, plural, one {{counter} ê} other {{counter} ê}} PO文", + "hashtag.counter_by_uses": "{count, plural, one {{counter} 篇} other {{counter} 篇}} PO文", + "hashtag.counter_by_uses_today": "Kin-á日有 {count, plural, one {{counter} 篇} other {{counter} 篇}} PO文", "hashtag.follow": "跟tuè hashtag", + "hashtag.mute": "消音 #{hashtag}", "hashtag.unfollow": "取消跟tuè hashtag", "hashtags.and_other": "……kap 其他 {count, plural, other {# ê}}", "hints.profiles.followers_may_be_missing": "Tsit ê個人資料ê跟tuè者資訊可能有落勾ê。", @@ -468,8 +475,119 @@ "keyboard_shortcuts.spoilers": "顯示/隱藏內容警告", "keyboard_shortcuts.start": "Phah開「開始用」欄", "keyboard_shortcuts.toggle_hidden": "顯示/隱藏內容警告後壁ê PO文", + "keyboard_shortcuts.toggle_sensitivity": "顯示/tshàng媒體", + "keyboard_shortcuts.toot": "PO新PO文", + "keyboard_shortcuts.translate": "kā PO文翻譯", + "keyboard_shortcuts.unfocus": "離開輸入框仔/tshiau-tshuē格仔", + "keyboard_shortcuts.up": "佇列單內kā suá khah面頂", + "lightbox.close": "關", + "lightbox.next": "下tsi̍t ê", + "lightbox.previous": "頂tsi̍t ê", + "lightbox.zoom_in": "Tshūn-kiu kàu實際ê sài-suh", + "lightbox.zoom_out": "Tshūn-kiu kàu適當ê sài-suh", + "limited_account_hint.action": "一直顯示個人資料", + "limited_account_hint.title": "Tsit ê 個人資料予 {domain} ê管理員tshàng起來ah。", + "link_preview.author": "Tuì {name}", + "link_preview.more_from_author": "看 {name} ê其他內容", + "link_preview.shares": "{count, plural, one {{counter} 篇} other {{counter} 篇}} PO文", + "lists.add_member": "加", + "lists.add_to_list": "加添kàu列單", + "lists.add_to_lists": "Kā {name} 加添kàu列單", + "lists.create": "建立", + "lists.create_a_list_to_organize": "開新ê列單,組織lí tshù ê時間線", + "lists.create_list": "建立列單", + "lists.delete": "Thâi掉列單", + "lists.done": "做好ah", + "lists.edit": "編輯列單", + "lists.exclusive": "佇tshù ê時間線kā成員tshàng起來。", + "lists.exclusive_hint": "Nā bóo-mi̍h口座佇tsit ê列單,ē tuì lí tshù ê時間線kā tsit ê口座tshàng起來,避免koh看見in ê PO文。", + "lists.find_users_to_add": "Tshuē beh加添ê用者", + "lists.list_members": "列單ê成員", + "lists.list_members_count": "{count, plural, other {# 位成員}}", + "lists.list_name": "列單ê名", + "lists.new_list_name": "新ê列單ê名", + "lists.no_lists_yet": "Iáu無列單。", + "lists.no_members_yet": "Iáu無成員。", + "lists.no_results_found": "Tshuē無結果。", + "lists.remove_member": "Suá掉", + "lists.replies_policy.followed": "所跟tuè ê任何用者", + "lists.replies_policy.list": "列單ê成員", + "lists.replies_policy.none": "無半位", + "lists.save": "儲存", + "lists.search": "Tshiau-tshuē", + "lists.show_replies_to": "列單成員回應ê顯示範圍", + "load_pending": "{count, plural, other {# ê 項目}}", + "loading_indicator.label": "Leh載入……", + "media_gallery.hide": "Khàm掉", + "moved_to_account_banner.text": "Lí ê口座 {disabledAccount} 已經停止使用ah,因為suá kàu {movedToAccount}。", + "mute_modal.hide_from_notifications": "Tuì通知內底khàm掉", + "mute_modal.hide_options": "Khàm掉選項", + "mute_modal.indefinite": "直到我取消消音", + "mute_modal.show_options": "顯示選項", + "mute_modal.they_can_mention_and_follow": "In iáu ē當提起á是跟tuè lí,毋過lí看buē著in。", + "mute_modal.they_wont_know": "In buē知影in受消音。", + "mute_modal.title": "Kā用者消音?", + "mute_modal.you_wont_see_mentions": "Lí buē看見提起in ê PO文。", + "mute_modal.you_wont_see_posts": "In iáu ē當看著lí ê PO文,毋過lí看bē tio̍h in ê。", + "navigation_bar.about": "概要", + "navigation_bar.administration": "管理", + "navigation_bar.advanced_interface": "用進階ê網頁界面開", + "navigation_bar.blocks": "封鎖ê用者", + "navigation_bar.bookmarks": "冊籤", + "navigation_bar.community_timeline": "本地ê時間線", + "navigation_bar.compose": "寫新ê PO文", + "navigation_bar.direct": "私人ê提起", + "navigation_bar.discover": "發現", + "navigation_bar.domain_blocks": "封鎖ê域名", + "navigation_bar.explore": "探查", + "navigation_bar.favourites": "Siōng kah意", + "navigation_bar.filters": "消音ê詞", + "navigation_bar.follow_requests": "跟tuè請求", + "navigation_bar.followed_tags": "跟tuè ê hashtag", + "navigation_bar.follows_and_followers": "Leh跟tuè ê kap跟tuè lí ê", + "navigation_bar.lists": "列單", + "navigation_bar.logout": "登出", + "navigation_bar.moderation": "審核", + "navigation_bar.mutes": "消音ê用者", + "navigation_bar.opened_in_classic_interface": "PO文、口座kap其他指定ê頁面,預設ē佇經典ê網頁界面內phah開。", + "navigation_bar.personal": "個人", + "navigation_bar.pins": "釘起來ê PO文", + "navigation_bar.preferences": "偏愛ê設定", + "navigation_bar.public_timeline": "聯邦ê時間線", + "navigation_bar.search": "Tshiau-tshuē", + "navigation_bar.security": "安全", + "not_signed_in_indicator.not_signed_in": "Lí著登入來接近使用tsit ê資源。", + "notification.admin.report": "{name} kā {target} 檢舉ah", + "notification.admin.report_account": "{name} kā {target} 所寫ê {count, plural, other {# 篇PO文}}檢舉ah,原因是:{category}", + "notification.admin.report_account_other": "{name} kā {target} 所寫ê {count, plural, other {# 篇PO文}}檢舉ah", + "notification.admin.report_statuses": "{name} kā {target} 檢舉ah,原因是:{category}", + "notification.admin.report_statuses_other": "{name} kā {target} 檢舉ah", + "notification.admin.sign_up": "口座 {name} 有開ah。", + "notification.admin.sign_up.name_and_others": "{name} kap {count, plural, other {其他 # ê lâng}} ê口座有開ah", + "notification.annual_report.message": "Lí ê {year} #Wrapstodon teh等lí!緊來看tsit年lí佇Mastodon頂ê上精彩ê內容,kap難忘ê時刻!", + "notification.annual_report.view": "Kā #Wrapstodon 看māi。", + "notification.favourite": "{name} kah意lí ê PO文", + "notification.favourite.name_and_others_with_link": "{name} kap{count, plural, other {另外 # ê lâng}}kah意lí ê PO文", "notification.favourite_pm": "{name} kah意lí ê私人提起", "notification.favourite_pm.name_and_others_with_link": "{name} kap{count, plural, other {另外 # ê lâng}}kah意lí ê私人提起", + "notification.follow": "{name}跟tuè lí", + "notification.follow.name_and_others": "{name} kap{count, plural, other {另外 # ê lâng}}跟tuè lí", + "notification.follow_request": "{name} 請求跟tuè lí", + "notification.follow_request.name_and_others": "{name} kap{count, plural, other {另外 # ê lâng}}請求跟tuè lí", + "notification.label.mention": "提起", + "notification.label.private_mention": "私人ê提起", + "notification.label.private_reply": "私人ê回應", + "notification.label.reply": "回應", + "notification.mention": "提起", + "notification.mentioned_you": "{name}kā lí提起", + "notification.moderation-warning.learn_more": "看詳細", + "notification.moderation_warning": "Lí有收著審核ê警告", + "notification.moderation_warning.action_delete_statuses": "Lí ê一寡PO文hōo lâng thâi掉ah。", + "notification.moderation_warning.action_disable": "Lí ê口座hōo lâng停止使用ah。", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Lí ê一寡PO文,hōo lâng標做敏感ê內容。", + "notification.moderation_warning.action_none": "Lí ê口座有收著審核ê警告。", + "notification_requests.edit_selection": "編輯", + "notification_requests.exit_selection": "做好ah", "search_popout.language_code": "ISO語言代碼", "status.translated_from_with": "用 {provider} 翻譯 {lang}" } diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json index 696f9fbed8..af2c922cbd 100644 --- a/app/javascript/mastodon/locales/ne.json +++ b/app/javascript/mastodon/locales/ne.json @@ -25,7 +25,6 @@ "account.enable_notifications": "@{name} ले पोस्ट गर्दा मलाई सूचित गर्नुहोस्", "account.endorse": "प्रोफाइलमा फिचर गर्नुहोस्", "account.featured_tags.last_status_never": "कुनै पोस्ट छैन", - "account.featured_tags.title": "{name}का विशेष ह्यासट्यागहरू", "account.follow": "फलो गर्नुहोस", "account.follow_back": "फलो ब्याक गर्नुहोस्", "account.followers": "फलोअरहरु", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index c1aef7ff7d..30f4777f61 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -27,9 +27,11 @@ "account.edit_profile": "Profiel bewerken", "account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst", "account.endorse": "Op profiel weergeven", + "account.featured": "Uitgelicht", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Berichten", "account.featured_tags.last_status_at": "Laatste bericht op {date}", "account.featured_tags.last_status_never": "Geen berichten", - "account.featured_tags.title": "Uitgelichte hashtags van {name}", "account.follow": "Volgen", "account.follow_back": "Terugvolgen", "account.followers": "Volgers", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} bericht} other {{counter} berichten}}", "account.unblock": "@{name} deblokkeren", "account.unblock_domain": "{domain} niet langer blokkeren", + "account.unblock_domain_short": "Deblokkeren", "account.unblock_short": "Deblokkeren", "account.unendorse": "Niet op profiel weergeven", "account.unfollow": "Ontvolgen", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Zoekresultaten", "emoji_button.symbols": "Symbolen", "emoji_button.travel": "Reizen en locaties", + "empty_column.account_featured": "Deze lijst is leeg", "empty_column.account_hides_collections": "Deze gebruiker heeft ervoor gekozen deze informatie niet beschikbaar te maken", "empty_column.account_suspended": "Account opgeschort", "empty_column.account_timeline": "Hier zijn geen berichten!", @@ -377,6 +381,8 @@ "generic.saved": "Opgeslagen", "getting_started.heading": "Aan de slag", "hashtag.admin_moderation": "Moderatie-omgeving van #{name} openen", + "hashtag.browse": "Berichten met #{hashtag} bekijken", + "hashtag.browse_from_account": "Berichten van @{name} met #{hashtag} bekijken", "hashtag.column_header.tag_mode.all": "en {additional}", "hashtag.column_header.tag_mode.any": "of {additional}", "hashtag.column_header.tag_mode.none": "zonder {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} bericht} other {{counter} berichten}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} bericht} other {{counter} berichten}} vandaag", "hashtag.follow": "Hashtag volgen", + "hashtag.mute": "#{hashtag} negeren", "hashtag.unfollow": "Hashtag ontvolgen", "hashtags.and_other": "…en {count, plural, one {}other {# meer}}", "hints.profiles.followers_may_be_missing": "Volgers voor dit profiel kunnen ontbreken.", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 4e0e96d974..ab867017a9 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -27,9 +27,11 @@ "account.edit_profile": "Rediger profil", "account.enable_notifications": "Varsle meg når @{name} skriv innlegg", "account.endorse": "Vis på profilen", + "account.featured": "Utvald", + "account.featured.hashtags": "Emneknaggar", + "account.featured.posts": "Innlegg", "account.featured_tags.last_status_at": "Sist nytta {date}", "account.featured_tags.last_status_never": "Ingen innlegg", - "account.featured_tags.title": "{name} sine framheva emneknaggar", "account.follow": "Fylg", "account.follow_back": "Fylg tilbake", "account.followers": "Fylgjarar", @@ -293,6 +295,7 @@ "emoji_button.search_results": "Søkeresultat", "emoji_button.symbols": "Symbol", "emoji_button.travel": "Reise & stader", + "empty_column.account_featured": "Denne lista er tom", "empty_column.account_hides_collections": "Denne brukaren har valt å ikkje gjere denne informasjonen tilgjengeleg", "empty_column.account_suspended": "Kontoen er utestengd", "empty_column.account_timeline": "Ingen tut her!", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 584132ffe6..a18ccdc0dc 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -29,7 +29,6 @@ "account.endorse": "Vis frem på profilen", "account.featured_tags.last_status_at": "Siste innlegg {date}", "account.featured_tags.last_status_never": "Ingen Innlegg", - "account.featured_tags.title": "{name} sine fremhevede emneknagger", "account.follow": "Følg", "account.follow_back": "Følg tilbake", "account.followers": "Følgere", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 616f8a64af..74ae8fad4d 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -26,7 +26,6 @@ "account.endorse": "Mostrar pel perfil", "account.featured_tags.last_status_at": "Darrièra publicacion lo {date}", "account.featured_tags.last_status_never": "Cap de publicacion", - "account.featured_tags.title": "Etiquetas en avant de {name}", "account.follow": "Sègre", "account.follow_back": "Sègre en retorn", "account.followers": "Seguidors", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index c6b2a5d412..ed827bcf29 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -29,7 +29,6 @@ "account.endorse": "Wyróżnij na profilu", "account.featured_tags.last_status_at": "Ostatni post {date}", "account.featured_tags.last_status_never": "Brak postów", - "account.featured_tags.title": "Polecane hasztagi {name}", "account.follow": "Obserwuj", "account.follow_back": "Również obserwuj", "account.followers": "Obserwujący", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 3ac1946503..55cfeea582 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -29,7 +29,6 @@ "account.endorse": "Recomendar", "account.featured_tags.last_status_at": "Última publicação em {date}", "account.featured_tags.last_status_never": "Sem publicações", - "account.featured_tags.title": "Hashtags em destaque de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir de volta", "account.followers": "Seguidores", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear domínio {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "Remover", "account.unfollow": "Deixar de seguir", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index d5df6e59cf..87c9e5846a 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -29,7 +29,6 @@ "account.endorse": "Destacar no perfil", "account.featured_tags.last_status_at": "Última publicação em {date}", "account.featured_tags.last_status_never": "Sem publicações", - "account.featured_tags.title": "Etiquetas destacadas por {name}", "account.follow": "Seguir", "account.follow_back": "Seguir também", "account.followers": "Seguidores", @@ -65,6 +64,7 @@ "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_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "Não destacar no perfil", "account.unfollow": "Deixar de seguir", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index 56d577f3f0..8fec42bbd0 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -29,7 +29,6 @@ "account.endorse": "Promovează pe profil", "account.featured_tags.last_status_at": "Ultima postare pe {date}", "account.featured_tags.last_status_never": "Fără postări", - "account.featured_tags.title": "Haștagurile recomandate de {name}", "account.follow": "Urmărește", "account.follow_back": "Urmăreşte înapoi", "account.followers": "Urmăritori", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index bc19a93c60..aaa73f51be 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -27,9 +27,11 @@ "account.edit_profile": "Редактировать", "account.enable_notifications": "Уведомлять о постах от @{name}", "account.endorse": "Рекомендовать в профиле", + "account.featured": "Избранное", + "account.featured.hashtags": "Хэштеги", + "account.featured.posts": "Посты", "account.featured_tags.last_status_at": "Последний пост {date}", "account.featured_tags.last_status_never": "Нет постов", - "account.featured_tags.title": "Избранные хэштеги {name}", "account.follow": "Подписаться", "account.follow_back": "Подписаться в ответ", "account.followers": "Подписчики", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} пост} few {{counter} поста} other {{counter} постов}}", "account.unblock": "Разблокировать @{name}", "account.unblock_domain": "Разблокировать {domain}", + "account.unblock_domain_short": "Разблокировать", "account.unblock_short": "Разблокировать", "account.unendorse": "Не рекомендовать в профиле", "account.unfollow": "Отписаться", diff --git a/app/javascript/mastodon/locales/ry.json b/app/javascript/mastodon/locales/ry.json index 8fd083efc3..ed9751634e 100644 --- a/app/javascript/mastodon/locales/ry.json +++ b/app/javascript/mastodon/locales/ry.json @@ -28,7 +28,6 @@ "account.endorse": "Указовати на профілови", "account.featured_tags.last_status_at": "Датум послідньої публикації {date}", "account.featured_tags.last_status_never": "Ниє публикацій", - "account.featured_tags.title": "Ублюблені гештеґы {name}", "account.follow": "Пудписати ся", "account.follow_back": "Пудписати ся тоже", "account.followers": "Пудписникы", diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json index 1ecc057023..ce88bda740 100644 --- a/app/javascript/mastodon/locales/sa.json +++ b/app/javascript/mastodon/locales/sa.json @@ -26,7 +26,6 @@ "account.endorse": "व्यक्तिगतविवरणे वैशिष्ट्यम्", "account.featured_tags.last_status_at": "{date} दिने गतस्थापनम्", "account.featured_tags.last_status_never": "न पत्रम्", - "account.featured_tags.title": "{name} इत्यस्य विशेषहैस्टैगः", "account.follow": "अनुस्रियताम्", "account.followers": "अनुसर्तारः", "account.followers.empty": "नाऽनुसर्तारो वर्तन्ते", diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json index c885078a73..79ef6f6ef5 100644 --- a/app/javascript/mastodon/locales/sc.json +++ b/app/javascript/mastodon/locales/sc.json @@ -29,7 +29,6 @@ "account.endorse": "Cussìgia in su profilu tuo", "account.featured_tags.last_status_at": "Ùrtima publicatzione in su {date}", "account.featured_tags.last_status_never": "Peruna publicatzione", - "account.featured_tags.title": "Etichetas de {name} in evidèntzia", "account.follow": "Sighi", "account.follow_back": "Sighi tue puru", "account.followers": "Sighiduras", diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json index ec8a92ac2e..7a7f926d5a 100644 --- a/app/javascript/mastodon/locales/sco.json +++ b/app/javascript/mastodon/locales/sco.json @@ -25,7 +25,6 @@ "account.endorse": "Shaw oan profile", "account.featured_tags.last_status_at": "Last post oan {date}", "account.featured_tags.last_status_never": "Nae posts", - "account.featured_tags.title": "{name}'s hielichtit hashtags", "account.follow": "Follae", "account.followers": "Follaers", "account.followers.empty": "Naebody follaes this uiser yit.", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index a55455f658..6cbf799328 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -27,9 +27,9 @@ "account.edit_profile": "Upraviť profil", "account.enable_notifications": "Zapnúť upozornenia na príspevky od @{name}", "account.endorse": "Zobraziť na vlastnom profile", + "account.featured.posts": "Príspevky", "account.featured_tags.last_status_at": "Posledný príspevok dňa {date}", "account.featured_tags.last_status_never": "Žiadne príspevky", - "account.featured_tags.title": "Odporúčané hashtagy účtu {name}", "account.follow": "Sledovať", "account.follow_back": "Sledovať späť", "account.followers": "Sledovatelia", @@ -65,6 +65,7 @@ "account.statuses_counter": "{count, plural, one {{counter} príspevok} other {{counter} príspevkov}}", "account.unblock": "Odblokovať @{name}", "account.unblock_domain": "Odblokovať doménu {domain}", + "account.unblock_domain_short": "Odblokovať", "account.unblock_short": "Odblokovať", "account.unendorse": "Nezobrazovať na vlastnom profile", "account.unfollow": "Zrušiť sledovanie", @@ -263,6 +264,7 @@ "emoji_button.search_results": "Výsledky hľadania", "emoji_button.symbols": "Symboly", "emoji_button.travel": "Cestovanie a miesta", + "empty_column.account_featured": "Tento zoznam je prázdny", "empty_column.account_hides_collections": "Tento účet sa rozhodol túto informáciu nesprístupniť", "empty_column.account_suspended": "Účet bol pozastavený", "empty_column.account_timeline": "Nie sú tu žiadne príspevky.", @@ -345,6 +347,7 @@ "generic.saved": "Uložené", "getting_started.heading": "Začíname", "hashtag.admin_moderation": "Otvor moderovacie rozhranie pre #{name}", + "hashtag.browse": "Prehľadávať príspevky pod #{hashtag}", "hashtag.column_header.tag_mode.all": "a {additional}", "hashtag.column_header.tag_mode.any": "alebo {additional}", "hashtag.column_header.tag_mode.none": "bez {additional}", @@ -358,6 +361,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} príspevok} few {{counter} príspevky} many {{counter} príspevkov} other {{counter} príspevkov}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} príspevok} few {{counter} príspevky} many {{counter} príspevkov} other {{counter} príspevkov}} dnes", "hashtag.follow": "Sledovať hashtag", + "hashtag.mute": "Utlmiť #{hashtag}", "hashtag.unfollow": "Prestať sledovať hashtag", "hashtags.and_other": "…a {count, plural, other {# ďalších}}", "hints.profiles.followers_may_be_missing": "Nasledovatelia tohto profilu môžu chýbať.", @@ -826,6 +830,9 @@ "video.expand": "Zväčšiť video", "video.fullscreen": "Zobraziť na celú obrazovku", "video.hide": "Skryť video", + "video.mute": "Stíšiť", "video.pause": "Pozastaviť", - "video.play": "Prehrať" + "video.play": "Prehrať", + "video.volume_down": "Hlasitosť nadol", + "video.volume_up": "Hlasitosť nahor" } diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index e6c0b67427..eef20456de 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -29,7 +29,6 @@ "account.endorse": "Izpostavi v profilu", "account.featured_tags.last_status_at": "Zadnja objava {date}", "account.featured_tags.last_status_never": "Ni objav", - "account.featured_tags.title": "Izpostavljeni ključniki osebe {name}", "account.follow": "Sledi", "account.follow_back": "Sledi nazaj", "account.followers": "Sledilci", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 116b3906a2..a42a25b374 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -27,9 +27,11 @@ "account.edit_profile": "Përpunoni profilin", "account.enable_notifications": "Njoftomë, kur poston @{name}", "account.endorse": "Pasqyrojeni në profil", + "account.featured": "Të zgjedhur", + "account.featured.hashtags": "Hashtag-ë", + "account.featured.posts": "Postime", "account.featured_tags.last_status_at": "Postimi i fundit më {date}", "account.featured_tags.last_status_never": "Pa postime", - "account.featured_tags.title": "Hashtagë të zgjedhur të {name}", "account.follow": "Ndiqeni", "account.follow_back": "Ndiqe gjithashtu", "account.followers": "Ndjekës", @@ -65,6 +67,7 @@ "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_domain_short": "Zhbllokoje", "account.unblock_short": "Zhbllokoje", "account.unendorse": "Mos e përfshi në profil", "account.unfollow": "Resht së ndjekuri", @@ -288,6 +291,7 @@ "emoji_button.search_results": "Përfundime kërkimi", "emoji_button.symbols": "Simbole", "emoji_button.travel": "Udhëtime & Vende", + "empty_column.account_featured": "Kjo listë është e zbrazët", "empty_column.account_hides_collections": "Ky përdorues ka zgjedhur të mos e japë këtë informacion", "empty_column.account_suspended": "Llogaria u pezullua", "empty_column.account_timeline": "S’ka mesazhe këtu!", @@ -372,6 +376,8 @@ "generic.saved": "U ruajt", "getting_started.heading": "Si t’ia fillohet", "hashtag.admin_moderation": "Hap ndërfaqe moderimi për #{name}", + "hashtag.browse": "Shfletoni postime me #{hashtag}", + "hashtag.browse_from_account": "Shfletoni postime nga @{name} me #{hashtag}", "hashtag.column_header.tag_mode.all": "dhe {additional}", "hashtag.column_header.tag_mode.any": "ose {additional}", "hashtag.column_header.tag_mode.none": "pa {additional}", @@ -385,6 +391,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} postim} other {{counter} postime}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} postim} other {{counter} postime}} sot", "hashtag.follow": "Ndiqe hashtag-un", + "hashtag.mute": "Heshtoje #{hashtag}", "hashtag.unfollow": "Hiqe ndjekjen e hashtag-ut", "hashtags.and_other": "…dhe {count, plural, one {}other {# më tepër}}", "hints.profiles.followers_may_be_missing": "Mund të mungojnë ndjekës për këtë profil.", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 3d077cbe22..2d00533e0e 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -28,7 +28,6 @@ "account.endorse": "Istakni na profilu", "account.featured_tags.last_status_at": "Poslednja objava {date}", "account.featured_tags.last_status_never": "Nema objava", - "account.featured_tags.title": "Istaknute heš oznake korisnika {name}", "account.follow": "Prati", "account.follow_back": "Uzvrati praćenje", "account.followers": "Pratioci", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 43c57b3e25..af323bed27 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -28,7 +28,6 @@ "account.endorse": "Истакни на профилу", "account.featured_tags.last_status_at": "Последња објава {date}", "account.featured_tags.last_status_never": "Нема објава", - "account.featured_tags.title": "Истакнуте хеш ознаке корисника {name}", "account.follow": "Прати", "account.follow_back": "Узврати праћење", "account.followers": "Пратиоци", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 8dc6161a94..c23a645135 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -29,7 +29,6 @@ "account.endorse": "Visa på profil", "account.featured_tags.last_status_at": "Senaste inlägg den {date}", "account.featured_tags.last_status_never": "Inga inlägg", - "account.featured_tags.title": "{name}s utvalda hashtaggar", "account.follow": "Följ", "account.follow_back": "Följ tillbaka", "account.followers": "Följare", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} inlägg} other {{counter} inlägg}}", "account.unblock": "Avblockera @{name}", "account.unblock_domain": "Avblockera {domain}", + "account.unblock_domain_short": "Avblockera", "account.unblock_short": "Avblockera", "account.unendorse": "Visa inte på profil", "account.unfollow": "Sluta följ", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 859413b899..429d0db428 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -29,7 +29,6 @@ "account.endorse": "แสดงในโปรไฟล์", "account.featured_tags.last_status_at": "โพสต์ล่าสุดเมื่อ {date}", "account.featured_tags.last_status_never": "ไม่มีโพสต์", - "account.featured_tags.title": "แฮชแท็กที่น่าสนใจของ {name}", "account.follow": "ติดตาม", "account.follow_back": "ติดตามกลับ", "account.followers": "ผู้ติดตาม", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, other {{counter} โพสต์}}", "account.unblock": "เลิกปิดกั้น @{name}", "account.unblock_domain": "เลิกปิดกั้นโดเมน {domain}", + "account.unblock_domain_short": "เลิกปิดกั้น", "account.unblock_short": "เลิกปิดกั้น", "account.unendorse": "ไม่แสดงในโปรไฟล์", "account.unfollow": "เลิกติดตาม", @@ -688,6 +688,7 @@ "poll_button.remove_poll": "เอาการสำรวจความคิดเห็นออก", "privacy.change": "เปลี่ยนความเป็นส่วนตัวของโพสต์", "privacy.direct.long": "ทุกคนที่กล่าวถึงในโพสต์", + "privacy.direct.short": "การกล่าวถึงแบบส่วนตัว", "privacy.private.long": "เฉพาะผู้ติดตามของคุณเท่านั้น", "privacy.private.short": "ผู้ติดตาม", "privacy.public.long": "ใครก็ตามที่อยู่ในและนอก Mastodon", @@ -892,6 +893,8 @@ "video.expand": "ขยายวิดีโอ", "video.fullscreen": "เต็มหน้าจอ", "video.hide": "ซ่อนวิดีโอ", + "video.mute": "ปิดเสียง", "video.pause": "หยุดชั่วคราว", - "video.play": "เล่น" + "video.play": "เล่น", + "video.unmute": "เลิกปิดเสียง" } diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json index 59dd34eb5c..08ce6fd324 100644 --- a/app/javascript/mastodon/locales/tok.json +++ b/app/javascript/mastodon/locales/tok.json @@ -29,7 +29,6 @@ "account.endorse": "lipu jan la o suli e ni", "account.featured_tags.last_status_at": "sitelen pini pi jan ni li lon tenpo {date}", "account.featured_tags.last_status_never": "toki ala li lon", - "account.featured_tags.title": "{name} la kulupu ni pi toki suli li pona", "account.follow": "o kute", "account.follow_back": "jan ni li kute e sina. o kute", "account.followers": "jan kute", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index e95efc642a..1f703e0748 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -27,9 +27,11 @@ "account.edit_profile": "Profili düzenle", "account.enable_notifications": "@{name} kişisinin gönderi bildirimlerini aç", "account.endorse": "Profilimde öne çıkar", + "account.featured": "Öne çıkan", + "account.featured.hashtags": "Etiketler", + "account.featured.posts": "Gönderiler", "account.featured_tags.last_status_at": "Son gönderinin tarihi {date}", "account.featured_tags.last_status_never": "Gönderi yok", - "account.featured_tags.title": "{name} kişisinin öne çıkan etiketleri", "account.follow": "Takip et", "account.follow_back": "Geri takip et", "account.followers": "Takipçi", @@ -65,6 +67,7 @@ "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_domain_short": "Engeli kaldır", "account.unblock_short": "Engeli kaldır", "account.unendorse": "Profilimde öne çıkarma", "account.unfollow": "Takibi bırak", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Arama sonuçları", "emoji_button.symbols": "Semboller", "emoji_button.travel": "Seyahat ve Yerler", + "empty_column.account_featured": "Bu liste boş", "empty_column.account_hides_collections": "Bu kullanıcı bu bilgiyi sağlamayı tercih etmemiştir", "empty_column.account_suspended": "Hesap askıya alındı", "empty_column.account_timeline": "Burada hiç gönderi yok!", @@ -377,6 +381,8 @@ "generic.saved": "Kaydet", "getting_started.heading": "Başlarken", "hashtag.admin_moderation": "#{name} için denetim arayüzünü açın", + "hashtag.browse": "#{hashtag} gönderilerine gözat", + "hashtag.browse_from_account": "@{name} kişisinin #{hashtag} gönderilerine gözat", "hashtag.column_header.tag_mode.all": "ve {additional}", "hashtag.column_header.tag_mode.any": "ya da {additional}", "hashtag.column_header.tag_mode.none": "{additional} olmadan", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} gönderi} other {{counter} gönderi}}", "hashtag.counter_by_uses_today": "bugün {count, plural, one {{counter} gönderi} other {{counter} gönderi}}", "hashtag.follow": "Etiketi takip et", + "hashtag.mute": "#{hashtag} gönderilerini sessize al", "hashtag.unfollow": "Etiketi takibi bırak", "hashtags.and_other": "…ve {count, plural, one {}other {# fazlası}}", "hints.profiles.followers_may_be_missing": "Bu profilin takipçileri eksik olabilir.", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 602d676361..ee50180ced 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -27,7 +27,6 @@ "account.endorse": "Профильдә тәкъдим итү", "account.featured_tags.last_status_at": "Соңгы хәбәр {date}", "account.featured_tags.last_status_never": "Хәбәрләр юк", - "account.featured_tags.title": "{name} тәкъдим ителгән хэштеглар", "account.follow": "Язылу", "account.followers": "Язылучы", "account.followers.empty": "Әле беркем дә язылмаган.", diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json index 378f688ba8..e550d7e678 100644 --- a/app/javascript/mastodon/locales/ug.json +++ b/app/javascript/mastodon/locales/ug.json @@ -1,21 +1,34 @@ { - "about.blocks": "ئوتتۇراھال مۇلازىمېتىر", - "about.contact": "ئالاقىلاشقۇچى:", - "account.badges.bot": "Bot", - "account.cancel_follow_request": "Withdraw follow request", - "account.posts": "Toots", - "account.posts_with_replies": "Toots and replies", + "about.blocks": "باشقۇرۇلىدىغان مۇلازىمېتىر", + "about.contact": "ئالاقە:", + "about.disclaimer": "Mastodon ھەقسىز، ئوچۇق كودلۇق يۇمشاق دېتال تاۋار ماركىسى Mastodon gGmbH غا تەۋە.", + "about.domain_blocks.no_reason_available": "سەۋەبىنى ئىشلەتكىلى بولمايدۇ", + "account.badges.bot": "ماشىنا ئادەم", + "account.cancel_follow_request": "ئەگىشىش ئىلتىماسىدىن ۋاز كەچ", + "account.posts": "يازما", + "account.posts_with_replies": "يازما ۋە ئىنكاس", + "account.report": "@{name} نى پاش قىل", "account.requested": "Awaiting approval", - "account_note.placeholder": "Click to add a note", - "column.pins": "Pinned toot", - "community.column_settings.media_only": "Media only", + "account_note.placeholder": "چېكىلسە ئىزاھات قوشىدۇ", + "column.pins": "چوققىلانغان يازما", + "community.column_settings.media_only": "ۋاسىتەلا", "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.", "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.", "compose_form.placeholder": "What is on your mind?", - "compose_form.publish_form": "Publish", - "compose_form.spoiler.marked": "Text is hidden behind warning", + "compose_form.publish_form": "يېڭى يازما", + "compose_form.reply": "جاۋاب", + "compose_form.save_changes": "يېڭىلا", + "compose_form.spoiler.marked": "مەزمۇن ئاگاھلاندۇرۇشىنى چىقىرىۋەت", "compose_form.spoiler.unmarked": "Text is not hidden", - "confirmations.delete.message": "Are you sure you want to delete this status?", + "compose_form.spoiler_placeholder": "مەزمۇن ئاگاھلاندۇرۇشى (تاللاشچان)", + "confirmation_modal.cancel": "ۋاز كەچ", + "confirmations.block.confirm": "توس", + "confirmations.delete.message": "بۇ يازمىنى راستىنلا ئۆچۈرەمسىز؟", + "confirmations.delete.title": "يازما ئۆچۈرەمدۇ؟", + "confirmations.delete_list.confirm": "ئۆچۈر", + "confirmations.delete_list.message": "بۇ تىزىمنى راستتىنلا مەڭگۈلۈك ئۆچۈرەمسىز؟", + "confirmations.delete_list.title": "تىزىمنى ئۆچۈرەمدۇ؟", + "confirmations.discard_edit_media.confirm": "تاشلىۋەت", "embed.instructions": "Embed this status on your website by copying the code below.", "empty_column.account_timeline": "No toots here!", "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 85b0125c07..6d37e0e1db 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -27,9 +27,11 @@ "account.edit_profile": "Редагувати профіль", "account.enable_notifications": "Повідомляти мене про дописи @{name}", "account.endorse": "Рекомендувати у моєму профілі", + "account.featured": "Рекомендоване", + "account.featured.hashtags": "Хештеги", + "account.featured.posts": "Дописи", "account.featured_tags.last_status_at": "Останній допис {date}", "account.featured_tags.last_status_never": "Немає дописів", - "account.featured_tags.title": "{name} виділяє хештеґи", "account.follow": "Підписатися", "account.follow_back": "Стежити також", "account.followers": "Підписники", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}", "account.unblock": "Розблокувати @{name}", "account.unblock_domain": "Розблокувати {domain}", + "account.unblock_domain_short": "Розблокувати", "account.unblock_short": "Розблокувати", "account.unendorse": "Не публікувати у профілі", "account.unfollow": "Відписатися", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Результати пошуку", "emoji_button.symbols": "Символи", "emoji_button.travel": "Подорожі та місця", + "empty_column.account_featured": "Список порожній", "empty_column.account_hides_collections": "Цей користувач вирішив не робити цю інформацію доступною", "empty_column.account_suspended": "Обліковий запис заблоковано", "empty_column.account_timeline": "Тут немає дописів!", @@ -390,6 +394,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}} сьогодні", "hashtag.follow": "Стежити за хештегом", + "hashtag.mute": "Ігнорувати #{hashtag}", "hashtag.unfollow": "Не стежити за хештегом", "hashtags.and_other": "…і {count, plural, other {ще #}}", "hints.profiles.followers_may_be_missing": "Підписники цього профілю можуть бути не показані.", diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json index cd50b512b4..e28ad93828 100644 --- a/app/javascript/mastodon/locales/ur.json +++ b/app/javascript/mastodon/locales/ur.json @@ -22,7 +22,6 @@ "account.endorse": "مشکص پر نمایاں کریں", "account.featured_tags.last_status_at": "آخری پوسٹ {date} کو", "account.featured_tags.last_status_never": "کوئی مراسلہ نہیں", - "account.featured_tags.title": "{name} کے نمایاں ہیش ٹیگز", "account.follow": "پیروی کریں", "account.follow_back": "اکاؤنٹ کو فالو بیک ", "account.followers": "پیروکار", diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json index e58ab35444..6dd350651d 100644 --- a/app/javascript/mastodon/locales/uz.json +++ b/app/javascript/mastodon/locales/uz.json @@ -25,7 +25,6 @@ "account.endorse": "Profildagi xususiyat", "account.featured_tags.last_status_at": "Oxirgi post: {date}", "account.featured_tags.last_status_never": "Habarlar yo'q", - "account.featured_tags.title": "{name} ning taniqli hashtaglari", "account.follow": "Obuna bo‘lish", "account.followers": "Obunachilar", "account.followers.empty": "Bu foydalanuvchini hali hech kim kuzatmaydi.", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index b179f50fa9..cabf3a5a82 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -27,9 +27,11 @@ "account.edit_profile": "Sửa hồ sơ", "account.enable_notifications": "Nhận thông báo khi @{name} đăng tút", "account.endorse": "Tôn vinh người này", + "account.featured": "Nổi bật", + "account.featured.hashtags": "Hashtag", + "account.featured.posts": "Tút", "account.featured_tags.last_status_at": "Tút gần nhất {date}", "account.featured_tags.last_status_never": "Chưa có tút", - "account.featured_tags.title": "Hashtag của {name}", "account.follow": "Theo dõi", "account.follow_back": "Theo dõi lại", "account.followers": "Người theo dõi", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, other {{counter} Tút}}", "account.unblock": "Bỏ chặn @{name}", "account.unblock_domain": "Bỏ ẩn {domain}", + "account.unblock_domain_short": "Bỏ chặn", "account.unblock_short": "Bỏ chặn", "account.unendorse": "Ngưng tôn vinh người này", "account.unfollow": "Bỏ theo dõi", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Kết quả tìm kiếm", "emoji_button.symbols": "Biểu tượng", "emoji_button.travel": "Du lịch", + "empty_column.account_featured": "Danh sách trống", "empty_column.account_hides_collections": "Người này đã chọn ẩn thông tin", "empty_column.account_suspended": "Tài khoản vô hiệu hóa", "empty_column.account_timeline": "Chưa có tút nào!", @@ -377,6 +381,8 @@ "generic.saved": "Đã lưu", "getting_started.heading": "Quản lý", "hashtag.admin_moderation": "Mở giao diện quản trị #{name}", + "hashtag.browse": "Tìm tút #{hashtag}", + "hashtag.browse_from_account": "Tìm tút của @{name} có chứa #{hashtag}", "hashtag.column_header.tag_mode.all": "và {additional}", "hashtag.column_header.tag_mode.any": "hoặc {additional}", "hashtag.column_header.tag_mode.none": "mà không {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, other {{counter} tút}}", "hashtag.counter_by_uses_today": "{count, plural, other {{counter} tút}} hôm nay", "hashtag.follow": "Theo dõi hashtag", + "hashtag.mute": "Ẩn #{hashtag}", "hashtag.unfollow": "Bỏ theo dõi hashtag", "hashtags.and_other": "…và {count, plural, other {# nữa}}", "hints.profiles.followers_may_be_missing": "Số người theo dõi có thể không đầy đủ.", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 050e1308b6..fc3f914e6a 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -27,9 +27,11 @@ "account.edit_profile": "修改个人资料", "account.enable_notifications": "当 @{name} 发布嘟文时通知我", "account.endorse": "在个人资料中推荐此用户", + "account.featured": "精选", + "account.featured.hashtags": "话题", + "account.featured.posts": "嘟文", "account.featured_tags.last_status_at": "上次发言于 {date}", "account.featured_tags.last_status_never": "暂无嘟文", - "account.featured_tags.title": "{name} 的精选标签", "account.follow": "关注", "account.follow_back": "回关", "account.followers": "关注者", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, other {{counter} 条嘟文}}", "account.unblock": "取消屏蔽 @{name}", "account.unblock_domain": "取消屏蔽 {domain} 域名", + "account.unblock_domain_short": "取消屏蔽", "account.unblock_short": "取消屏蔽", "account.unendorse": "不在个人资料中推荐此用户", "account.unfollow": "取消关注", @@ -293,6 +296,7 @@ "emoji_button.search_results": "搜索结果", "emoji_button.symbols": "符号", "emoji_button.travel": "旅行与地点", + "empty_column.account_featured": "这个列表为空", "empty_column.account_hides_collections": "该用户选择不公开此信息", "empty_column.account_suspended": "账号已被停用", "empty_column.account_timeline": "这里没有嘟文!", @@ -390,6 +394,7 @@ "hashtag.counter_by_uses": "{count, plural, other {{counter} 条嘟文}}", "hashtag.counter_by_uses_today": "今日 {count, plural, other {{counter} 条嘟文}}", "hashtag.follow": "关注话题", + "hashtag.mute": "停止提醒 #{hashtag}", "hashtag.unfollow": "取消关注话题", "hashtags.and_other": "… 和另外 {count, plural, other {# 个话题}}", "hints.profiles.followers_may_be_missing": "该账号的关注者列表可能没有完全显示。", @@ -905,6 +910,12 @@ "video.expand": "展开视频", "video.fullscreen": "全屏", "video.hide": "隐藏视频", + "video.mute": "停止提醒", "video.pause": "暂停", - "video.play": "播放" + "video.play": "播放", + "video.skip_backward": "后退", + "video.skip_forward": "前进", + "video.unmute": "恢复提醒", + "video.volume_down": "音量减小", + "video.volume_up": "提高音量" } diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index fb920a6b09..493d06b672 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -29,7 +29,6 @@ "account.endorse": "在個人檔案中推薦對方", "account.featured_tags.last_status_at": "上次帖文於 {date}", "account.featured_tags.last_status_never": "暫無文章", - "account.featured_tags.title": "{name} 的精選標籤", "account.follow": "關注", "account.follow_back": "追蹤對方", "account.followers": "追蹤者", @@ -84,6 +83,8 @@ "alert.unexpected.message": "發生意外錯誤。", "alert.unexpected.title": "失敗!", "alt_text_badge.title": "替代文字", + "alt_text_modal.cancel": "取消", + "alt_text_modal.done": "完成", "announcement.announcement": "公告", "attachments_list.unprocessed": "(未處理)", "audio.hide": "隱藏音訊", @@ -135,6 +136,7 @@ "column_header.pin": "置頂", "column_header.show_settings": "顯示設定", "column_header.unpin": "取消置頂", + "column_search.cancel": "取消", "column_subheading.settings": "設定", "community.column_settings.local_only": "只顯示本站", "community.column_settings.media_only": "只顯示多媒體", @@ -344,6 +346,7 @@ "home.pending_critical_update.title": "有重要的安全更新!", "home.show_announcements": "顯示公告", "ignore_notifications_modal.ignore": "忽略推播通知", + "info_button.label": "幫助", "interaction_modal.on_another_server": "於不同伺服器", "interaction_modal.on_this_server": "於此伺服器", "interaction_modal.title.favourite": "把 {name} 的帖文加入最愛", @@ -394,6 +397,7 @@ "limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。", "link_preview.author": "由 {name} 提供", "lists.delete": "刪除列表", + "lists.done": "完成", "lists.edit": "編輯列表", "lists.replies_policy.followed": "任何已關注的用戶", "lists.replies_policy.list": "列表中的用戶", @@ -461,6 +465,7 @@ "notification.update": "{name} 編輯了帖文", "notification_requests.accept": "接受", "notification_requests.dismiss": "忽略", + "notification_requests.exit_selection": "完成", "notification_requests.notifications_from": "來自 {name} 的通知", "notification_requests.title": "已過濾之通知", "notifications.clear": "清空通知紀錄", @@ -507,6 +512,7 @@ "notifications_permission_banner.enable": "啟用桌面通知", "notifications_permission_banner.how_to_control": "只要啟用桌面通知,便可在 Mastodon 網站沒有打開時收到通知。在已經啟用桌面通知的時候,你可以透過上面的 {icon} 按鈕準確控制哪些類型的互動會產生桌面通知。", "notifications_permission_banner.title": "不放過任何事情", + "onboarding.follows.done": "完成", "onboarding.follows.empty": "很遺憾,現在無法顯示任何結果。你可以嘗試搜尋或瀏覽探索頁面來找使用者來追蹤,或者稍後再試。", "onboarding.profile.discoverable": "將個人檔案設為可被搜尋", "onboarding.profile.discoverable_hint": "當你在 Mastodon 上選擇可被搜尋時,你的帖文可能會出現在搜尋結果和熱門,你的個人檔案也可能被推薦給與你興趣相似的人。", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 710814f12d..0d37e7f6c5 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -27,9 +27,11 @@ "account.edit_profile": "編輯個人檔案", "account.enable_notifications": "當 @{name} 嘟文時通知我", "account.endorse": "於個人檔案推薦對方", + "account.featured": "精選內容", + "account.featured.hashtags": "主題標籤", + "account.featured.posts": "嘟文", "account.featured_tags.last_status_at": "上次嘟文於 {date}", "account.featured_tags.last_status_never": "沒有嘟文", - "account.featured_tags.title": "{name} 的推薦主題標籤", "account.follow": "跟隨", "account.follow_back": "跟隨回去", "account.followers": "跟隨者", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, other {{count} 則嘟文}}", "account.unblock": "解除封鎖 @{name}", "account.unblock_domain": "解除封鎖網域 {domain}", + "account.unblock_domain_short": "解除封鎖", "account.unblock_short": "解除封鎖", "account.unendorse": "取消於個人檔案推薦對方", "account.unfollow": "取消跟隨", @@ -293,6 +296,7 @@ "emoji_button.search_results": "搜尋結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊與地點", + "empty_column.account_featured": "此列表為空", "empty_column.account_hides_collections": "這位使用者選擇不提供此資訊", "empty_column.account_suspended": "帳號已被停權", "empty_column.account_timeline": "這裡還沒有嘟文!", @@ -377,6 +381,8 @@ "generic.saved": "已儲存", "getting_started.heading": "開始使用", "hashtag.admin_moderation": "開啟 #{name} 的管理介面", + "hashtag.browse": "瀏覽於 #{hashtag} 之嘟文", + "hashtag.browse_from_account": "瀏覽來自 @{name} 於 #{hashtag} 之嘟文", "hashtag.column_header.tag_mode.all": "以及 {additional}", "hashtag.column_header.tag_mode.any": "或是 {additional}", "hashtag.column_header.tag_mode.none": "而無需 {additional}", @@ -390,6 +396,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} 則} other {{counter} 則}}嘟文", "hashtag.counter_by_uses_today": "本日有 {count, plural, one {{counter} 則} other {{counter} 則}}嘟文", "hashtag.follow": "跟隨主題標籤", + "hashtag.mute": "靜音 #{hashtag}", "hashtag.unfollow": "取消跟隨主題標籤", "hashtags.and_other": "…及其他 {count, plural, other {# 個}}", "hints.profiles.followers_may_be_missing": "此個人檔案之跟隨者或有缺失。", diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts index 88cbc3359b..55dbbcbb34 100644 --- a/app/javascript/mastodon/models/account.ts +++ b/app/javascript/mastodon/models/account.ts @@ -178,10 +178,5 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) { ), note_emojified: emojify(accountJSON.note, emojiMap), note_plain: unescapeHTML(accountJSON.note), - url: - accountJSON.url.startsWith('http://') || - accountJSON.url.startsWith('https://') - ? accountJSON.url - : accountJSON.uri, }); } diff --git a/app/javascript/mastodon/models/dropdown_menu.ts b/app/javascript/mastodon/models/dropdown_menu.ts index 35a29ab62a..ceea9ad4dd 100644 --- a/app/javascript/mastodon/models/dropdown_menu.ts +++ b/app/javascript/mastodon/models/dropdown_menu.ts @@ -3,16 +3,18 @@ interface BaseMenuItem { dangerous?: boolean; } -interface ActionMenuItem extends BaseMenuItem { +export interface ActionMenuItem extends BaseMenuItem { action: () => void; } -interface LinkMenuItem extends BaseMenuItem { +export interface LinkMenuItem extends BaseMenuItem { to: string; } -interface ExternalLinkMenuItem extends BaseMenuItem { +export interface ExternalLinkMenuItem extends BaseMenuItem { href: string; + target?: string; + method?: 'post' | 'put' | 'delete'; } export type MenuItem = @@ -20,5 +22,3 @@ export type MenuItem = | LinkMenuItem | ExternalLinkMenuItem | null; - -export type DropdownMenu = MenuItem[]; diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index c1afdf1bcc..1fd660e48b 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -624,9 +624,9 @@ export const composeReducer = (state = initialState, action) => { if (action.status.get('poll')) { map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), - multiple: action.status.getIn(['poll', 'multiple']), - expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), + options: ImmutableList(action.status.get('poll').options.map(x => x.title)), + multiple: action.status.get('poll').multiple, + expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at), })); } }); @@ -660,9 +660,9 @@ export const composeReducer = (state = initialState, action) => { if (action.status.get('poll')) { map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), - multiple: action.status.getIn(['poll', 'multiple']), - expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), + options: ImmutableList(action.status.get('poll').options.map(x => x.title)), + multiple: action.status.get('poll').multiple, + expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at), })); } }); diff --git a/app/javascript/mastodon/reducers/dropdown_menu.ts b/app/javascript/mastodon/reducers/dropdown_menu.ts index 59e19bb16d..0e46f0ee80 100644 --- a/app/javascript/mastodon/reducers/dropdown_menu.ts +++ b/app/javascript/mastodon/reducers/dropdown_menu.ts @@ -3,15 +3,15 @@ import { createReducer } from '@reduxjs/toolkit'; import { closeDropdownMenu, openDropdownMenu } from '../actions/dropdown_menu'; interface DropdownMenuState { - openId: string | null; + openId: number | null; keyboard: boolean; - scrollKey: string | null; + scrollKey: string | undefined; } const initialState: DropdownMenuState = { openId: null, keyboard: false, - scrollKey: null, + scrollKey: undefined, }; export const dropdownMenuReducer = createReducer(initialState, (builder) => { @@ -27,7 +27,7 @@ export const dropdownMenuReducer = createReducer(initialState, (builder) => { .addCase(closeDropdownMenu, (state, { payload: { id } }) => { if (state.openId === id) { state.openId = null; - state.scrollKey = null; + state.scrollKey = undefined; } }); }); diff --git a/app/javascript/mastodon/reducers/followed_tags.js b/app/javascript/mastodon/reducers/followed_tags.js deleted file mode 100644 index afea8e3b35..0000000000 --- a/app/javascript/mastodon/reducers/followed_tags.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; - -import { - FOLLOWED_HASHTAGS_FETCH_REQUEST, - FOLLOWED_HASHTAGS_FETCH_SUCCESS, - FOLLOWED_HASHTAGS_FETCH_FAIL, - FOLLOWED_HASHTAGS_EXPAND_REQUEST, - FOLLOWED_HASHTAGS_EXPAND_SUCCESS, - FOLLOWED_HASHTAGS_EXPAND_FAIL, -} from 'mastodon/actions/tags'; - -const initialState = ImmutableMap({ - items: ImmutableList(), - isLoading: false, - next: null, -}); - -export default function followed_tags(state = initialState, action) { - switch(action.type) { - case FOLLOWED_HASHTAGS_FETCH_REQUEST: - return state.set('isLoading', true); - case FOLLOWED_HASHTAGS_FETCH_SUCCESS: - return state.withMutations(map => { - map.set('items', fromJS(action.followed_tags)); - map.set('isLoading', false); - map.set('next', action.next); - }); - case FOLLOWED_HASHTAGS_FETCH_FAIL: - return state.set('isLoading', false); - case FOLLOWED_HASHTAGS_EXPAND_REQUEST: - return state.set('isLoading', true); - case FOLLOWED_HASHTAGS_EXPAND_SUCCESS: - return state.withMutations(map => { - map.update('items', set => set.concat(fromJS(action.followed_tags))); - map.set('isLoading', false); - map.set('next', action.next); - }); - case FOLLOWED_HASHTAGS_EXPAND_FAIL: - return state.set('isLoading', false); - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 0c5e748b00..617d7a08d9 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -16,7 +16,6 @@ import conversations from './conversations'; import custom_emojis from './custom_emojis'; import { dropdownMenuReducer } from './dropdown_menu'; import filters from './filters'; -import followed_tags from './followed_tags'; import height_cache from './height_cache'; import history from './history'; import { listsReducer } from './lists'; @@ -80,7 +79,6 @@ const reducers = { markers: markersReducer, picture_in_picture: pictureInPictureReducer, history, - followed_tags, reaction_deck, notificationPolicy: notificationPolicyReducer, notificationRequests: notificationRequestsReducer, diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js index 2e60d831b1..cc660af5c3 100644 --- a/app/javascript/mastodon/reducers/status_lists.js +++ b/app/javascript/mastodon/reducers/status_lists.js @@ -184,6 +184,7 @@ const removeOneFromAllBookmarkCategoriesById = (state, statusId) => { return s; }; +/** @type {import('@reduxjs/toolkit').Reducer} */ export default function statusLists(state = initialState, action) { switch(action.type) { case FAVOURITED_STATUSES_FETCH_REQUEST: diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 91ff05df4d..a03ee70e3f 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -6,6 +6,7 @@ import { me, isHideItem } from '../initial_state'; import { getFilters } from './filters'; export { makeGetAccount } from "./accounts"; +export { getStatusList, getSubStatusList } from "./statuses"; export const makeGetStatus = () => { return createSelector( @@ -93,15 +94,3 @@ export const makeGetReport = () => createSelector([ (_, base) => base, (state, _, targetAccountId) => state.getIn(['accounts', targetAccountId]), ], (base, targetAccount) => base.set('target_account', targetAccount)); - -export const getStatusList = createSelector([ - (state, type) => state.getIn(['status_lists', type, 'items']), -], (items) => items.toList()); - -export const getBookmarkCategoryStatusList = createSelector([ - (state, bookmarkCategoryId) => state.getIn(['status_lists', 'bookmark_category_statuses', bookmarkCategoryId, 'items']), -], (items) => items ? items.toList() : ImmutableList()); - -export const getCircleStatusList = createSelector([ - (state, circleId) => state.getIn(['status_lists', 'circle_statuses', circleId, `items`]), -], (items) => items ? items.toList() : ImmutableList()); diff --git a/app/javascript/mastodon/selectors/statuses.ts b/app/javascript/mastodon/selectors/statuses.ts new file mode 100644 index 0000000000..d07cc93aec --- /dev/null +++ b/app/javascript/mastodon/selectors/statuses.ts @@ -0,0 +1,33 @@ +import { createSelector } from '@reduxjs/toolkit'; +import type { OrderedSet as ImmutableOrderedSet } from 'immutable'; +import { List as ImmutableList } from 'immutable'; + +import type { RootState } from 'mastodon/store'; + +export const getStatusList = createSelector( + [ + ( + state: RootState, + type: + | 'favourites' + | 'bookmarks' + | 'pins' + | 'trending' + | 'emoji_reactions', + ) => + state.status_lists.getIn([type, 'items']) as ImmutableOrderedSet, + ], + (items) => items.toList(), +); + +export const getSubStatusList = createSelector( + [ + (state: RootState, type: 'bookmark_category' | 'circle', id: string) => + state.status_lists.getIn([ + `${type}_statuses`, + id, + 'items', + ]) as ImmutableOrderedSet | null, + ], + (items) => (items ? items.toList() : ImmutableList()), +); diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 69b88c8645..7ae8cb2d08 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -17,7 +17,12 @@ body { font-weight: 400; color: $primary-text-color; text-rendering: optimizelegibility; - font-feature-settings: 'kern'; + + // Disable kerning for Japanese text to preserve monospaced alignment for readability + &:not(:lang(ja)) { + font-feature-settings: 'kern'; + } + text-size-adjust: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0%); -webkit-tap-highlight-color: transparent; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 2c62522d56..9758ecc62c 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -5582,6 +5582,9 @@ a.status-card { &__results { &__item { + display: flex; + align-items: center; + gap: 0.5em; cursor: pointer; color: $primary-text-color; font-size: 14px; @@ -6586,7 +6589,7 @@ a.status-card { a { text-decoration: none; - color: $inverted-text-color; + color: $highlight-text-color; font-weight: 500; &:hover { @@ -7902,14 +7905,11 @@ a.status-card { } .radio-button__input.checked::before { - position: absolute; - left: 2px; - top: 2px; content: ''; display: block; border-radius: 50%; - width: 12px; - height: 12px; + width: calc(100% - 4px); + height: calc(100% - 4px); background: $ui-highlight-color; } @@ -8574,13 +8574,9 @@ noscript { &__item { display: flex; align-items: center; - padding: 15px; + padding: 16px; border-bottom: 1px solid var(--background-border-color); - gap: 15px; - - &:last-child { - border-bottom: 0; - } + gap: 8px; &__name { flex: 1 1 auto; @@ -8687,7 +8683,7 @@ noscript { } &--compact &__item { - padding: 10px; + padding: 12px; } } diff --git a/app/javascript/styles/modern-contrast.scss b/app/javascript/styles/modern-contrast.scss deleted file mode 100644 index 23e7b346ff..0000000000 --- a/app/javascript/styles/modern-contrast.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern -// Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ - -@use 'contrast/variables'; -@use 'application'; -@use 'modern/style'; -@use 'contrast/diff'; \ No newline at end of file diff --git a/app/javascript/styles/modern-dark.scss b/app/javascript/styles/modern-dark.scss deleted file mode 100644 index 208b714754..0000000000 --- a/app/javascript/styles/modern-dark.scss +++ /dev/null @@ -1,6 +0,0 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern -// Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ - -@use 'mastodon/variables'; -@use 'application'; -@use 'modern/style'; \ No newline at end of file diff --git a/app/javascript/styles/modern-light.scss b/app/javascript/styles/modern-light.scss deleted file mode 100644 index c8ecbd0bc8..0000000000 --- a/app/javascript/styles/modern-light.scss +++ /dev/null @@ -1,8 +0,0 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern -// Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ - -@use 'mastodon-light/variables'; -@use 'mastodon-light/css_variables'; -@use 'application'; -@use 'modern/style'; -@use 'mastodon-light/diff'; \ No newline at end of file diff --git a/app/javascript/styles/modern/style.scss b/app/javascript/styles/modern/style.scss deleted file mode 100644 index 4902cad50e..0000000000 --- a/app/javascript/styles/modern/style.scss +++ /dev/null @@ -1,2859 +0,0 @@ -:root { - --tl-width: 720px; - --emoji-size: 2em; - --avatar-size: 46px; - --radius: 12px; - --radius-round: 24px; - --panel-radius: var(--radius); - --hover-color: rgba(170,170,170,0.07); - --elevated-color: rgba(150,150,200,0.1); - --elevated-tint: rgba(200,200,240,0.07); - --border-color: rgba(120,120,200,0.2); - --border-color-2: #787878; - --shadow: 0 10px 40px -10px rgba(0,0,0,0.15); - --shadow-low: 0 8px 24px -16px rgba(0,0,0,0.2); - --shadow-med: 0 8px 60px -30px rgba(0,0,0,0.1); - --column-shadow: 0 8px 24px 12px rgba(0,0,0,0.02); - --background-border-color: var(--border-color); - } - @media (max-width: 759px) { - :root { - --panel-radius: 0px; - } - } - .layout-multiple-columns { - --panel-radius: 0px; - } - body { - font-display: swap !important; - } - body:not(.admin)::before { - content: ""; - position: fixed; - inset: 0; - background: rgba(0,0,0,0.06); - z-index: -1; - } - p { - line-height: 1.5; - } - input { - text-align: start; - } - .button--block { - font-weight: bold; - } - .unhandled-link span, - .mention span { - text-decoration: none !important; - } - .unhandled-link:not(:focus):not(:hover) span, - .mention:not(:focus):not(:hover) span { - text-decoration: underline !important; - } - input, - .input-copy, - select, - textarea, - .compose-form__upload-thumbnail, - .button, - :not(.notification__filter-bar) > button:not(.column-header__button), - video, - .privacy-dropdown__dropdown, - .react-toggle-track, - .reply-indicator, - .compose-form__warning { - border-radius: var(--radius); - } - button:focus-visible, - .focusable:focus-visible, - a:focus-visible, - .media-gallery__item-thumbnail:focus-visible { - box-shadow: inset 0 0 0 2px #dc7b18; - outline: 2px #dc7b18 solid; - outline-offset: -2px; - } - :not(.radio-button__input):not(span) { - border-color: var(--border-color); - } - .nothing-here, - .column-inline-form, - .scrollable, - .detailed-status__action-bar, - .column-back-button, - .column-header__collapsible.collapsed, - .column-header__collapsible-inner, - .audio-player, - .search__input { - border: 0 !important; - } - .account__section-headline, - .notification__filter-bar, - .column-header { - border-inline: 0; - } - .account__section-headline, - .notification__filter-bar, - .column > .scrollable { - background: none; - } - .account__avatar, - #profile_page_avatar, - .account__avatar-composite, - .account-card__title__avatar img { - border-radius: 30%; - flex: none; - } - :not(body):not(.scrollable)::-webkit-scrollbar { - width: 6px; - margin-block: 10px; - } - :not(body):not(.scrollable)::-webkit-scrollbar-track { - background: none; - } - :not(body):not(.scrollable)::-webkit-scrollbar-thumb { - border-radius: 100px; - transition: background-color 0.2s; - } - :not(body):not(.scrollable):not(:hover)::-webkit-scrollbar-thumb { - background: none; - padding-block: 10px; - } - @media (prefers-reduced-motion: no-preference) { - body:not(.reduce-motion) .ui__navigation-bar__item, - body:not(.reduce-motion) .load-more, - body:not(.reduce-motion) .setting-toggle, - body:not(.reduce-motion) .column-header__back-button, - body:not(.reduce-motion) .column-back-button, - body:not(.reduce-motion) .trends__item, - body:not(.reduce-motion) .story, - body:not(.reduce-motion) .account__avatar, - body:not(.reduce-motion) .button, - body:not(.reduce-motion) .media-gallery__item, - body:not(.reduce-motion) .column-link, - body:not(.reduce-motion) select, - body:not(.reduce-motion) .status-card, - body:not(.reduce-motion) .audio-player, - body:not(.reduce-motion) .account { - transition: transform 0.4s cubic-bezier(0, 0, 0, 3), background 0.2s, opacity 0.2s !important; - } - body:not(.reduce-motion) .ui__navigation-bar__item:active, - body:not(.reduce-motion) .load-more:active, - body:not(.reduce-motion) .setting-toggle:active, - body:not(.reduce-motion) .column-header__back-button:active, - body:not(.reduce-motion) .column-back-button:active, - body:not(.reduce-motion) .trends__item:active, - body:not(.reduce-motion) .story:active, - body:not(.reduce-motion) .account__avatar:active, - body:not(.reduce-motion) .button:active, - body:not(.reduce-motion) .media-gallery__item:active, - body:not(.reduce-motion) .column-link:active, - body:not(.reduce-motion) select:active, - body:not(.reduce-motion) .status-card:active, - body:not(.reduce-motion) .audio-player:active, - body:not(.reduce-motion) .account:active, - body:not(.reduce-motion) .ui__navigation-bar__item:focus-visible, - body:not(.reduce-motion) .load-more:focus-visible, - body:not(.reduce-motion) .setting-toggle:focus-visible, - body:not(.reduce-motion) .column-header__back-button:focus-visible, - body:not(.reduce-motion) .column-back-button:focus-visible, - body:not(.reduce-motion) .trends__item:focus-visible, - body:not(.reduce-motion) .story:focus-visible, - body:not(.reduce-motion) .account__avatar:focus-visible, - body:not(.reduce-motion) .button:focus-visible, - body:not(.reduce-motion) .media-gallery__item:focus-visible, - body:not(.reduce-motion) .column-link:focus-visible, - body:not(.reduce-motion) select:focus-visible, - body:not(.reduce-motion) .status-card:focus-visible, - body:not(.reduce-motion) .audio-player:focus-visible, - body:not(.reduce-motion) .account:focus-visible { - transform: scale(0.99); - transition: transform 0.4s cubic-bezier(0, 0, 0, 1) !important; - } - body:not(.reduce-motion) .column-header__button, - body:not(.reduce-motion) .column-header__buttons > .column-header__back-button, - body:not(.reduce-motion) .react-toggle-track, - body:not(.reduce-motion) .icon-button, - body:not(.reduce-motion) .floating-action-button { - transition: transform 0.4s cubic-bezier(0, 0, 0, 4), background 0.2s !important; - } - body:not(.reduce-motion) .column-header__button:active, - body:not(.reduce-motion) .column-header__buttons > .column-header__back-button:active, - body:not(.reduce-motion) .react-toggle-track:active, - body:not(.reduce-motion) .icon-button:active, - body:not(.reduce-motion) .floating-action-button:active { - transform: scale(0.95); - transition: transform 0.4s cubic-bezier(0, 0, 0, 1) !important; - } - body:not(.reduce-motion) .status__content__spoiler-link span { - display: inline-block; - transition: transform 0.4s cubic-bezier(0, 0, 0, 4) !important; - } - body:not(.reduce-motion) .status__content__spoiler-link:active span { - transition: transform 0.4s cubic-bezier(0, 0, 0, 1) !important; - transform: translateY(1px); - } - .reduce-motion * { - animation-duration: 0s !important; - } - @-moz-keyframes bounceIn { - 0% { - transform: scale(1.1); - opacity: 0; - } - 30% { - transform: scale(0.99); - opacity: 1; - } - 60% { - transform: scale(1.005); - opacity: 1; - } - 100% { - transform: scale(1); - opacity: 1; - } - } - @-webkit-keyframes bounceIn { - 0% { - transform: scale(1.1); - opacity: 0; - } - 30% { - transform: scale(0.99); - opacity: 1; - } - 60% { - transform: scale(1.005); - opacity: 1; - } - 100% { - transform: scale(1); - opacity: 1; - } - } - @-o-keyframes bounceIn { - 0% { - transform: scale(1.1); - opacity: 0; - } - 30% { - transform: scale(0.99); - opacity: 1; - } - 60% { - transform: scale(1.005); - opacity: 1; - } - 100% { - transform: scale(1); - opacity: 1; - } - } - @keyframes bounceIn { - 0% { - transform: scale(1.1); - opacity: 0; - } - 30% { - transform: scale(0.99); - opacity: 1; - } - 60% { - transform: scale(1.005); - opacity: 1; - } - 100% { - transform: scale(1); - opacity: 1; - } - } - @-moz-keyframes slideUp { - from { - transform: translateY(20px); - } - } - @-webkit-keyframes slideUp { - from { - transform: translateY(20px); - } - } - @-o-keyframes slideUp { - from { - transform: translateY(20px); - } - } - @keyframes slideUp { - from { - transform: translateY(20px); - } - } - @-moz-keyframes slideUpFade { - from { - transform: translateY(20px); - filter: opacity(0); - } - } - @-webkit-keyframes slideUpFade { - from { - transform: translateY(20px); - filter: opacity(0); - } - } - @-o-keyframes slideUpFade { - from { - transform: translateY(20px); - filter: opacity(0); - } - } - @keyframes slideUpFade { - from { - transform: translateY(20px); - filter: opacity(0); - } - } - @-moz-keyframes slideUpFadeBig { - from { - transform: translateY(200px); - filter: opacity(0); - } - } - @-webkit-keyframes slideUpFadeBig { - from { - transform: translateY(200px); - filter: opacity(0); - } - } - @-o-keyframes slideUpFadeBig { - from { - transform: translateY(200px); - filter: opacity(0); - } - } - @keyframes slideUpFadeBig { - from { - transform: translateY(200px); - filter: opacity(0); - } - } - @-moz-keyframes slideDownFade { - from { - transform: translateY(-20px); - filter: opacity(0); - } - } - @-webkit-keyframes slideDownFade { - from { - transform: translateY(-20px); - filter: opacity(0); - } - } - @-o-keyframes slideDownFade { - from { - transform: translateY(-20px); - filter: opacity(0); - } - } - @keyframes slideDownFade { - from { - transform: translateY(-20px); - filter: opacity(0); - } - } - @-moz-keyframes slideUpBig { - from { - transform: translateY(50vh); - } - } - @-webkit-keyframes slideUpBig { - from { - transform: translateY(50vh); - } - } - @-o-keyframes slideUpBig { - from { - transform: translateY(50vh); - } - } - @keyframes slideUpBig { - from { - transform: translateY(50vh); - } - } - @-moz-keyframes fadeUp { - from { - transform: translateY(10px); - opacity: 0; - } - } - @-webkit-keyframes fadeUp { - from { - transform: translateY(10px); - opacity: 0; - } - } - @-o-keyframes fadeUp { - from { - transform: translateY(10px); - opacity: 0; - } - } - @keyframes fadeUp { - from { - transform: translateY(10px); - opacity: 0; - } - } - @-moz-keyframes scaleIn { - from { - transform: scale(0.98); - opacity: 0; - } - } - @-webkit-keyframes scaleIn { - from { - transform: scale(0.98); - opacity: 0; - } - } - @-o-keyframes scaleIn { - from { - transform: scale(0.98); - opacity: 0; - } - } - @keyframes scaleIn { - from { - transform: scale(0.98); - opacity: 0; - } - } - @-moz-keyframes fadeLeft { - from { - transform: translateX(20px) opacity(0); - } - } - @-webkit-keyframes fadeLeft { - from { - transform: translateX(20px) opacity(0); - } - } - @-o-keyframes fadeLeft { - from { - transform: translateX(20px) opacity(0); - } - } - @keyframes fadeLeft { - from { - transform: translateX(20px) opacity(0); - } - } - @-moz-keyframes rainbow { - to { - filter: hue-rotate(360deg); - } - } - @-webkit-keyframes rainbow { - to { - filter: hue-rotate(360deg); - } - } - @-o-keyframes rainbow { - to { - filter: hue-rotate(360deg); - } - } - @keyframes rainbow { - to { - filter: hue-rotate(360deg); - } - } - } - .columns-area__panels { - --top: 5px; - gap: 0; - } - @media (min-width: 1175px) { - .columns-area__panels { - padding-inline: 10px; - padding-top: var(--top); - box-sizing: border-box; - transition: padding 0.4s; - --top: 20px; - } - } - @media (min-width: 1320px) { - .columns-area__panels { - --top: 30px; - } - } - .compose-panel { - overflow-y: auto; - margin-top: calc(0px - var(--top)); - padding-top: var(--top); - padding-bottom: 20px; - padding-inline: 10px; - box-sizing: border-box; - max-height: unset !important; - height: 100%; - } - .compose-panel > * { - padding-inline: 0; - } - .compose-panel > .navigation-bar { - padding-top: 0 !important; - } - .compose-panel .search, - .drawer .search { - margin-bottom: 25px; - } - .compose-form__uploads { - padding: 0; - margin-block: 0 !important; - margin-inline: 12px; - width: unset; - } - .search { - border-radius: var(--radius); - margin-inline: -5px; - } - .search label { - box-sizing: border-box; - } - .search input { - border-radius: var(--radius-round) !important; - } - .search .search__icon > i { - margin-inline: 5px; - } - .search__popout { - border-radius: var(--radius); - animation: scaleIn 0.2s; - box-shadow: var(--shadow-low); - margin-top: 10px; - margin-inline: 4px; - width: calc(100% - 8px); - } - .navigation-bar .icon-button { - width: auto !important; - height: auto !important; - padding: 8px; - } - .compose-form { - min-height: unset; - overflow: unset; - gap: 15px; - flex: 1 0 auto !important; - } - .compose-form > * { - margin: 0 !important; - } - .compose-form > [aria-hidden="true"] { - display: none; - } - .compose-form > .navigation-bar { - margin-top: 10px; - } - .compose-form .reply-indicator { - position: relative; - transition: min-height 0.1s; - } - .compose-form .reply-indicator__display-name { - padding: 0; - } - .compose-form .spoiler-input__border { - display: none; - } - .compose-form #cw-spoiler-input { - padding-inline: 12px; - } - .compose-form .compose-form__autosuggest-wrapper, - .compose-form .autosuggest-textarea__textarea { - border-radius: var(--radius) var(--radius) 0 0 !important; - border-bottom: 0; - } - .compose-form .compose-form__buttons-wrapper { - border-radius: 0 0 var(--radius) var(--radius) !important; - } - .compose-form .compose-form__publish-button-wrapper { - overflow: visible !important; - max-width: 100%; - padding: 0; - } - .compose-form .compose-form__upload__actions { - z-index: 5; - position: relative; - } - .compose-form .compose-form__upload__actions button { - background: none; - } - .compose-form .compose-form__upload__thumbnail { - display: flex; - flex-direction: column; - } - .compose-form .compose-form__upload__warning { - position: relative; - flex-grow: 1; - display: flex; - } - .compose-form .compose-form__upload__warning button { - margin-top: auto; - } - .compose-form .compose-form__upload__warning button.active { - box-shadow: 0 0 0 100px rgba(0,0,0,0.75); - width: 100%; - height: 100%; - font-weight: bold; - font-size: 1.1em; - color: inherit; - transition: background 0.2s, transform 0.2s cubic-bezier(0, 0, 0, 1) !important; - } - .compose-form .compose-form__upload__warning button.active svg { - height: 1.2em; - width: 1.2em; - } - .compose-form .compose-form__upload__warning button.active:hover, - .compose-form .compose-form__upload__warning button.active:focus { - background: rgba(20,20,20,0.75); - } - .compose-form__highlightable { - border-radius: var(--radius); - overflow: visible !important; - } - .compose-form__highlightable #cw-spoiler-input { - border-radius: 0 !important; - } - .compose-form__highlightable textarea { - background: none !important; - } - .compose-form__highlightable .compose-form__dropdowns { - max-width: calc(100% - 7ch); - } - .compose-form__highlightable .compose-form__actions { - position: relative; - } - .compose-form__highlightable .compose-form__buttons { - display: flex; - flex-wrap: wrap; - flex-direction: row; - gap: 0; - flex-grow: 9999; - } - .compose-form__highlightable .compose-form__buttons * { - display: flex; - flex-grow: 1; - } - .compose-form__highlightable .compose-form__buttons label { - display: none; - } - .compose-form__highlightable .compose-form__buttons button { - flex-grow: 1; - padding: 5px; - } - .compose-form__highlightable .compose-form__submit button { - padding: 8px 16px; - } - .compose-form__highlightable .character-counter { - position: absolute; - inset-inline-end: 0; - bottom: calc(100% + 12px); - padding: 4px; - font-size: 13px; - } - .server-banner .server-banner__hero { - border-radius: var(--radius); - width: 100%; - border-radius: var(--radius) var(--radius) 0 0; - border: 1px solid var(--border-color); - box-sizing: border-box; - } - .server-banner .server-banner__description { - border: 1px solid var(--border-color); - padding: 14px 12px; - margin-top: -20px; - line-height: 1.5; - border-radius: 0 0 var(--radius) var(--radius); - } - .server-banner .server-banner__meta { - flex-direction: column; - } - .server-banner .server-banner__meta .server-banner__meta__column { - width: unset; - overflow: visible; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: flex-end; - } - .server-banner .server-banner__meta .server-banner__meta__column h4 { - margin: 0; - font-size: 0.9em !important; - width: 100%; - } - .server-banner .server-banner__meta .server-banner__meta__column .account { - margin-inline: -10px; - padding: 10px !important; - width: 100%; - } - .server-banner .server-banner__meta .server-banner__meta__column .account::after { - content: unset !important; - } - .server-banner .server-banner__meta .server-banner__meta__column .server-banner__number, - .server-banner .server-banner__meta .server-banner__meta__column .server-banner__number-label { - font-size: 1.1em; - line-height: 2; - margin-inline-end: 0.5em; - } - .server-banner .server-banner__meta .server-banner__meta__column .server-banner__number-label { - font-weight: 400 !important; - } - .navigation-panel__sign-in-banner { - margin-inline: 5px; - position: relative; - } - .link-footer { - margin-top: 20px; - } - .link-footer > p:last-child { - margin-bottom: 0; - } - .columns-area { - box-shadow: var(--column-shadow); - padding: 0; - overflow: visible; - } - @media (min-width: 1175px) { - .columns-area { - border-radius: var(--radius) var(--radius) 0 0 !important; - } - .columns-area > :first-child { - border-radius: var(--radius) var(--radius) 0 0 !important; - } - } - .columns-area__panels__main { - overflow: visible !important; - contain: inline-size style !important; - transition: max-width 0.2s cubic-bezier(0, 0, 0, 1.1), margin 0.2s cubic-bezier(0, 0, 0, 1.1); - } - @media (min-width: 1175px) { - .columns-area__panels__main { - width: 0; - flex-grow: 1; - margin-inline: 10px; - max-width: var(--tl-width) !important; - } - } - @media (min-width: 1320px) { - .columns-area__panels__main { - margin: 0 20px; - } - } - @media (min-width: 760px) { - .columns-area__panels__main { - display: grid; - grid-template-columns: 100%; - } - .columns-area__panels__main .column, - .columns-area__panels__main .columns-area { - grid-column: 1; - overflow: clip !important; - } - } - .columns-area__panels__main > div { - grid-row: 1; - } - .column { - background: var(--background-color); - overflow: clip; - } - .column::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-tint); - pointer-events: none; - } - @media (min-width: 760px) { - .layout-single-column .scrollable > [tabindex="-1"]:first-child { - margin-top: 10px; - } - .layout-single-column .item-list > article:first-of-type { - margin-top: 10px; - } - .layout-single-column .item-list > article::after { - inset-inline: calc(var(--radius) + 10px); - } - .layout-single-column .load-more, - .layout-single-column .trends__item, - .layout-single-column .focusable, - .layout-single-column .entry, - .layout-single-column .statuses-grid__item .detailed-status, - .layout-single-column .story, - .layout-single-column .scrollable :not(.focusable) > .account:not(.account--minimal), - .layout-single-column .timeline-hint, - .layout-single-column .notification-request { - margin-inline: 10px; - max-width: calc(100% - 20px); - } - } - .scrollable { - padding-bottom: 40vh !important; - } - .empty-column-indicator, - .error-column { - background: none; - } - .dismissable-banner { - display: flex; - align-items: center; - flex-direction: row-reverse; - gap: 20px; - margin: 0; - border-radius: 0; - border: 0; - padding: 25px; - } - .dismissable-banner > div { - padding: 0; - } - .dismissable-banner button { - padding: 16px; - margin: -16px -14px; - } - .tabs-bar__wrapper { - grid-column: 2; - border: 0 !important; - padding-top: 0; - transition: margin 0.2s cubic-bezier(0, 0, 0, 1.1), top 0.4s; - } - @media (min-width: 760px) { - .tabs-bar__wrapper { - margin-top: -100vh; - } - } - .column-header, - .column-inline-form { - font-weight: 600; - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; - } - .column-header ~ .scrollable, - .column-inline-form ~ .scrollable { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; - } - .column-header__title { - gap: 12px; - } - .announcements, - .column-header__collapsible:not(.collapsed) { - flex-direction: column-reverse; - align-items: flex-start; - border: 0; - animation: slideDownFade 0.3s backwards cubic-bezier(0, 1, 0, 1.2); - } - .column-header__collapsible { - transition: none; - background: var(--surface-background-color); - overflow-y: auto !important; - } - .tabs-bar__wrapper .collapsed { - display: none; - } - .announcements { - background: var(--surface-background-color); - } - .announcements__container { - width: 100% !important; - } - .announcements__mastodon { - display: block !important; - z-index: -1; - position: relative; - } - .announcements__pagination { - bottom: unset; - padding-block: 0; - display: flex; - align-items: center; - } - .column-header__wrapper > :not(.column-header):not(.collapsed) { - border-top: 2px solid var(--background-color) !important; - border: 0; - } - .column-header { - overflow: hidden; - } - .column-header > button { - z-index: 2; - } - .column-header__buttons { - isolation: isolate; - } - .column-header__buttons button { - transition: background 0.2s, transform 0.3s !important; - position: relative; - border-radius: 100px !important; - min-width: 40px; - margin: 5px; - margin-inline-start: 0; - font-size: 0.9em; - padding-inline: 10px; - } - .column-header__buttons button:not(.active) { - background: var(--elevated-color) !important; - z-index: 2; - } - .column-header__buttons button svg { - margin: 0; - } - .column-header__buttons button span { - display: none; - } - .column-header__buttons button::before { - content: ""; - position: absolute; - inset: -20px -800px; - transform: scale(0); - transform-origin: bottom center; - background: var(--surface-background-color); - z-index: -1; - border-radius: 100px; - pointer-events: none; - opacity: 0; - transition: transform 0.3s, opacity 0.3s; - } - @media (prefers-reduced-motion) { - .column-header__buttons button::before { - transition: none !important; - } - } - .column-header__buttons button.active::before { - transform: scale(1, 5); - opacity: 1; - transition: transform 0.3s, opacity 0.1s; - } - @media (min-width: 760px) { - .tabs-bar__wrapper { - inset-inline: unset !important; - height: 50px !important; - top: 0; - top: var(--top) !important; - width: 285px; - border-radius: var(--radius) var(--radius) !important; - box-shadow: 0 12px 12px -12px rgba(0,0,0,0.1); - margin-inline-start: 20px; - } - .tabs-bar__wrapper .column-header__wrapper { - display: flex; - flex-direction: column; - border-radius: var(--radius); - overflow: hidden; - } - .tabs-bar__wrapper .column-header__wrapper > div { - background: var(--surface-background-color); - } - .tabs-bar__wrapper .column-header { - background: none !important; - overflow: hidden; - border: 0; - } - } - @media (min-width: 760px) and (max-width: 1319px) { - .tabs-bar__wrapper { - margin-inline-start: 10px; - } - } - @media (min-width: 760px) and (max-width: 1174px) { - .tabs-bar__wrapper { - width: 265px; - top: 10px !important; - } - } - @media (min-width: 760px) { - .column-back-button--slim { - margin-left: auto !important; - order: -1; - } - .column-back-button--slim > .column-back-button { - margin-top: 0 !important; - top: unset !important; - } - } - .column-settings__row, - .column-settings__hashtags { - gap: 0; - } - .column-settings h3 { - font-size: 1em; - margin-bottom: 8px; - } - .column-select__control { - border-radius: var(--radius); - } - .local-settings__page__item, - .glitch-setting-text.glitch-setting-text, - .setting-toggle, - .app-form__toggle { - display: flex; - align-items: center; - margin-bottom: 14px; - position: relative; - padding: 0.7em; - background: var(--elevated-color); - margin-block: 0 2px !important; - overflow: hidden; - } - .local-settings__page__item:first-of-type, - .glitch-setting-text.glitch-setting-text:first-of-type, - .setting-toggle:first-of-type, - .app-form__toggle:first-of-type { - border-top-left-radius: var(--radius); - border-top-right-radius: var(--radius); - } - .local-settings__page__item:last-of-type, - .glitch-setting-text.glitch-setting-text:last-of-type, - .setting-toggle:last-of-type, - .app-form__toggle:last-of-type { - border-bottom-left-radius: var(--radius); - border-bottom-right-radius: var(--radius); - } - .local-settings__page__item label, - .glitch-setting-text.glitch-setting-text label, - .setting-toggle label, - .app-form__toggle label, - .local-settings__page__item legend, - .glitch-setting-text.glitch-setting-text legend, - .setting-toggle legend, - .app-form__toggle legend { - padding-block: 2px !important; - } - .local-settings__page__item label span::before, - .glitch-setting-text.glitch-setting-text label span::before, - .setting-toggle label span::before, - .app-form__toggle label span::before { - content: ""; - position: absolute; - inset: -900px; - } - .local-settings__page__item .react-toggle, - .glitch-setting-text.glitch-setting-text .react-toggle, - .setting-toggle .react-toggle, - .app-form__toggle .react-toggle { - order: 2; - } - .local-settings__page__item .setting-toggle__label, - .glitch-setting-text.glitch-setting-text .setting-toggle__label, - .setting-toggle .setting-toggle__label, - .app-form__toggle .setting-toggle__label { - margin-bottom: 0 !important; - flex-grow: 1; - width: 0; - } - .local-settings__page__item::before, - .glitch-setting-text.glitch-setting-text::before, - .setting-toggle::before, - .app-form__toggle::before { - content: ""; - position: absolute; - inset: 0; - background: var(--hover-color); - opacity: 0; - transition: opacity 0.2s; - pointer-events: none; - } - .local-settings__page__item:hover::before, - .glitch-setting-text.glitch-setting-text:hover::before, - .setting-toggle:hover::before, - .app-form__toggle:hover::before, - .local-settings__page__item:focus-within::before, - .glitch-setting-text.glitch-setting-text:focus-within::before, - .setting-toggle:focus-within::before, - .app-form__toggle:focus-within::before { - opacity: 1; - } - @media (min-width: 760px) and (max-width: 1174px) { - .column-back-button--slim > .column-back-button { - margin-top: -55px !important; - top: unset !important; - } - } - .navigation-panel.navigation-panel { - box-sizing: border-box; - height: calc(100vh - var(--top) - 50px + var(--radius)); - padding-bottom: 10px; - margin: 0; - border: 0; - margin-top: calc(0px - var(--radius)); - padding-top: calc(10px + var(--radius)); - overflow: hidden auto; - } - .navigation-panel.navigation-panel hr { - display: none; - } - @media (min-width: 1175px) { - .navigation-panel.navigation-panel { - padding-top: calc(var(--radius) + 10px); - margin-top: calc(50px - var(--radius)); - } - .navigation-panel.navigation-panel .navigation-panel__logo { - margin: 0; - } - .navigation-panel.navigation-panel .navigation-panel__logo .column-link, - .navigation-panel.navigation-panel .navigation-panel__logo hr { - display: none !important; - } - .navigation-panel.navigation-panel .switch-to-advanced { - border-radius: var(--radius); - margin-top: 0; - } - } - .column-link { - border: 0; - gap: 12px; - } - .icon-with-badge__badge { - display: flex !important; - align-items: center; - justify-content: center; - padding: 0 0.3em !important; - width: 2em; - height: 2em; - min-width: max-content; - border-radius: 2em; - font-weight: 600; - font-size: 0.6em !important; - top: -10px; - box-sizing: border-box; - } - @media (min-width: 760px) { - .column-link { - flex-grow: 100 !important; - display: flex !important; - align-items: center !important; - align-content: center; - max-height: 3em; - min-height: 3em !important; - padding-block: 0; - border-radius: 100px; - position: relative; - box-sizing: border-box; - opacity: 0.9; - overflow: hidden; - background: none !important; - } - .column-link::before { - content: "" !important; - position: absolute; - border-radius: 100px; - width: unset !important; - height: unset !important; - inset: 0px 0px !important; - opacity: 0 !important; - background-color: rgba(150,150,150,0.1); - transition: opacity 0.2s; - } - .column-link.active { - opacity: 1 !important; - font-weight: 600; - } - .column-link:hover:before, - .column-link:focus:before { - opacity: 1 !important; - } - } - .navigation-panel.navigation-panel .trends__item { - margin: 0 !important; - } - .scrollable > div:first-child > [tabindex="-1"]:last-child .status__wrapper::after { - content: unset; - } - .focusable, - .entry, - .statuses-grid__item .detailed-status, - .trends__item, - .story, - .scrollable :not(.focusable) > .account:not(.account--minimal), - .timeline-hint, - .notification-request { - overflow: hidden; - contain: paint inline-size; - position: relative; - border-radius: var(--panel-radius); - border: 0; - } - .focusable.focusable, - .entry.focusable, - .statuses-grid__item .detailed-status.focusable, - .trends__item.focusable, - .story.focusable, - .scrollable :not(.focusable) > .account:not(.account--minimal).focusable, - .timeline-hint.focusable, - .notification-request.focusable { - background: none; - } - @media (pointer: fine) { - .focusable::before, - .entry::before, - .statuses-grid__item .detailed-status::before, - .trends__item::before, - .story::before, - .scrollable :not(.focusable) > .account:not(.account--minimal)::before, - .timeline-hint::before, - .notification-request::before { - content: ""; - position: absolute; - inset: 0px !important; - height: unset !important; - width: unset !important; - pointer-events: none; - transition: background-color 0.2s; - } - .focusable:hover::before, - .entry:hover::before, - .statuses-grid__item .detailed-status:hover::before, - .trends__item:hover::before, - .story:hover::before, - .scrollable :not(.focusable) > .account:not(.account--minimal):hover::before, - .timeline-hint:hover::before, - .notification-request:hover::before, - .focusable:focus-within::before, - .entry:focus-within::before, - .statuses-grid__item .detailed-status:focus-within::before, - .trends__item:focus-within::before, - .story:focus-within::before, - .scrollable :not(.focusable) > .account:not(.account--minimal):focus-within::before, - .timeline-hint:focus-within::before, - .notification-request:focus-within::before { - background-color: var(--hover-color); - } - } - .status:not(.status--first-in-thread) { - border: 0; - } - .detailed-status, - .status { - padding: 16px; - } - .status__info .account__avatar, - .status__info .status__avatar { - max-width: var(--avatar-size) !important; - max-height: var(--avatar-size) !important; - } - .status__line { - left: calc(16px + (var(--avatar-size) / 2)); - } - .status__prepend + .status:not(.status-direct) { - padding-top: 5px; - } - @media (max-width: 450px) { - .status--in-thread { - --avatar-size: 34px; - } - .status--in-thread .status__info ~ * { - margin-inline-start: calc(var(--avatar-size) + 10px); - width: calc(100% - (var(--avatar-size) + 10px)); - } - } - .status__content { - text-align: unset !important; - line-height: 1.5; - } - .status__content.status__content--with-spoiler { - overflow: visible; - } - .status__content.status__content--with-spoiler > p { - margin-inline: -100px; - padding-inline: 100px; - overflow: hidden; - } - .status__content.status__content--with-spoiler > p:first-child { - margin-bottom: 0; - } - .status__content p:empty { - max-height: 0; - } - .status__content picture { - display: contents; - } - .status__content .custom-emoji { - display: inline-block; - height: var(--emoji-size) !important; - min-width: var(--emoji-size) !important; - width: auto !important; - margin: -0.2ex 0 0.2ex; - } - @media (prefers-reduced-motion: no-preference) { - .custom-emoji { - transition: transform 1s cubic-bezier(0, 0.7, 0, 1); - } - .custom-emoji:hover { - transform: scale(1.7); - transition: transform 0.4s cubic-bezier(0, 0.7, 0, 1); - } - } - .status__content ~ [style*="aspect-ratio"] { - max-height: 80vh; - } - .detailed-status__wrapper-direct .status__content, - .status-direct .status__content, - .status__wrapper-direct .status__content, - .conversation .status__content { - position: relative !important; - background: var(--elevated-color); - padding: 12px 14px; - border-radius: var(--radius-round); - border-top-left-radius: 6px; - box-sizing: border-box; - margin-bottom: 0; - overflow: hidden !important; - max-width: max-content; - } - .detailed-status__wrapper-direct .status__content .media-gallery, - .status-direct .status__content .media-gallery, - .status__wrapper-direct .status__content .media-gallery, - .conversation .status__content .media-gallery { - width: 999px; - max-width: 100% !important; - } - .status__wrapper-direct:not(.detailed-status__wrapper-direct) .status__prepend { - position: absolute; - contain: strict; - } - .detailed-status { - border: 0; - padding-bottom: 4px; - } - .detailed-status__wrapper, - .detailed-status { - box-shadow: var(--shadow); - } - .detailed-status__wrapper .status__content, - .detailed-status .status__content { - min-height: unset !important; - } - .detailed-status__wrapper { - isolation: isolate; - background: none; - } - .detailed-status__wrapper::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-tint) !important; - pointer-events: none; - z-index: -1; - } - .detailed-status__wrapper .detailed-status { - box-shadow: none; - } - .detailed-status__meta { - margin-top: 14px; - line-height: 2; - } - .detailed-status__meta > * { - display: inline-flex; - border: 0 !important; - padding: 0 !important; - margin-inline-end: 8px; - } - .detailed-status__meta > *:not(:last-child)::after { - content: "·"; - } - .media-gallery, - .video-player, - .status-card.horizontal.interactive, - .status-card, - .audio-player, - .picture-in-picture-placeholder { - box-shadow: var(--shadow-low); - border-radius: var(--radius) !important; - margin-block: 10px; - animation: scaleIn 0.4s; - max-width: unset !important; - } - .media-gallery:has(.spoiler-button:not(.spoiler-button--minified)) { - height: 150px !important; - aspect-ratio: unset !important; - } - .media-gallery__item { - border-radius: 0; - outline: none; - } - .spoiler-button--minified button { - padding: 6px !important; - background: rgba(0,0,0,0.2) !important; - } - .spoiler-button--minified button::after { - content: ""; - position: absolute; - inset: -50px; - } - .spoiler-button--minified button:hover { - background: rgba(0,0,0,0.4) !important; - } - .spoiler-button--minified .icon { - width: 18px; - height: 18px; - } - .status-card { - align-items: stretch; - gap: 0; - } - .status-card:not(.horizontal) { - border: 1px solid var(--border-color) !important; - } - .status-card:not(.expanded) .status-card__image { - overflow: hidden; - } - .status-card:not(.expanded) .status-card__image img { - border-radius: 0; - } - .status-card:not(.interactive) .status-card__image { - position: relative; - aspect-ratio: unset !important; - } - .status-card__content { - margin-block: auto; - padding: 15px; - } - .status-card__host { - font-size: 0.85em; - line-height: 1.5; - margin: 0; - } - .status-card__title { - font-size: 1em; - margin-top: 0.2em; - margin-bottom: 0; - line-height: 1.4; - } - .status-card__description { - line-height: 1.4 !important; - margin: 0; - } - @supports (-webkit-line-clamp: 8) { - .status-card__description { - display: -webkit-box; - -webkit-line-clamp: 8; - -webkit-box-orient: vertical; - white-space: unset; - } - } - .status-card__author { - margin-top: 0.4em; - font-size: 0.85em; - } - .status-card:hover { - background-color: var(--hover-color); - } - .more-from-author { - background: none; - border: 0; - padding-top: 0; - border-radius: var(--radius); - } - .audio-player .video-player__seek { - margin: var(--radius); - } - .hashtag-bar { - margin-top: 10px; - } - .hashtag-bar a, - .hashtag-bar button { - color: var(--accent, #8c8dff); - transition: opacity 0.2s; - padding: 5px 10px; - } - .hashtag-bar a { - position: relative; - border-radius: var(--radius-round); - background: var(--elevated-color); - } - .hashtag-bar a::after { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-color); - border-radius: inherit; - opacity: 0; - transition: opacity 0.2s; - } - .hashtag-bar a:hover, - .hashtag-bar a:focus { - opacity: 1; - } - .hashtag-bar a:hover::after, - .hashtag-bar a:focus::after { - opacity: 1; - } - .hashtag-bar button { - padding-block: 0; - } - .status__action-bar { - flex-wrap: wrap; - margin-top: 0.4em; - margin-bottom: -6px; - gap: 0; - margin-inline-start: -8px; - } - .status__action-bar__button-wrapper { - flex-grow: 1; - max-width: 55px; - min-width: max-content; - } - .status__action-bar * { - display: flex !important; - justify-content: center !important; - flex-grow: 1 !important; - } - .status__action-bar .icon-button { - margin: 0; - } - .status__action-bar .icon-button::before { - content: ""; - position: absolute; - inset: -0.5em; - } - .status__action-bar, - .detailed-status__action-bar, - .picture-in-picture__footer { - position: relative; - z-index: 2; - justify-content: unset; - } - .status__action-bar .icon-button, - .detailed-status__action-bar .icon-button, - .picture-in-picture__footer .icon-button { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.5em 0.4em !important; - border-radius: var(--radius); - position: relative; - } - .status__action-bar .icon-button .icon-button__counter, - .detailed-status__action-bar .icon-button .icon-button__counter, - .picture-in-picture__footer .icon-button .icon-button__counter { - width: auto !important; - } - .status__action-bar .icon-button--with-counter, - .detailed-status__action-bar .icon-button--with-counter, - .picture-in-picture__footer .icon-button--with-counter { - padding-inline: 0.7em !important; - } - .detailed-status__action-bar, - .picture-in-picture__footer { - padding-inline: 15px !important; - gap: 0; - } - .detailed-status__action-bar .icon-button, - .picture-in-picture__footer .icon-button { - flex-grow: 1 !important; - } - .detailed-status__action-bar div, - .picture-in-picture__footer div { - display: flex; - justify-content: center; - flex-grow: 1; - } - .item-list > article > div { - position: relative; - } - .item-list > article > div::after { - content: ""; - position: absolute; - bottom: 0px; - inset-inline: 0; - border-top: 1px solid var(--border-color); - pointer-events: none; - } - @media (min-width: 760px) { - .layout-single-column .item-list > article > div::after { - inset-inline: calc(var(--radius) + 10px); - } - } - .account__wrapper { - line-height: 1.5; - } - .account__contents { - display: flex; - flex-wrap: wrap; - flex-grow: 1; - gap: 0 10px; - } - .display-name { - margin-bottom: 1px !important; - } - .account:not(.account--minimal) .display-name__account { - display: block; - width: 300px; - } - .account__details { - font-size: 0.9em; - opacity: 0.8; - width: 100px; - flex-grow: 1; - text-align: end; - align-items: center; - line-height: 1.2; - } - .account__details:has(.verified-badge) > :not(.verified-badge) { - display: none; - } - .account__wrapper button:not(:hover):not(:focus) { - background: var(--elevated-color); - color: inherit; - } - .notification-ungrouped { - padding: 0; - } - .notification-ungrouped__header { - padding-top: 16px; - margin-bottom: 0; - } - .notification-ungrouped .status__wrapper { - margin-inline: 0; - max-width: unset; - } - .notification-ungrouped .status__wrapper::before, - .notification-ungrouped .status__wrapper::after { - content: unset; - } - .notification-ungrouped .status { - padding: 16px; - } - .notification-group { - padding: 16px; - } - .notification-group__main__additional-content { - display: none; - } - .trends__item, - .story, - .account-card { - animation: slideUpFade backwards 0.4s 0.24s cubic-bezier(0, 1, 1, 1); - border-radius: var(--radius); - } - .trends__item:nth-child(1), - .story:nth-child(1), - .account-card:nth-child(1) { - animation-delay: 0.04s; - } - .trends__item:nth-child(2), - .story:nth-child(2), - .account-card:nth-child(2) { - animation-delay: 0.08s; - } - .trends__item:nth-child(3), - .story:nth-child(3), - .account-card:nth-child(3) { - animation-delay: 0.12s; - } - .trends__item:nth-child(4), - .story:nth-child(4), - .account-card:nth-child(4) { - animation-delay: 0.16s; - } - .trends__item:nth-child(5), - .story:nth-child(5), - .account-card:nth-child(5) { - animation-delay: 0.2s; - } - .trends__item:nth-child(6), - .story:nth-child(6), - .account-card:nth-child(6) { - animation-delay: 0.24s; - } - .explore__links { - padding: 10px; - display: flex; - flex-wrap: wrap; - align-content: flex-start; - } - .explore__links > .dismissable-banner { - margin: -10px; - margin-bottom: 10px; - } - .explore__links .story { - margin-inline: 0 !important; - } - .trends__item { - display: flex !important; - margin-inline: 0 !important; - } - .trends__item__name a::before { - content: ""; - position: absolute; - inset: 0; - } - .trends__item__current { - display: none; - } - .trends__item__sparkline { - overflow: visible !important; - pointer-events: none; - } - .trends__item__sparkline svg { - overflow: visible !important; - } - .trends__item__sparkline path:first-child { - filter: blur(8px); - } - .trends__item__sparkline path:last-child { - mask: linear-gradient(to left, #000, #000, transparent); - -webkit-mask: linear-gradient(to left, #000, #000, transparent); - } - .rtl .trends__item__sparkline { - transform: scaleX(-1); - } - .explore__links .trends__item { - margin: 7.5px !important; - padding: 25px !important; - box-shadow: var(--shadow-med); - width: 200px; - background: var(--elevated-color); - flex-grow: 1; - } - .explore__links .trends__item::after { - content: unset !important; - } - .explore__links .trends__item a { - font-size: 1.4em; - line-height: 1.7em; - } - .explore__links .trends__item a::before { - content: ""; - position: absolute; - inset: 0; - } - .explore__links .trends__item .trends__item__sparkline { - height: 100%; - } - .explore__links .trends__item .trends__item__sparkline svg { - height: 100%; - float: right; - overflow: visible !important; - position: relative; - } - .explore__links .trends__item .trends__item__sparkline svg path { - display: unset !important; - transition: transform 1s; - } - .explore__links .trends__item .trends__item__sparkline svg path:first-child { - transform-origin: center; - } - .explore__links .trends__item:hover svg path:first-child, - .explore__links .trends__item:focus-within svg path:first-child { - transform: scale(2); - } - .explore__links .story { - width: 100%; - margin: 0; - } - .account__header { - overflow: visible; - } - .follow-request-banner { - margin-bottom: -100px; - padding-bottom: 120px; - } - .account__header__image { - height: 0; - padding-bottom: 35%; - border-radius: var(--panel-radius) var(--panel-radius) 0 0; - position: sticky; - top: calc(0px - var(--panel-radius)); - overflow: clip; - } - .account__header__image img { - position: absolute; - } - .account__header__image .account__header__info { - position: absolute; - z-index: 2; - } - .account__header__image .account__header__info > span { - position: sticky; - top: var(--radius); - } - .account__header__bar { - position: relative; - z-index: 2; - border: 0; - padding-inline: 20px; - border-radius: var(--radius) var(--radius) 0 0; - margin-top: calc(0px - var(--radius)) !important; - display: flex; - flex-direction: column; - background: var(--background-color); - isolation: isolate; - } - @media (max-width: 760px) { - .account__header__bar { - padding-inline: 15px; - } - } - .account__header__bar::before { - content: ""; - background: var(--elevated-tint); - position: absolute; - inset: 0; - pointer-events: none; - } - .account__header__bar::after { - content: ""; - position: absolute; - inset-inline: 0; - height: 95px; - background: inherit; - z-index: -1; - border-radius: var(--radius); - mask: linear-gradient(to bottom, transparent, #000); - } - .account__header__tabs { - overflow: visible !important; - align-items: flex-end; - padding: 0; - height: unset !important; - margin-top: -55px !important; - } - .account__header__tabs::before { - content: ""; - position: absolute; - top: -55px; - inset-inline: 0; - height: 150px; - backdrop-filter: blur(40px); - filter: brightness(1.1); - pointer-events: none; - opacity: 0.7; - z-index: -2; - clip-path: inset(55px 0 0 0 round var(--radius)); - } - .account__header__tabs ~ div { - z-index: 2; - } - .account__header__bar .avatar { - margin-inline-start: 0 !important; - overflow: visible !important; - position: relative; - margin-top: 20px; - } - .account__header__bar .avatar .account-role { - position: absolute; - bottom: 0; - left: 110%; - border-radius: var(--radius); - } - .account__header__bar .account__avatar { - border: 0; - box-shadow: var(--shadow); - background: none; - background-size: cover !important; - } - .account__header__tabs__name { - margin-bottom: 0; - padding: 0; - margin-top: 16px; - } - .account__header__tabs__name h1 { - display: flex; - flex-wrap: wrap; - white-space: unset; - gap: 0 0.4em; - font-weight: 600; - } - .account__header__extra { - margin-top: 8px; - } - .account__header__fields, - .account__header__account-note { - display: flex; - flex-wrap: wrap; - gap: 2px; - background: none; - border-radius: var(--radius) !important; - overflow: hidden; - max-width: max-content; - border: 0 !important; - } - .account__header__fields dl { - display: inline; - border-radius: 0; - overflow: hidden; - border: 0 !important; - padding: 8px 12px !important; - margin: 0 !important; - position: relative; - overflow: hidden; - flex-grow: 1; - } - .account__header__fields dl:not(.verified) { - background-color: var(--elevated-color); - } - .account__header__fields dl dt { - all: unset !important; - color: unset !important; - opacity: 0.9 !important; - } - .account__header__fields dl dd { - padding: 0; - white-space: unset; - max-height: unset; - text-align: unset; - } - .account__header__fields dl dd > span > a:first-child:last-child::after, - .account__header__fields dl dd .h-card:first-child:last-child a::after { - content: ""; - position: absolute; - inset: 0; - background-color: var(--hover-color); - opacity: 0; - transition: opacity 0.2s; - } - .account__header__fields dl dd > span > a:first-child:last-child:hover:after, - .account__header__fields dl dd .h-card:first-child:last-child a:hover:after, - .account__header__fields dl dd > span > a:first-child:last-child:focus:after, - .account__header__fields dl dd .h-card:first-child:last-child a:focus:after { - opacity: 1; - } - .account__header__fields dl dd.verified { - overflow: visible !important; - border: 0; - background: none; - } - .account__header__fields dl dd.verified a::before, - .account__header__fields dl dd.verified a::after { - content: ""; - position: absolute; - inset: 0; - background: currentcolor; - opacity: 0.2; - } - .account__header__fields dl dd.verified a::after { - background: linear-gradient(20deg, currentcolor, transparent) !important; - opacity: 0.2 !important; - z-index: -2; - } - .account__header__account-note { - position: relative; - font-size: 0.9em; - max-width: unset; - padding: 0.5em 10px !important; - margin-block: -5px 10px; - margin-inline: -10px !important; - border-radius: var(--radius) !important; - } - .account__header__account-note::after { - content: ""; - position: absolute; - bottom: 0; - inset-inline: 10px; - border-top: 1px solid var(--border-color); - transition: opacity 0.2s; - } - .account__header__account-note:focus-within::after { - opacity: 0; - } - .account__header__account-note label { - z-index: 2; - margin: 0; - pointer-events: none; - font-size: inherit; - } - .account__header__account-note textarea { - margin: -100px !important; - padding: 100px !important; - padding-inline-end: 0.7em !important; - margin-inline-end: -0.7em !important; - box-sizing: content-box; - width: 100%; - font-size: inherit; - transition: background 0.2s; - } - .account__header__account-note textarea::placeholder { - font-weight: 600; - } - .account-gallery__container { - border-radius: var(--radius); - overflow: clip; - padding: 0; - margin: 4px; - gap: 4px; - } - .account-authorize__wrapper { - background: var(--elevated-color); - border-radius: var(--radius); - overflow: hidden; - flex-grow: 1; - margin: 10px; - display: flex; - flex-direction: column; - } - @media (max-width: 760px) { - .account-authorize__wrapper { - margin-inline: 10px; - } - } - .layout-multiple-columns .account-authorize__wrapper { - margin-inline: 10px; - } - .account-authorize__wrapper .account-authorize { - padding: 20px 15px 10px; - } - .account-authorize__wrapper .detailed-status__display-name { - margin-bottom: 10px !important; - } - .account-authorize__wrapper .account--panel { - margin-top: auto; - border-bottom: 0; - padding-inline: 15px; - gap: 10px; - background: none; - } - .account-authorize__wrapper br { - display: block; - } - .account-authorize__wrapper p { - margin-bottom: 0.2em; - } - .account-authorize__wrapper .account--panel__button:first-child .icon-button:not(:hover):not(:focus) { - background: var(--elevated-color); - } - .account-authorize__wrapper .icon-button { - width: 100% !important; - padding: 10px; - height: unset !important; - } - .about__meta { - border-radius: var(--radius); - } - .account--minimal { - max-width: 100%; - } - .about__section { - margin: 30px -20px; - padding-inline: 20px; - contain: inline-size paint; - } - .about__section.active .about__section__title { - margin-inline: -20px; - border-radius: 0; - border-inline: 0; - border-bottom: 0; - } - .about__section__title { - position: sticky; - top: -1px; - z-index: 2; - background: var(--background-color-tint); - border: 1px solid var(--border-color); - border-radius: var(--radius); - overflow: hidden; - transition: margin 0.2s cubic-bezier(0, 1, 0, 1), border-radius 0.2s; - } - .about__section__title::after { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-tint); - backdrop-filter: blur(10px); - z-index: -1; - } - .about__section__body { - border: 0; - padding: 0; - animation: slideDownFade 0.4s 0.1s backwards cubic-bezier(0, 1, 0, 1.2); - } - .explore__search-results { - border: 0; - } - .search-results__section__header { - margin: 0px 0px 10px; - color: unset; - background: none; - padding-inline: 25px; - font-weight: 600; - } - .search-results__section { - border: 0; - margin-bottom: 20px; - } - .admin-wrapper .content__heading { - margin-bottom: 2em; - } - .admin-wrapper h4 { - margin: 0; - border-bottom: 0; - } - .admin-wrapper form > h4 { - margin-top: 2em !important; - border-bottom: 0 !important; - margin-bottom: 0 !important; - } - .admin-wrapper .lead { - margin-bottom: 15px; - } - .admin-wrapper .flash-message, - .admin-wrapper .applications-list__item, - .admin-wrapper .filters-list__item { - border-radius: var(--radius); - border: 0; - overflow: clip; - } - .admin-wrapper .fields-row { - margin-inline: 0; - border-radius: var(--radius); - overflow: clip; - padding-top: 0; - gap: 2px; - display: flex; - flex-wrap: wrap; - } - .admin-wrapper .fields-group:not(.fields-row__column), - .admin-wrapper .fields-row { - margin-bottom: 1em !important; - } - .admin-wrapper .fields-row > .fields-row__column { - max-width: unset; - width: 300px; - border-radius: 0 !important; - display: flex; - flex-direction: column; - flex-grow: 1; - margin: 0 !important; - } - .admin-wrapper .fields-row > .fields-row__column .fields-group { - border-radius: 0 !important; - margin: 0 !important; - } - .admin-wrapper .fields-row > .fields-row__column .with_block_label { - display: flex; - flex-direction: column; - height: 100%; - } - .admin-wrapper .fields-row > .fields-row__column .with_block_label > .label_input { - display: flex; - flex-direction: column; - flex-grow: 1; - } - .admin-wrapper .fields-row > .fields-row__column .with_block_label > .label_input > textarea { - min-height: 300px; - flex-grow: 1; - } - .admin-wrapper .fields-row > .fields-row__column > :last-child { - flex-grow: 1; - align-items: flex-start; - border: 0; - } - .admin-wrapper .fields-row > .fields-row__column > :not(:first-child):not(:last-child) { - padding-block: 0.5em !important; - margin-block: -3px; - } - .admin-wrapper :not(.fields-row__column) > .fields-group, - .admin-wrapper .fields-row > *, - .admin-wrapper .label_input > ul, - .admin-wrapper .label_input__wrapper > ul, - .admin-wrapper .with_block_label.radio_buttons .label_input { - border-radius: var(--radius); - overflow: hidden; - padding: 0; - display: flex; - flex-direction: column; - gap: 2px; - } - .admin-wrapper :not(.fields-row__column) > .fields-group > *, - .admin-wrapper .fields-row > * > *, - .admin-wrapper .label_input > ul > *, - .admin-wrapper .label_input__wrapper > ul > *, - .admin-wrapper .with_block_label.radio_buttons .label_input > * { - background-color: var(--elevated-color); - padding: 0.8rem; - margin-block: 0px; - position: relative; - border-radius: 0 !important; - overflow: hidden; - } - .admin-wrapper :not(.fields-row__column) > .fields-group > *::after, - .admin-wrapper .fields-row > * > *::after, - .admin-wrapper .label_input > ul > *::after, - .admin-wrapper .label_input__wrapper > ul > *::after, - .admin-wrapper .with_block_label.radio_buttons .label_input > *::after { - content: ""; - position: absolute; - inset: 0; - background-color: var(--hover-color); - z-index: -1; - opacity: 0; - transition: opacity 0.2s; - } - .admin-wrapper :not(.fields-row__column) > .fields-group > *:hover::after, - .admin-wrapper .fields-row > * > *:hover::after, - .admin-wrapper .label_input > ul > *:hover::after, - .admin-wrapper .label_input__wrapper > ul > *:hover::after, - .admin-wrapper .with_block_label.radio_buttons .label_input > *:hover::after, - .admin-wrapper :not(.fields-row__column) > .fields-group > *:focus-within::after, - .admin-wrapper .fields-row > * > *:focus-within::after, - .admin-wrapper .label_input > ul > *:focus-within::after, - .admin-wrapper .label_input__wrapper > ul > *:focus-within::after, - .admin-wrapper .with_block_label.radio_buttons .label_input > *:focus-within::after { - opacity: 1; - } - .admin-wrapper :not(.fields-row__column) > .fields-group :not(.input.with_block_label) > label::before, - .admin-wrapper .fields-row > * :not(.input.with_block_label) > label::before, - .admin-wrapper .label_input > ul :not(.input.with_block_label) > label::before, - .admin-wrapper .label_input__wrapper > ul :not(.input.with_block_label) > label::before, - .admin-wrapper .with_block_label.radio_buttons .label_input :not(.input.with_block_label) > label::before { - content: ""; - position: absolute; - inset: -900px; - } - .admin-wrapper .label_input__wrapper > :not([type="checkbox"]):not(label) { - margin-top: 4px; - } - .admin-wrapper .label_input { - position: relative; - } - .admin-wrapper label { - margin: 0 !important; - display: flex; - align-items: center; - padding: 0 !important; - } - .admin-wrapper label input { - margin: 0; - margin-inline-end: 10px !important; - position: static !important; - } - .admin-wrapper input, - .admin-wrapper .select { - border-radius: var(--radius) !important; - z-index: 2; - } - .admin-wrapper .radio { - flex-grow: 1; - } - .admin-wrapper .radio:not(:last-child) { - margin-bottom: 0 !important; - } - .admin-wrapper .hint:last-child { - margin-bottom: 0 !important; - } - .admin-wrapper .input.with_block_label > .row { - flex-wrap: wrap; - margin: 0; - } - .admin-wrapper .input.with_block_label > .row > .string { - padding: 0; - width: 100%; - margin: 0; - } - .admin-wrapper .input.with_block_label > .row > .string:first-child input { - border-radius: var(--radius) var(--radius) 0 0 !important; - } - .admin-wrapper .input.with_block_label > .row > .string:last-child input { - border-radius: 0 0 var(--radius) var(--radius) !important; - } - .admin-wrapper .input.with_block_label > .row:not(:last-child) { - margin-bottom: 8px; - } - .admin-wrapper li.checkbox { - flex-grow: 1; - overflow: hidden; - } - .admin-wrapper ul { - flex-direction: row !important; - flex-wrap: wrap; - gap: 2px; - flex-grow: 1; - } - .admin-wrapper li.checkbox { - flex-basis: 45%; - } - .admin-wrapper .spacer { - border-top: 1px solid var(--border-color) !important; - } - .batch-table label { - padding-inline-start: 20px !important; - } - .batch-table, - .table, - :not(.batch-table__row__content) > table { - overflow: clip; - border-radius: var(--radius); - border-spacing: 0 2px; - border-collapse: separate; - } - .batch-table__toolbar, - .batch-table__row, - .batch-table tr > *, - .table tr > *, - :not(.batch-table__row__content) > table tr > * { - border: 0; - margin-bottom: 2px !important; - } - .batch-table td, - .table td, - :not(.batch-table__row__content) > table td, - .batch-table th, - .table th, - :not(.batch-table__row__content) > table th, - .batch-table__row { - position: relative; - } - .batch-table tr > td > div > span, - .table tr > td > div > span, - :not(.batch-table__row__content) > table tr > td > div > span, - .batch-table tr > th > div > span, - .table tr > th > div > span, - :not(.batch-table__row__content) > table tr > th > div > span { - padding-inline: 0.7em; - display: inline-block; - } - .keyboard-shortcuts { - padding: 0; - margin-top: -4px; - } - .keyboard-shortcuts table { - width: 100%; - border-radius: 0; - } - .keyboard-shortcuts td { - padding: 0.7em; - } - .batch-table__row, - .batch-table th, - .table th, - :not(.batch-table__row__content) > table th, - .batch-table > tbody > tr > td, - .table > tbody > tr > td, - :not(.batch-table__row__content) > table > tbody > tr > td, - .batch-table tfoot td, - .table tfoot td, - :not(.batch-table__row__content) > table tfoot td { - background: var(--elevated-color) !important; - vertical-align: middle; - } - .batch-table__row::after, - .batch-table th::after, - .table th::after, - :not(.batch-table__row__content) > table th::after, - .batch-table > tbody > tr > td::after, - .table > tbody > tr > td::after, - :not(.batch-table__row__content) > table > tbody > tr > td::after, - .batch-table tfoot td::after, - .table tfoot td::after, - :not(.batch-table__row__content) > table tfoot td::after { - content: ""; - position: absolute; - inset: 0 0; - background: var(--hover-color); - opacity: 0; - transition: 0.2s; - pointer-events: none; - } - .batch-table__row:hover::after, - .batch-table th:hover::after, - .table th:hover::after, - :not(.batch-table__row__content) > table th:hover::after, - .batch-table > tbody > tr > td:hover::after, - .table > tbody > tr > td:hover::after, - :not(.batch-table__row__content) > table > tbody > tr > td:hover::after, - .batch-table tfoot td:hover::after, - .table tfoot td:hover::after, - :not(.batch-table__row__content) > table tfoot td:hover::after, - .batch-table__row:focus-within::after, - .batch-table th:focus-within::after, - .table th:focus-within::after, - :not(.batch-table__row__content) > table th:focus-within::after, - .batch-table > tbody > tr > td:focus-within::after, - .table > tbody > tr > td:focus-within::after, - :not(.batch-table__row__content) > table > tbody > tr > td:focus-within::after, - .batch-table tfoot td:focus-within::after, - .table tfoot td:focus-within::after, - :not(.batch-table__row__content) > table tfoot td:focus-within::after { - opacity: 1; - } - .batch-table__row > a:first-child:last-child, - .batch-table th > a:first-child:last-child, - .table th > a:first-child:last-child, - :not(.batch-table__row__content) > table th > a:first-child:last-child, - .batch-table > tbody > tr > td > a:first-child:last-child, - .table > tbody > tr > td > a:first-child:last-child, - :not(.batch-table__row__content) > table > tbody > tr > td > a:first-child:last-child, - .batch-table tfoot td > a:first-child:last-child, - .table tfoot td > a:first-child:last-child, - :not(.batch-table__row__content) > table tfoot td > a:first-child:last-child { - margin: 0; - width: 100%; - padding: 0.5em; - } - .batch-table th:hover td:not([rowspan])::after, - .table th:hover td:not([rowspan])::after, - :not(.batch-table__row__content) > table th:hover td:not([rowspan])::after, - .batch-table tr:hover td:not([rowspan])::after, - .table tr:hover td:not([rowspan])::after, - :not(.batch-table__row__content) > table tr:hover td:not([rowspan])::after, - .batch-table th:hover th:not([rowspan])::after, - .table th:hover th:not([rowspan])::after, - :not(.batch-table__row__content) > table th:hover th:not([rowspan])::after, - .batch-table tr:hover th:not([rowspan])::after, - .table tr:hover th:not([rowspan])::after, - :not(.batch-table__row__content) > table tr:hover th:not([rowspan])::after { - opacity: 1 !important; - } - .batch-table th [rowspan]:hover ~ td::after, - .table th [rowspan]:hover ~ td::after, - :not(.batch-table__row__content) > table th [rowspan]:hover ~ td::after, - .batch-table tr [rowspan]:hover ~ td::after, - .table tr [rowspan]:hover ~ td::after, - :not(.batch-table__row__content) > table tr [rowspan]:hover ~ td::after { - opacity: 0 !important; - } - .batch-table th [rowspan]::after, - .table th [rowspan]::after, - :not(.batch-table__row__content) > table th [rowspan]::after, - .batch-table tr [rowspan]::after, - .table tr [rowspan]::after, - :not(.batch-table__row__content) > table tr [rowspan]::after { - inset-inline: -900px; - } - .layout-multiple-columns.layout-multiple-columns { - --column-header-height: 45px; - } - .layout-multiple-columns.layout-multiple-columns .column-header, - .layout-multiple-columns.layout-multiple-columns .column-header button { - background: none; - } - .layout-multiple-columns.layout-multiple-columns .column-header, - .layout-multiple-columns.layout-multiple-columns .scrollable, - .layout-multiple-columns.layout-multiple-columns .column-back-button, - .layout-multiple-columns.layout-multiple-columns .account__header__image { - border-radius: 0 !important; - gap: 0 !important; - } - .layout-multiple-columns.layout-multiple-columns .columns-area { - background: none !important; - height: 100%; - } - .layout-multiple-columns.layout-multiple-columns .columns-area > div { - border: 0 !important; - padding: 0 !important; - } - .layout-multiple-columns.layout-multiple-columns .columns-area > div:not(.drawer):not(:last-child) { - margin-inline-end: 2px !important; - } - .layout-multiple-columns.layout-multiple-columns .columns-area > div.column { - flex-grow: 1; - max-width: 600px; - } - .layout-multiple-columns.layout-multiple-columns .columns-area > div:first-child { - margin-inline-start: auto !important; - } - .layout-multiple-columns.layout-multiple-columns .columns-area > div:last-child { - margin-inline-end: auto !important; - } - .layout-multiple-columns.layout-multiple-columns .drawer.drawer { - padding-top: 15px !important; - overflow: clip; - flex-grow: 1; - max-width: 350px; - } - .drawer__header { - border-radius: var(--radius-round); - background: var(--elevated-color); - margin-inline: 15px; - overflow: hidden; - border: 0 !important; - } - .drawer__header a { - border: 0; - } - .drawer__header a:first-child { - padding-inline-start: 15px !important; - } - .drawer__header a:last-child { - padding-inline-end: 15px !important; - } - .layout-multiple-columns.layout-multiple-columns .drawer.drawer .search { - z-index: 2; - margin-inline: 15px; - margin-bottom: 0; - } - .layout-multiple-columns.layout-multiple-columns .drawer.drawer > .drawer__pager { - border: 0; - overflow: visible !important; - } - .layout-multiple-columns.layout-multiple-columns .drawer.drawer .drawer__inner:not(.darker) { - margin-top: -20px; - padding-top: 30px; - height: unset; - bottom: 0; - } - .layout-multiple-columns.layout-multiple-columns .drawer.drawer .drawer__inner__mastodon { - margin-inline: -6px; - z-index: -1; - } - .layout-multiple-columns.layout-multiple-columns .compose-form { - margin-inline: 5px; - } - .layout-multiple-columns.layout-multiple-columns .drawer__inner:not(.darker), - .layout-multiple-columns.layout-multiple-columns .drawer__inner__mastodon { - background-color: transparent !important; - } - .layout-multiple-columns.layout-multiple-columns .darker { - background-color: var(--surface-background-color); - border-radius: var(--radius) var(--radius) 0 0; - top: 10px; - width: unset; - inset-inline: 2px; - } - .layout-multiple-columns.layout-multiple-columns .column { - background: none; - } - .layout-multiple-columns.layout-multiple-columns .column::after { - content: ""; - position: absolute; - inset: 0; - top: var(--column-header-height); - background: var(--background-color); - z-index: -1; - } - .layout-multiple-columns.layout-multiple-columns .column::before, - .layout-multiple-columns.layout-multiple-columns .column::after { - top: var(--column-header-height); - border-radius: var(--radius) var(--radius) 0 0; - } - .layout-multiple-columns.layout-multiple-columns .column-back-button.active, - .layout-multiple-columns.layout-multiple-columns .column-header__wrapper.active { - box-shadow: none; - } - .layout-multiple-columns.layout-multiple-columns .column-back-button.active::before, - .layout-multiple-columns.layout-multiple-columns .column-header__wrapper.active::before { - inset-inline: var(--radius); - } - .layout-multiple-columns.layout-multiple-columns .column-back-button .column-header, - .layout-multiple-columns.layout-multiple-columns .column-header__wrapper .column-header { - border: 0 !important; - height: var(--column-header-height); - } - .layout-multiple-columns.layout-multiple-columns .column-back-button .column-header__buttons, - .layout-multiple-columns.layout-multiple-columns .column-header__wrapper .column-header__buttons { - height: 100%; - } - .layout-multiple-columns.layout-multiple-columns .column-back-button svg, - .layout-multiple-columns.layout-multiple-columns .column-header__wrapper svg { - height: 1.4em; - } - .layout-multiple-columns.layout-multiple-columns .column-back-button + .scrollable.scrollable, - .layout-multiple-columns.layout-multiple-columns .column-header__wrapper + .scrollable.scrollable { - border-radius: var(--radius) var(--radius) 0 0 !important; - overflow-y: scroll; - } - .layout-multiple-columns.layout-multiple-columns .getting-started__trends { - padding: 0px 20px; - } - .column[aria-labelledby="Misc"] > .scrollable, - .column[aria-labelledby="Getting-started"] > .scrollable, - .getting-started { - position: relative; - padding: 5px 10px !important; - } - .column[aria-labelledby="Misc"] > .scrollable .getting-started__wrapper, - .column[aria-labelledby="Getting-started"] > .scrollable .getting-started__wrapper, - .getting-started .getting-started__wrapper { - background: none; - } - .column[aria-labelledby="Misc"] > .scrollable .column-link, - .column[aria-labelledby="Getting-started"] > .scrollable .column-link, - .getting-started .column-link, - .column[aria-labelledby="Misc"] > .scrollable .column-subheading, - .column[aria-labelledby="Getting-started"] > .scrollable .column-subheading, - .getting-started .column-subheading { - border: 0 !important; - padding: 20px !important; - background: none; - } - .column[aria-labelledby="Misc"] > .scrollable .getting-started__footer, - .column[aria-labelledby="Getting-started"] > .scrollable .getting-started__footer, - .getting-started .getting-started__footer { - padding-inline: 20px; - } - .column[aria-labelledby="Misc"] > .scrollable .getting-started__footer a span, - .column[aria-labelledby="Getting-started"] > .scrollable .getting-started__footer a span, - .getting-started .getting-started__footer a span { - font-size: 1.1em !important; - line-height: 2; - } - @media (min-width: 760px) and (max-width: 1175px) { - .columns-area__panels__pane--navigational { - margin-top: 50px; - } - .navigation-panel__menu { - padding: 10px; - } - .navigation-panel__compose-button { - margin-block: 10px; - margin-inline: 6px; - padding-inline: 16px; - border-radius: 100px; - justify-content: center; - } - .navigation-panel__sign-in-banner { - display: block !important; - margin-block: 10px; - padding-block: 10px; - border-block: 1px solid var(--border-color); - } - } - @media (max-width: 759px) { - .tabs-bar__wrapper::before, - .ui__navigation-bar::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-color); - z-index: -1; - } - .ui__navigation-bar { - color: var(--on-input-color); - height: 70px; - padding-inline: 5px; - } - .ui__navigation-bar__item { - position: relative; - border: 0 !important; - padding-block: 12px; - gap: 6px; - opacity: 0.7; - } - .ui__navigation-bar__item::before { - content: ""; - position: absolute; - width: 60px; - top: 10px; - bottom: 30px; - background: currentColor; - border-radius: 100px; - z-index: -1; - opacity: 0; - transform: scaleX(0.8); - transition: opacity 0.2s, transform 0.2s; - } - .ui__navigation-bar__item::after { - content: attr(aria-label); - font-size: 12px; - max-width: 100%; - padding-inline: 4px; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - box-sizing: border-box; - } - .ui__navigation-bar__item.active { - opacity: 1; - } - .ui__navigation-bar__item.active::before { - opacity: 0.15; - transform: none; - } - .columns-area__panels__pane--navigational { - z-index: 100; - padding: 40px 10px; - padding-top: 60vh; - min-width: unset; - overflow-y: scroll; - box-sizing: border-box; - overscroll-behavior: contain; - visibility: hidden; - transition: visibility 0s 0.2s; - } - .columns-area__panels__pane--navigational .columns-area__panels__pane__inner { - width: 100%; - position: relative; - inset: unset; - height: max-content; - border-radius: 24px; - margin-top: auto; - transform: translateY(100vh) !important; - transition: transform 0.2s cubic-bezier(0, 0, 1, 0), opacity 1s; - } - .columns-area__panels__pane--navigational .navigation-panel__compose-button { - display: none; - } - html:not(:has(.sign-in-banner)) .columns-area__panels__pane--navigational [href="/home"], - html:not(:has(.sign-in-banner)) .columns-area__panels__pane--navigational [href="/explore"], - html:not(:has(.sign-in-banner)) .columns-area__panels__pane--navigational [href="/notifications"] { - display: none; - } - .columns-area__panels__pane--navigational .navigation-panel { - display: contents; - } - .columns-area__panels__pane--navigational .navigation-panel__menu { - padding: 10px 5px !important; - } - .columns-area__panels__pane--navigational.columns-area__panels__pane--overlay { - visibility: visible; - transition: none; - animation: slideUpFadeBig 0.3s cubic-bezier(0, 0.9, 0, 1.05) forwards; - } - .columns-area__panels__pane--navigational.columns-area__panels__pane--overlay .columns-area__panels__pane__inner { - transform: none !important; - transition: none; - } - } - #hover-card, - .dropdown-menu { - border-radius: var(--radius); - animation: scaleIn 0.2s cubic-bezier(0, 0, 0, 1.1); - } - .dropdown-menu__container__list { - overflow: hidden auto; - border-radius: var(--radius); - max-height: 70vh; - } - .dropdown-menu__item { - overflow: hidden; - } - .dropdown-menu__item a { - padding: 0.7em 1em !important; - transition: background-color 0.2s, color 0.2s; - min-width: 150px; - } - .dropdown-menu__separator { - margin: 0 !important; - } - .interaction-modal { - border-radius: var(--radius); - overflow-y: auto; - box-sizing: border-box; - width: 700px; - text-align: center; - } - .interaction-modal__choices { - gap: 10px; - display: flex; - flex-wrap: wrap; - } - .interaction-modal__choices .interaction-modal__choices__choice { - max-height: 50vh; - overflow-y: auto; - border: 1px solid var(--border-color); - padding: 24px; - margin: 0; - border-radius: var(--radius); - transition: background 0.2s; - position: relative; - } - .interaction-modal__choices .prose:last-child { - margin-bottom: 0; - } - .interaction-modal__choices h3 { - margin-bottom: 10px; - } - .modal-root__container { - animation: bounceIn 0.7s; - } - @media (max-width: 760px) { - .modal-root__modal { - margin-top: auto; - max-width: 100%; - border-radius: var(--radius) var(--radius) 0 0; - } - } - .picture-in-picture { - z-index: 101; - } - .picture-in-picture .picture-in-picture__header { - border-radius: var(--radius) var(--radius) 0 0; - } - .picture-in-picture .media-gallery, - .picture-in-picture .video-player, - .picture-in-picture .status-card.horizontal.interactive, - .picture-in-picture .status-card, - .picture-in-picture .audio-player, - .picture-in-picture .picture-in-picture-placeholder { - --radius: 0; - margin: 0 !important; - } - .picture-in-picture .picture-in-picture__footer { - border-radius: 0 0 var(--radius) var(--radius); - } - .modal-root__modal:has(.focal-point) { - width: unset; - max-width: 90vw; - } - .modal-root__modal:has(.focal-point) .dialog-modal__content { - overflow: hidden; - display: flex; - flex-direction: column; - } - .modal-root__modal:has(.focal-point) .dialog-modal__content__preview { - padding: 0 !important; - min-height: 0; - max-width: 100%; - } - .modal-root__modal:has(.focal-point) .dialog-modal__content__preview img { - max-width: 100% !important; - max-height: 100% !important; - border-radius: 0; - max-height: unset; - min-height: 0; - } - .modal-root__modal:has(.focal-point) .focal-point__reticle { - transition: box-shadow 0.2s; - } - .modal-root__modal:has(.focal-point) .focal-point { - min-height: 0 !important; - } - .modal-root__modal:has(.focal-point) .focal-point:not(:hover) .focal-point__reticle { - box-shadow: none; - } - .emoji-picker-dropdown__menu { - border-radius: var(--radius); - overflow: hidden; - resize: both; - width: 400px; - } - .emoji-mart { - display: flex !important; - flex-direction: column !important; - width: 100% !important; - height: 100% !important; - } - .emoji-mart-scroll { - flex-grow: 1; - max-height: unset !important; - } - .emoji-mart-bar { - order: 2; - } - .emoji-mart-category-list { - overflow: visible !important; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(calc(20px + 6%), 1fr)); - } - .emoji-mart-category-list li { - display: contents; - } - .emoji-mart-category-list button { - position: relative; - padding: 0 !important; - padding-top: 100% !important; - } - .emoji-mart-category-list button img, - .emoji-mart-category-list button span { - height: calc(100% - 10px) !important; - width: calc(100% - 10px) !important; - inset: 5px; - position: absolute !important; - transition: transform 0.1s cubic-bezier(0, 0, 0, 1); - } - .emoji-mart-category-list button:hover img, - .emoji-mart-category-list button:hover span { - transform: scale(1.2); - } - .emoji-picker-dropdown__modifiers { - top: 16px; - } \ No newline at end of file diff --git a/app/lib/activitypub/parser/media_attachment_parser.rb b/app/lib/activitypub/parser/media_attachment_parser.rb index bcbf92214f..56b8b23f84 100644 --- a/app/lib/activitypub/parser/media_attachment_parser.rb +++ b/app/lib/activitypub/parser/media_attachment_parser.rb @@ -15,15 +15,13 @@ class ActivityPub::Parser::MediaAttachmentParser end def remote_url - url = Addressable::URI.parse(@json['url'])&.normalize&.to_s - url unless unsupported_uri_scheme?(url) + Addressable::URI.parse(@json['url'])&.normalize&.to_s rescue Addressable::URI::InvalidURIError nil end def thumbnail_remote_url - url = Addressable::URI.parse(@json['icon'].is_a?(Hash) ? @json['icon']['url'] : @json['icon'])&.normalize&.to_s - url unless unsupported_uri_scheme?(url) + Addressable::URI.parse(@json['icon'].is_a?(Hash) ? @json['icon']['url'] : @json['icon'])&.normalize&.to_s rescue Addressable::URI::InvalidURIError nil end diff --git a/app/lib/activitypub/parser/status_parser.rb b/app/lib/activitypub/parser/status_parser.rb index 03e3f789b5..1968f18468 100644 --- a/app/lib/activitypub/parser/status_parser.rb +++ b/app/lib/activitypub/parser/status_parser.rb @@ -33,10 +33,7 @@ class ActivityPub::Parser::StatusParser end def url - return if @object['url'].blank? - - url = url_to_href(@object['url'], 'text/html') - url unless unsupported_uri_scheme?(url) + url_to_href(@object['url'], 'text/html') if @object['url'].present? end def text diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 99d85a262a..3ead162ec3 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -4,7 +4,6 @@ require 'singleton' class ActivityPub::TagManager include Singleton - include JsonLdHelper include RoutingHelper CONTEXT = 'https://www.w3.org/ns/activitystreams' @@ -18,7 +17,7 @@ class ActivityPub::TagManager end def url_for(target) - return unsupported_uri_scheme?(target.url) ? nil : target.url if target.respond_to?(:local?) && !target.local? + return target.url if target.respond_to?(:local?) && !target.local? return unless target.respond_to?(:object_type) diff --git a/app/lib/admin/metrics/dimension/space_usage_dimension.rb b/app/lib/admin/metrics/dimension/space_usage_dimension.rb index f1b6dba040..0d3fd8db33 100644 --- a/app/lib/admin/metrics/dimension/space_usage_dimension.rb +++ b/app/lib/admin/metrics/dimension/space_usage_dimension.rb @@ -45,7 +45,6 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension PreviewCard.sum(:image_file_size), Account.sum(Arel.sql('COALESCE(avatar_file_size, 0) + COALESCE(header_file_size, 0)')), Backup.sum(:dump_file_size), - Import.sum(:data_file_size), SiteUpload.sum(:file_file_size), ].sum diff --git a/app/lib/http_signature_draft.rb b/app/lib/http_signature_draft.rb index cb794b223a..fc0d498b29 100644 --- a/app/lib/http_signature_draft.rb +++ b/app/lib/http_signature_draft.rb @@ -6,13 +6,14 @@ class HttpSignatureDraft REQUEST_TARGET = '(request-target)' - def initialize(keypair, key_id) + def initialize(keypair, key_id, full_path: true) @keypair = keypair @key_id = key_id + @full_path = full_path end def request_target(verb, url) - if url.query.nil? + if url.query.nil? || !@full_path "#{verb} #{url.path}" else "#{verb} #{url.path}?#{url.query}" diff --git a/app/lib/request.rb b/app/lib/request.rb index 212acf64d0..ad39f928db 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -75,6 +75,7 @@ class Request @url = Addressable::URI.parse(url).normalize @http_client = options.delete(:http_client) @allow_local = options.delete(:allow_local) + @full_path = !options.delete(:omit_query_string) @options = { follow: { max_hops: 3, @@ -101,7 +102,7 @@ class Request key_id = ActivityPub::TagManager.instance.key_uri_for(actor) keypair = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : actor.keypair - @signing = HttpSignatureDraft.new(keypair, key_id) + @signing = HttpSignatureDraft.new(keypair, key_id, full_path: @full_path) self end diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index deead3717d..56ffd2a28c 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -28,13 +28,7 @@ class StatusCacheHydrator def hydrate_non_reblog_payload(empty_payload, account_id, account) empty_payload.tap do |payload| - payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.id) - payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.id) - payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.conversation_id) - payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.id) - payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.id) if @status.account_id == account_id - payload[:filtered] = mapped_applied_custom_filter(account_id, @status) - payload[:emoji_reactions] = @status.emoji_reactions_grouped_by_name(account) + fill_status_payload(payload, @status, account_id, account) if payload[:poll] payload[:poll][:voted] = @status.account_id == account_id @@ -48,19 +42,12 @@ class StatusCacheHydrator payload[:muted] = false payload[:bookmarked] = false payload[:pinned] = false if @status.account_id == account_id - payload[:filtered] = mapped_applied_custom_filter(account_id, @status.reblog) # If the reblogged status is being delivered to the author who disabled the display of the application # used to create the status, we need to hydrate it here too payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id - payload[:reblog][:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.reblog_of_id) - payload[:reblog][:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.reblog_of_id) - payload[:reblog][:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.reblog.conversation_id) - payload[:reblog][:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.reblog_of_id) - payload[:reblog][:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.reblog_of_id) if @status.reblog.account_id == account_id - payload[:reblog][:filtered] = payload[:filtered] - payload[:reblog][:emoji_reactions] = @status.reblog.emoji_reactions_grouped_by_name(account) + fill_status_payload(payload[:reblog], @status.reblog, account_id, account) if payload[:reblog][:poll] if @status.reblog.account_id == account_id @@ -73,11 +60,22 @@ class StatusCacheHydrator end end + payload[:filtered] = payload[:reblog][:filtered] payload[:favourited] = payload[:reblog][:favourited] payload[:reblogged] = payload[:reblog][:reblogged] end end + def fill_status_payload(payload, status, account_id, account) + payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: status.id) + payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: status.id) + payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: status.conversation_id) + payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: status.id) + payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: status.id) if status.account_id == account_id + payload[:filtered] = mapped_applied_custom_filter(account_id, status) + payload[:emoji_reactions] = status.emoji_reactions_grouped_by_name(account) + end + def mapped_applied_custom_filter(account_id, status) CustomFilter .apply_cached_filters(CustomFilter.cached_filters_for(account_id), status, following: following?(account_id)) diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb index 386b49a2df..b67105aade 100644 --- a/app/models/custom_filter.rb +++ b/app/models/custom_filter.rb @@ -38,7 +38,7 @@ class CustomFilter < ApplicationRecord include Expireable include Redisable - enum :action, { warn: 0, hide: 1, blur: 2 }, suffix: :action + enum :action, { warn: 0, hide: 1, blur: 2 }, suffix: :action, validate: true belongs_to :account has_many :keywords, class_name: 'CustomFilterKeyword', inverse_of: :custom_filter, dependent: :destroy diff --git a/app/models/import.rb b/app/models/import.rb deleted file mode 100644 index 6b261f8d00..0000000000 --- a/app/models/import.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: imports -# -# id :bigint(8) not null, primary key -# type :integer not null -# approved :boolean default(FALSE), not null -# created_at :datetime not null -# updated_at :datetime not null -# data_file_name :string -# data_content_type :string -# data_updated_at :datetime -# account_id :bigint(8) not null -# overwrite :boolean default(FALSE), not null -# data_file_size :integer -# - -# NOTE: This is a deprecated model, only kept to not break ongoing imports -# on upgrade. See `BulkImport` and `Form::Import` for its replacements. - -class Import < ApplicationRecord - FILE_TYPES = %w(text/plain text/csv application/csv).freeze - MODES = %i(merge overwrite).freeze - - self.inheritance_column = false - - belongs_to :account - - enum :type, { following: 0, blocking: 1, muting: 2, domain_blocking: 3, bookmarks: 4 } - - validates :type, presence: true - - has_attached_file :data - validates_attachment_content_type :data, content_type: FILE_TYPES - validates_attachment_presence :data - - def mode - overwrite? ? :overwrite : :merge - end - - def mode=(str) - self.overwrite = str.to_sym == :overwrite - end -end diff --git a/app/models/list.rb b/app/models/list.rb index a568b11776..a441d065cf 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -20,7 +20,7 @@ class List < ApplicationRecord PER_ACCOUNT_LIMIT = 50 - enum :replies_policy, { list: 0, followed: 1, none: 2 }, prefix: :show + enum :replies_policy, { list: 0, followed: 1, none: 2 }, prefix: :show, validate: true belongs_to :account diff --git a/app/serializers/rest/web_push_subscription_serializer.rb b/app/serializers/rest/web_push_subscription_serializer.rb index 4cb980bb93..01825a3bb0 100644 --- a/app/serializers/rest/web_push_subscription_serializer.rb +++ b/app/serializers/rest/web_push_subscription_serializer.rb @@ -6,7 +6,7 @@ class REST::WebPushSubscriptionSerializer < ActiveModel::Serializer delegate :standard, to: :object def alerts - (object.data&.dig('alerts') || {}).each_with_object({}) { |(k, v), h| h[k] = ActiveModel::Type::Boolean.new.cast(v) } + (object.data&.dig('alerts') || {}).transform_values { |v| ActiveModel::Type::Boolean.new.cast(v) } end def server_key diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index 6a6d9e391a..f2e4f45104 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -57,7 +57,20 @@ class ActivityPub::FetchRepliesService < BaseService return unless @allow_synchronous_requests return if non_matching_uri_hosts?(@reference_uri, collection_or_uri) - fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary) + # NOTE: For backward compatibility reasons, Mastodon signs outgoing + # queries incorrectly by default. + # + # While this is relevant for all URLs with query strings, this is + # the only code path where this happens in practice. + # + # Therefore, retry with correct signatures if this fails. + begin + fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary) + rescue Mastodon::UnexpectedResponseError => e + raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present? + + fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary, request_options: { omit_query_string: false }) + end end def filter_replies(items) diff --git a/app/services/import_service.rb b/app/services/import_service.rb deleted file mode 100644 index a695df2fc9..0000000000 --- a/app/services/import_service.rb +++ /dev/null @@ -1,144 +0,0 @@ -# frozen_string_literal: true - -require 'csv' - -# NOTE: This is a deprecated service, only kept to not break ongoing imports -# on upgrade. See `BulkImportService` for its replacement. - -class ImportService < BaseService - ROWS_PROCESSING_LIMIT = 20_000 - - def call(import) - @import = import - @account = @import.account - - case @import.type - when 'following' - import_follows! - when 'blocking' - import_blocks! - when 'muting' - import_mutes! - when 'domain_blocking' - import_domain_blocks! - when 'bookmarks' - import_bookmarks! - end - end - - private - - def import_follows! - parse_import_data!(['Account address']) - import_relationships!('follow', 'unfollow', @account.following, ROWS_PROCESSING_LIMIT, reblogs: { header: 'Show boosts', default: true }, notify: { header: 'Notify on new posts', default: false }, languages: { header: 'Languages', default: nil }) - end - - def import_blocks! - parse_import_data!(['Account address']) - import_relationships!('block', 'unblock', @account.blocking, ROWS_PROCESSING_LIMIT) - end - - def import_mutes! - parse_import_data!(['Account address']) - import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT, notifications: { header: 'Hide notifications', default: true }) - end - - def import_domain_blocks! - parse_import_data!(['#domain']) - items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#domain'].strip } - - if @import.overwrite? - presence_hash = items.index_with(true) - - @account.domain_blocks.find_each do |domain_block| - if presence_hash[domain_block.domain] - items.delete(domain_block.domain) - else - @account.unblock_domain!(domain_block.domain) - end - end - end - - items.each do |domain| - @account.block_domain!(domain) - end - - AfterAccountDomainBlockWorker.push_bulk(items) do |domain| - [@account.id, domain] - end - end - - def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {}) - local_domain_suffix = "@#{Rails.configuration.x.local_domain}" - items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), extra_fields.to_h { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }] }.reject { |(id, _)| id.blank? } - - if @import.overwrite? - presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] } - - overwrite_scope.reorder(nil).find_each do |target_account| - if presence_hash[target_account.acct] - items.delete(target_account.acct) - extra = presence_hash[target_account.acct][1] - Import::RelationshipWorker.perform_async(@account.id, target_account.acct, action, extra.stringify_keys) - else - Import::RelationshipWorker.perform_async(@account.id, target_account.acct, undo_action) - end - end - end - - head_items = items.uniq { |acct, _| acct.split('@')[1] } - tail_items = items - head_items - - Import::RelationshipWorker.push_bulk(head_items + tail_items) do |acct, extra| - [@account.id, acct, action, extra.stringify_keys] - end - end - - def import_bookmarks! - parse_import_data!(['#uri']) - items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#uri'].strip } - - if @import.overwrite? - presence_hash = items.index_with(true) - - @account.bookmarks.find_each do |bookmark| - if presence_hash[bookmark.status.uri] - items.delete(bookmark.status.uri) - else - bookmark.destroy! - end - end - end - - statuses = items.filter_map do |uri| - status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status) - next if status.nil? && ActivityPub::TagManager.instance.local_uri?(uri) - - status || ActivityPub::FetchRemoteStatusService.new.call(uri) - rescue *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError - nil - rescue => e - Rails.logger.warn "Unexpected error when importing bookmark: #{e}" - nil - end - - account_ids = statuses.map(&:account_id) - preloaded_relations = @account.relations_map(account_ids, skip_blocking_and_muting: true) - - statuses.keep_if { |status| StatusPolicy.new(@account, status, preloaded_relations).show? } - - statuses.each do |status| - @account.bookmarks.find_or_create_by!(account: @account, status: status) - end - end - - def parse_import_data!(default_headers) - data = CSV.parse(import_data, headers: true) - data = CSV.parse(import_data, headers: default_headers) unless data.headers&.first&.strip&.include?(' ') - @data = data.compact_blank - end - - def import_data - Paperclip.io_adapters.for(@import.data).read.force_encoding(Encoding::UTF_8) - end -end diff --git a/app/views/admin/announcements/previews/show.html.haml b/app/views/admin/announcements/previews/show.html.haml index fdfbf598b5..54d5d45ed6 100644 --- a/app/views/admin/announcements/previews/show.html.haml +++ b/app/views/admin/announcements/previews/show.html.haml @@ -7,6 +7,8 @@ = material_symbol 'chevron_left' = t('admin.announcements.back') +.flash-message.info= t('admin.announcements.preview.disclaimer') + %p.lead = t('admin.announcements.preview.explanation_html', count: @user_count, display_count: number_with_delimiter(@user_count)) diff --git a/app/workers/import/relationship_worker.rb b/app/workers/import/relationship_worker.rb deleted file mode 100644 index 2298b095a7..0000000000 --- a/app/workers/import/relationship_worker.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -# NOTE: This is a deprecated worker, only kept to not break ongoing imports -# on upgrade. See `Import::RowWorker` for its replacement. - -class Import::RelationshipWorker - include Sidekiq::Worker - - sidekiq_options queue: 'pull', retry: 8, dead: false - - def perform(account_id, target_account_uri, relationship, options) - from_account = Account.find(account_id) - target_domain = domain(target_account_uri) - target_account = stoplight_wrapper(target_domain).run { ResolveAccountService.new.call(target_account_uri, { check_delivery_availability: true }) } - options.symbolize_keys! - - return if target_account.nil? - - case relationship - when 'follow' - begin - FollowService.new.call(from_account, target_account, **options) - rescue ActiveRecord::RecordInvalid - raise if FollowLimitValidator.limit_for_account(from_account) < from_account.following_count - end - when 'unfollow' - UnfollowService.new.call(from_account, target_account) - when 'block' - BlockService.new.call(from_account, target_account) - when 'unblock' - UnblockService.new.call(from_account, target_account) - when 'mute' - MuteService.new.call(from_account, target_account, **options) - when 'unmute' - UnmuteService.new.call(from_account, target_account) - end - rescue ActiveRecord::RecordNotFound - true - end - - def domain(uri) - domain = uri.is_a?(Account) ? uri.domain : uri.split('@')[1] - TagManager.instance.local_domain?(domain) ? nil : TagManager.instance.normalize_domain(domain) - end - - def stoplight_wrapper(domain) - if domain.present? - Stoplight("source:#{domain}") - .with_fallback { nil } - .with_threshold(1) - .with_cool_off_time(5.minutes.seconds) - .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) } - else - Stoplight('domain-blank') - end - end -end diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb deleted file mode 100644 index b6afb972a9..0000000000 --- a/app/workers/import_worker.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -# NOTE: This is a deprecated worker, only kept to not break ongoing imports -# on upgrade. See `ImportWorker` for its replacement. - -class ImportWorker - include Sidekiq::Worker - - sidekiq_options queue: 'pull', retry: false - - def perform(import_id) - import = Import.find(import_id) - ImportService.new.call(import) - ensure - import&.destroy - end -end diff --git a/app/workers/unfilter_notifications_worker.rb b/app/workers/unfilter_notifications_worker.rb index 53a35ce12c..cb8a46b8f4 100644 --- a/app/workers/unfilter_notifications_worker.rb +++ b/app/workers/unfilter_notifications_worker.rb @@ -4,25 +4,14 @@ class UnfilterNotificationsWorker include Sidekiq::Worker include Redisable - # Earlier versions of the feature passed a `notification_request` ID - # If `to_account_id` is passed, the first argument is an account ID - # TODO for after 4.3.0: drop the single-argument case - def perform(notification_request_or_account_id, from_account_id = nil) - if from_account_id.present? - @notification_request = nil - @from_account = Account.find_by(id: from_account_id) - @recipient = Account.find_by(id: notification_request_or_account_id) - else - @notification_request = NotificationRequest.find_by(id: notification_request_or_account_id) - @from_account = @notification_request&.from_account - @recipient = @notification_request&.account - end + def perform(account_id, from_account_id) + @from_account = Account.find_by(id: from_account_id) + @recipient = Account.find_by(id: account_id) return if @from_account.nil? || @recipient.nil? push_to_conversations! unfilter_notifications! - remove_request! decrement_worker_count! end @@ -36,10 +25,6 @@ class UnfilterNotificationsWorker filtered_notifications.in_batches.update_all(filtered: false) end - def remove_request! - @notification_request&.destroy! - end - def filtered_notifications Notification.where(account: @recipient, from_account: @from_account, filtered: true) end diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb deleted file mode 100644 index e0ad54d8c3..0000000000 --- a/config/initializers/deprecations.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -if ENV['REDIS_NAMESPACE'] - es_configured = ENV['ES_ENABLED'] == 'true' || ENV.fetch('ES_HOST', 'localhost') != 'localhost' || ENV.fetch('ES_PORT', '9200') != '9200' || ENV.fetch('ES_PASS', 'password') != 'password' - - warn <<~MESSAGE - WARNING: the REDIS_NAMESPACE environment variable is deprecated and will be removed in Mastodon 4.4.0. - - Please see documentation at https://github.com/mastodon/redis_namespace_migration - MESSAGE - - warn <<~MESSAGE if es_configured && !ENV['ES_PREFIX'] - - In addition, as REDIS_NAMESPACE is being used as a prefix for Elasticsearch, please do not forget to set ES_PREFIX to "#{ENV.fetch('REDIS_NAMESPACE')}". - MESSAGE -end diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index ed16d50a76..6d908fa477 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -169,7 +169,7 @@ else end Rails.application.reloader.to_prepare do - Paperclip.options[:content_type_mappings] = { csv: Import::FILE_TYPES } + Paperclip.options[:content_type_mappings] = { csv: %w(text/plain text/csv application/csv) } end # In some places in the code, we rescue this exception, but we don't always diff --git a/config/locales/activerecord.es-MX.yml b/config/locales/activerecord.es-MX.yml index c3b0562c32..02384f1c71 100644 --- a/config/locales/activerecord.es-MX.yml +++ b/config/locales/activerecord.es-MX.yml @@ -56,7 +56,7 @@ es-MX: user: attributes: date_of_birth: - below_limit: está por debajo de la edad mínima + below_limit: está por debajo del límite de edad email: blocked: utiliza un proveedor de correo no autorizado unreachable: no parece existir diff --git a/config/locales/activerecord.ga.yml b/config/locales/activerecord.ga.yml index e5b07470ae..853c705663 100644 --- a/config/locales/activerecord.ga.yml +++ b/config/locales/activerecord.ga.yml @@ -55,6 +55,8 @@ ga: too_soon: róluath, caithfidh sé bheith níos déanaí ná %{date} user: attributes: + date_of_birth: + below_limit: faoi ​​bhun na teorann aoise email: blocked: úsáideann soláthraí ríomhphoist dícheadaithe unreachable: ní cosúil go bhfuil sé ann diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 1bdf202fca..37cd0b3bf6 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -325,6 +325,7 @@ cs: create: Vytvořit oznámení title: Nové oznámení preview: + disclaimer: Vzhledem k tomu, že se od nich uživatelé nemohou odhlásit, měla by být e-mailová upozornění omezena na důležitá oznámení, jako je narušení osobních údajů nebo oznámení o uzavření serveru. explanation_html: 'E-mail bude odeslán %{display_count} uživatelům. Následující text bude zahrnut do onoho e-mailu:' title: Náhled oznámení publish: Zveřejnit diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 9349176842..edd89fedc6 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -511,6 +511,36 @@ cy: new: title: Mewnforio blociau parth no_file: Heb ddewis ffeil + fasp: + debug: + callbacks: + created_at: Crëwyd am + delete: Dileu + ip: Cyfeiriad IP + request_body: Gofyn am y corff + title: Adalwasau Dadfygio + providers: + active: Gweithredol + base_url: URL sylfaen + callback: Adalwad + delete: Dileu + edit: Golygu Darparwr + finish_registration: Gorffen cofrestru + name: Enw + providers: Darparwyr + public_key_fingerprint: Ôl bys allwedd cyhoeddus + registration_requested: Cais am gofrestru + registrations: + confirm: Cadarnhau + description: Rydych wedi derbyn cofrestriad gan FASP. Gwrthodwch hyn os nad chi ofynnodd amdano. Os taw chi gychwynnodd hyn, cymharwch yr enw ac allwedd yr ôl bys yn ofalus cyn cadarnhau'r cofrestriad. + reject: Gwrthod + title: Cadarnhau Cofrestriad FASP + save: Cadw + select_capabilities: Dewis Galluoedd + sign_in: Mewngofnodi + status: Statws + title: Fediverse Auxiliary Service Providers + title: FASP follow_recommendations: description_html: "Mae dilyn yr argymhellion yn helpu i ddefnyddwyr newydd ddod o hyd i gynnwys diddorol yn gyflym. Pan nad yw defnyddiwr wedi rhyngweithio digon ag eraill i ffurfio argymhellion dilyn personol, argymhellir y cyfrifon hyn yn lle hynny. Cânt eu hailgyfrifo'n ddyddiol o gymysgedd o gyfrifon gyda'r ymgysylltiadau diweddar uchaf a'r cyfrif dilynwyr lleol uchaf ar gyfer iaith benodol." language: Ar gyfer iaith diff --git a/config/locales/da.yml b/config/locales/da.yml index b0314e0f74..63a414811e 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -319,6 +319,7 @@ da: create: Opret bekendtgørelse title: Ny bekendtgørelse preview: + disclaimer: Da brugere ikke kan fravælge e-mailnotifikationer, bør disse begrænses til vigtige emner som f.eks. personlige databrud eller serverlukninger. explanation_html: 'E-mailen sendes til %{display_count} brugere. Flg. tekst medtages i e-mailen:' title: Forhåndsvis annonceringsnotifikation publish: Publicér diff --git a/config/locales/de.yml b/config/locales/de.yml index ab0bc53fcc..0842ab73d7 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -319,6 +319,7 @@ de: create: Ankündigung erstellen title: Neue Ankündigung preview: + disclaimer: Da sich Profile nicht davon abmelden können, sollten Benachrichtigungen per E-Mail auf wichtige Ankündigungen wie z. B. zu Datenpannen oder Serverabschaltung beschränkt sein. explanation_html: 'Die E-Mail wird an %{display_count} Nutzer*innen gesendet. Folgendes wird in der E-Mail enthalten sein:' title: Vorschau der Ankündigung publish: Veröffentlichen @@ -497,6 +498,7 @@ de: registration_requested: Registrierung angefordert registrations: confirm: Bestätigen + description: Sie haben eine Registrierung von einer FASP erhalten. Lehnen Sie ab, wenn Sie dies nicht initiiert haben. Wenn Sie dies initiiert haben, vergleichen Sie Namen und Fingerabdruck vor der Bestätigung der Registrierung. reject: Ablehnen title: FASP-Registrierung bestätigen save: Speichern diff --git a/config/locales/doorkeeper.kab.yml b/config/locales/doorkeeper.kab.yml index fa9e1c540a..c65bada409 100644 --- a/config/locales/doorkeeper.kab.yml +++ b/config/locales/doorkeeper.kab.yml @@ -111,9 +111,9 @@ kab: lists: Tibdarin media: Imeddayen n umidya mutes: Yeggugem - notifications: Alɣuten + notifications: Ilɣa profile: Amaɣnu-k Mastodon - push: Alɣuten yettudemmren + push: Ilɣa yettudemmren reports: Ineqqisen search: Nadi statuses: Tisuffaɣ @@ -127,7 +127,7 @@ kab: admin:read: ad iɣeṛ akk isefka ɣef uqeddac admin:write: ad iẓreg akk isefka ɣef uqeddac follow: ad ibeddel assaɣen n umiḍan - push: ad iṭṭef-d alɣuten-ik·im yettwademren + push: ad iṭṭef-d ilɣa-k·m yettwademren read: ad iɣeṛ akk isefka n umiḍan-ik·im read:accounts: ẓer isallen n yimiḍanen read:blocks: ẓer imiḍanen i tesḥebseḍ @@ -136,7 +136,7 @@ kab: read:follows: ẓer imeḍfaṛen-ik read:lists: ẓer tibdarin-ik·im read:mutes: ẓer wid i tesgugmeḍ - read:notifications: ad iẓer alɣuten-inek·inem + read:notifications: ad iẓer ilɣa-inek·inem read:reports: ẓer ineqqisen-ik·im read:search: anadi deg umkan-ik·im read:statuses: ad iẓer meṛṛa tisuffaɣ @@ -148,4 +148,4 @@ kab: write:follows: ḍfeṛ imdanen write:lists: ad yesnulfu tibdarin write:media: ad yessali ifuyla n umidya - write:notifications: sfeḍ alɣuten-ik·im + write:notifications: sfeḍ ilɣa-k·m diff --git a/config/locales/doorkeeper.ms.yml b/config/locales/doorkeeper.ms.yml index f89def7b85..aadce76efd 100644 --- a/config/locales/doorkeeper.ms.yml +++ b/config/locales/doorkeeper.ms.yml @@ -128,11 +128,11 @@ ms: crypto: Penyulitan hujung ke hujung favourites: Sukaan filters: Penapis - follow: Ikut, Senyap dan Blok + follow: Ikutan, Redaman dan Sekatan follows: Ikutan lists: Senarai media: Lampiran media - mutes: Senyapkan + mutes: Redaman notifications: Pemberitahuan push: Pemberitahuan segera reports: Laporan @@ -173,7 +173,7 @@ ms: read:filters: lihat penapis anda read:follows: lihat senarai yang anda ikuti read:lists: lihat senarai anda - read:mutes: lihat senarai yang anda senyapkan + read:mutes: lihat redamanku read:notifications: lihat notifikasi anda read:reports: lihat laporan anda read:search: cari bagi pihak anda @@ -182,13 +182,13 @@ ms: write:accounts: ubaisuai profail anda write:blocks: domain dan akaun blok write:bookmarks: menandabuku hantaran - write:conversations: senyapkan dan padamkan perbualan + write:conversations: redamkan dan padamkan perbualan write:favourites: hantaran disukai write:filters: cipta penapis write:follows: ikut orang write:lists: cipta senarai write:media: memuat naik fail media - write:mutes: membisukan orang dan perbualan + write:mutes: redamkan orang dan perbualan write:notifications: kosongkan pemberitahuan anda write:reports: melaporkan orang lain write:statuses: terbitkan hantaran diff --git a/config/locales/el.yml b/config/locales/el.yml index 2c6d91298e..d2f4fbba01 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -318,6 +318,9 @@ el: new: create: Δημιουργία ανακοίνωσης title: Νέα ανακοίνωση + preview: + explanation_html: 'Το email θα αποσταλεί σε %{display_count} χρήστες. Το ακόλουθο κείμενο θα συμπεριληφθεί στο e-mail:' + title: Προεπισκόπηση ειδοποίησης ανακοίνωσης publish: Δημοσίευση published_msg: Επιτυχής δημοσίευση ανακοίνωσης! scheduled_for: Προγραμματισμένη για %{time} @@ -476,6 +479,34 @@ el: new: title: Εισαγωγή αποκλεισμένων τομέων no_file: Δεν επιλέχθηκε αρχείο + fasp: + debug: + callbacks: + created_at: Δημιουργήθηκε στις + delete: Διαγραφή + ip: Διεύθυνση IP + request_body: Σώμα αιτήματος + title: Κλήσεις Αποσφαλμάτωσης + providers: + active: Ενεργό + base_url: URL βάσης + callback: Επανάκληση + delete: Διαγραφή + edit: Επεξεργασία Παρόχου + finish_registration: Ολοκλήρωση εγγραφής + name: Όνομα + providers: Πάροχοι + public_key_fingerprint: Αποτύπωμα δημόσιου κλειδιού + registration_requested: Η εγγραφή ζητήθηκε + registrations: + confirm: Επιβεβαίωση + description: Έλαβες μια εγγραφή από ένα FASP. Απέρριψέ την αν δεν την άρχισες εσύ. Αν το άρχισες εσύ, σύγκρινε προσεκτικά το όνομα και το κλειδί αποτύπωμα πριν από την επιβεβαίωση της εγγραφής. + reject: Απόρριψη + title: Επιβεβαίωση Εγγραφής FASP + save: Αποθήκευση + select_capabilities: Επέλεξε Δυνατότητες + sign_in: Σύνδεση + status: Κατάσταση follow_recommendations: description_html: "Ακολουθώντας συστάσεις βοηθάει τους νέους χρήστες να βρουν γρήγορα ενδιαφέρον περιεχόμενο. Όταν ένας χρήστης δεν έχει αλληλεπιδράσει με άλλους αρκετά για να διαμορφώσει εξατομικευμένες συστάσεις, συνιστώνται αυτοί οι λογαριασμοί. Υπολογίζονται εκ νέου σε καθημερινή βάση από ένα σύνολο λογαριασμών με τις υψηλότερες πρόσφατες αλληλεπιδράσεις και μεγαλύτερο αριθμό τοπικών ακόλουθων για μια δεδομένη γλώσσα." language: Για τη γλώσσα diff --git a/config/locales/en.yml b/config/locales/en.yml index 66148cfefe..dca6a2d9f8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -335,6 +335,7 @@ en: create: Create announcement title: New announcement preview: + disclaimer: As users cannot opt out of them, email notifications should be limited to important announcements such as personal data breach or server closure notifications. explanation_html: 'The email will be sent to %{display_count} users. The following text will be included in the e-mail:' title: Preview announcement notification publish: Publish @@ -2280,10 +2281,7 @@ en: themes: contrast: Mastodon (High contrast) default: Mastodon (Dark) - modern-dark: Modern Dark - modern-light: Modern Light - modern-contrast: Modern Contrast - full-dark: Full Dark + full-dark: フルダーク mastodon-light: Mastodon (Light) system: Automatic (use system theme) time: diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index dd92aed5f6..ab3d307fd8 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -319,6 +319,7 @@ es-AR: create: Crear anuncio title: Nuevo anuncio preview: + disclaimer: Como los usuarios no pueden excluirse de ellas, las notificaciones por correo electrónico deberían limitarse a anuncios importantes como la violación de datos personales o las notificaciones de cierre del servidor. explanation_html: 'El correo electrónico se enviará a %{display_count} usuarios. En el correo electrónico se incluirá el siguiente texto:' title: Previsualizar la notificación del anuncio publish: Publicar diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 5f92f2c85d..3032b663a1 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -319,6 +319,7 @@ es-MX: create: Crear anuncio title: Nuevo anuncio preview: + disclaimer: Como los usuarios no pueden optar por no recibirlas, las notificaciones por correo electrónico deben limitarse a anuncios importantes, como la violación de datos personales o las notificaciones de cierre de servidores. explanation_html: 'El correo electrónico se enviará a %{display_count} usuarios. En el correo electrónico se incluirá el siguiente texto:' title: Vista previa de la notificación del anuncio publish: Publicar @@ -500,7 +501,7 @@ es-MX: registration_requested: Se solicitó el registro registrations: confirm: Confirmar - description: Has recibido un registro de un FASP. Recházalo si no lo iniciaste tú. Si lo iniciaste tú, compara cuidadosamente el nombre y la huella de la clave antes de confirmar el registro. + description: Has recibido un registro de un FASP. Recházalo si no lo iniciaste tú. Si lo comenzaste tú, compara cuidadosamente el nombre y la huella de la clave antes de confirmar el registro. reject: Rechazar title: Confirmar registro FASP save: Guardar diff --git a/config/locales/es.yml b/config/locales/es.yml index ed09dfb76b..9e0feb9d18 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -3,7 +3,7 @@ es: about: about_mastodon_html: 'La red social del futuro: ¡Sin anuncios, sin vigilancia corporativa, diseño ético, y descentralización! ¡Sé dueño de tu información con Mastodon!' contact_missing: No establecido - contact_unavailable: No disponible + contact_unavailable: N/D hosted_on: Mastodon alojado en %{domain} title: Acerca de accounts: @@ -319,6 +319,7 @@ es: create: Crear anuncio title: Nuevo anuncio preview: + disclaimer: Como los usuarios no pueden optar por no recibirlas, las notificaciones por correo electrónico deben limitarse a anuncios importantes, como la violación de datos personales o las notificaciones de cierre de servidores. explanation_html: 'El correo electrónico se enviará a %{display_count} usuarios. En el correo electrónico se incluirá el siguiente texto:' title: Vista previa de la notificación del anuncio publish: Publicar diff --git a/config/locales/fa.yml b/config/locales/fa.yml index b5dce6dabf..f1c74829c9 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -479,6 +479,22 @@ fa: new: title: درون‌ریزی انسدادهای دامنه no_file: هیچ پرونده‌ای گزیده نشده + fasp: + debug: + callbacks: + delete: حذف + providers: + active: فعال + delete: حذف + finish_registration: تکمیل ثبت‌نام + name: نام + providers: ارائه دهندگان + registrations: + confirm: تایید + reject: رد کردن + save: ذخیره + sign_in: ورود + status: وضعیت follow_recommendations: description_html: "پیشنهادات پیگیری به کاربران جدید کک می‌کند تا سریع‌تر محتوای جالب را پیدا کنند. زمانی که کاربری هنوز به اندازه کافی با دیگران تعامل نداشته است تا پیشنهادات پیگیری شخصی‌سازی‌شده دریافت کند، این حساب‌ها را به جای آن فهرست مشاهده خواهد کرد. این حساب‌ها به صورت روزانه و در ترکیب با بیشتری تعاملات و بالاترین دنبال‌کنندگان محلی برای یک زبان مشخص بازمحاسبه می‌شوند." language: برای زبان diff --git a/config/locales/fi.yml b/config/locales/fi.yml index e999748ad9..9a5bf97afe 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -319,6 +319,7 @@ fi: create: Luo tiedote title: Uusi tiedote preview: + disclaimer: Koska käyttäjät eivät voi kieltäytyä niistä, sähköposti-ilmoitukset tulee rajata tärkeisiin tiedotteisiin, kuten ilmoituksiin henkilötietojen tietoturvaloukkauksista tai palvelimen sulkeutumisesta. explanation_html: "%{display_count} käyttäjälle lähetetään sähköpostia. Sähköpostiviestiin sisällytetään seuraava teksti:" title: Esikatsele tiedoteilmoitus publish: Julkaise @@ -457,7 +458,7 @@ fi: domain: Verkkotunnus new: create: Lisää verkkotunnus - resolve: Selvitä verkkotunnus + resolve: Resolvoi verkkotunnus title: Estä uusi sähköpostiverkkotunnus no_email_domain_block_selected: Sähköpostiverkkotunnusten estoja ei muutettu, koska yhtäkään ei ollut valittuna not_permitted: Ei sallittu diff --git a/config/locales/fo.yml b/config/locales/fo.yml index ec38374070..8e611c346d 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -319,6 +319,7 @@ fo: create: Stovna kunngerð title: Nýggj kunngerð preview: + disclaimer: Av tí at brúkarar ikki kunnu velja tær frá, eiga teldupostfráboðanir at vera avmarkaðar til týdningarmiklar kunngerðir, sosum trygdarbrot og boð um at ambætarin verður tikin niður. explanation_html: 'Teldubrævið verður sent til %{display_count} brúkarar. Fylgjandi tekstur kemur við í teldubrævið:' title: Undanvís fráboðan um kunngerð publish: Legg út diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index dd751c0678..e85952f91c 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -482,6 +482,15 @@ fr-CA: new: title: Importer des blocages de domaine no_file: Aucun fichier sélectionné + fasp: + providers: + registrations: + confirm: Confirmer + reject: Rejeter + save: Enregistrer + select_capabilities: Sélectionnez les Capacités + sign_in: Se connecter + status: État follow_recommendations: description_html: "Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée." language: Pour la langue diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6ad2733f3e..25218bd019 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -482,6 +482,15 @@ fr: new: title: Importer des blocages de domaine no_file: Aucun fichier sélectionné + fasp: + providers: + registrations: + confirm: Confirmer + reject: Rejeter + save: Enregistrer + select_capabilities: Sélectionnez les Capacités + sign_in: Se connecter + status: État follow_recommendations: description_html: "Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée." language: Pour la langue diff --git a/config/locales/ga.yml b/config/locales/ga.yml index d552c72de7..58d02fc6a9 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -503,6 +503,36 @@ ga: new: title: Iompórtáil bloic fearainn no_file: Níor roghnaíodh aon chomhad + fasp: + debug: + callbacks: + created_at: Cruthaithe ag + delete: Scrios + ip: Seoladh IP + request_body: Comhlacht iarratais + title: Glaonna Dífhabhtaithe + providers: + active: Gníomhach + base_url: Bun-URL + callback: Glao ar ais + delete: Scrios + edit: Cuir Soláthraí in Eagar + finish_registration: Críochnaigh clárú + name: Ainm + providers: Soláthraithe + public_key_fingerprint: Méarloirg eochair phoiblí + registration_requested: Clárú iarrtha + registrations: + confirm: Deimhnigh + description: Fuair ​​tú clárú ó FASP. Diúltaigh é murar chuir tú tús leis seo. Má thionscain tú é seo, déan comparáid chúramach idir an t-ainm agus an eochair-mhéarlorg sula ndearbhaítear an clárúchán. + reject: Diúltaigh + title: Deimhnigh Clárú FASP + save: Sábháil + select_capabilities: Roghnaigh Cumais + sign_in: Sínigh Isteach + status: Stádas + title: Soláthraithe Seirbhíse Cúnta Fediverse + title: FASP follow_recommendations: description_html: "Lean na moltaí cabhraíonn sé le húsáideoirí nua ábhar suimiúil a aimsiú go tapa. Nuair nach mbíonn go leor idirghníomhaithe ag úsáideoir le daoine eile chun moltaí pearsantaithe a leanúint, moltar na cuntais seo ina ionad sin. Déantar iad a athríomh ar bhonn laethúil ó mheascán de chuntais a bhfuil na rannpháirtíochtaí is airde acu le déanaí agus na háirimh áitiúla is airde leanúna do theanga ar leith." language: Don teanga diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 1da5c146c7..ed51799e8a 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -319,6 +319,7 @@ gl: create: Crear anuncio title: Novo anuncio preview: + disclaimer: As usuarias non poden omitilas, as notificiacións por correo deberían limitarse a anuncios importantes como fugas de datos personais ou notificación do cese do servizo. explanation_html: 'Vaise enviar o correo a %{display_count} usuarias. Incluirase o seguinte texto no correo:' title: Previsualización da notificación do anuncio publish: Publicar diff --git a/config/locales/he.yml b/config/locales/he.yml index f9b2970fd0..457569d05a 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -232,7 +232,7 @@ he: silence_account: הגבלת חשבון suspend_account: השעיית חשבון unassigned_report: ביטול הקצאת דו"ח - unblock_email_account: ביטול חסימת כתובת דוא"ל + unblock_email_account: הסרת חסימת כתובת דוא"ל unsensitive_account: ביטול Force-Sensitive לחשבון unsilence_account: ביטול השתקת חשבון unsuspend_account: ביטול השעיית חשבון @@ -263,11 +263,11 @@ he: create_user_role_html: "%{name} יצר את התפקיד של %{target}" demote_user_html: "%{name} הוריד/ה בדרגה את המשתמש %{target}" destroy_announcement_html: "%{name} מחק/ה את ההכרזה %{target}" - destroy_canonical_email_block_html: "%{name} הסיר/ה חסימה מדואל %{target}" + destroy_canonical_email_block_html: "%{name} הסירו חסימה מדואל %{target}" destroy_custom_emoji_html: "%{name} מחק אמוג'י של %{target}" destroy_domain_allow_html: "%{name} לא התיר/ה פדרציה עם הדומיין %{target}" - destroy_domain_block_html: "%{name} הסיר/ה חסימה מהדומיין %{target}" - destroy_email_domain_block_html: '%{name} הסיר/ה חסימה מדומיין הדוא"ל %{target}' + destroy_domain_block_html: החסימה על מתחם %{target} הוסרה ע"י %{name} + destroy_email_domain_block_html: הוסרה חסימת מתחם דוא"ל %{target} בידי %{name} destroy_instance_html: "%{name} טיהר/ה את הדומיין %{target}" destroy_ip_block_html: "%{name} מחק/ה את הכלל עבור IP %{target}" destroy_relay_html: "%{name} מחקו את הממסר %{target}" @@ -325,6 +325,7 @@ he: create: יצירת הכרזה title: הכרזה חדשה preview: + disclaimer: כיוון שהמשתמשים לא יכולים לבטל אותם, הודעות דוא"ל צריכות להיות מוגבלות בשימוש להודעות חשובות כגון הודעות על גניבת מידע אישי או הודעות על סגירת השרת. explanation_html: 'הדואל ישלח אל %{display_count} משתמשיםות. להלן המלל שישלח בדואל:' title: צפיה מקדימה בהודעה publish: פרסום @@ -495,6 +496,36 @@ he: new: title: יבוא רשימת שרתים חסומים no_file: לא נבחר קובץ + fasp: + debug: + callbacks: + created_at: תאריך יצירה + delete: מחיקה + ip: כתובת IP + request_body: גוף הבקשה + title: ניפוי תקלות בקריאות חוזרות + providers: + active: פעילים + base_url: קישור בסיס + callback: קריאה חוזרת + delete: מחיקה + edit: עריכת ספק + finish_registration: סיום הרשמה + name: שם + providers: ספקים + public_key_fingerprint: טביעת האצבע של המפתח הציבורי + registration_requested: נדרשת הרשמה + registrations: + confirm: אישור + description: קיבלת הרשמה דרך FASP. יש לדחות אותה אם לא ביקשת את ההרשמה הזו מיוזמתך. אם זו בקשה מיוזמתך, יש להשוות בהקפדה אם השם וטביעת האצבע של המפתח הציבורי תואמים לפני אישור הרישום. + reject: דחיה + title: אישור הרשמת FASP + save: שמירה + select_capabilities: בחירת יכולות + sign_in: כניסה + status: מצב + title: ספקי משנה לפדיוורס + title: פרוטוקול FASP follow_recommendations: description_html: "עקבו אחר ההמלצות על מנת לעזור למשתמשים חדשים למצוא תוכן מעניין. במידה ומשתמש לא תקשר מספיק עם משתמשים אחרים כדי ליצור המלצות מעקב, חשבונות אלה יומלצו במקום. הם מחושבים מחדש על בסיסי יומיומי מתערובת של החשבונות הפעילים ביותר עם החשבונות הנעקבים ביותר עבור שפה נתונה." language: עבור שפה diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 272dc0eb21..ef5d8d97e6 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -319,6 +319,7 @@ hu: create: Közlemény létrehozása title: Új közlemény preview: + disclaimer: Mivel a felhasználók nem iratkozhatnak le róluk, az e-mailes értesítéseket érdemes a fontos bejelentésekre korlátozni, mint a személyes adatokat érintő adatvédelmi incidensek vagy a kiszolgáló bezárására vonatkozó értesítések. explanation_html: 'Az e-mail %{display_count} felhasználónak lesz elküldve. A következő szöveg fog szerepelni a levélben:' title: Közleményértesítés előnézete publish: Közzététel diff --git a/config/locales/is.yml b/config/locales/is.yml index ef407a6a0e..0516fa4eb8 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -319,6 +319,7 @@ is: create: Búa til auglýsingu title: Ný auglýsing preview: + disclaimer: Þar sem notendur geta ekki afþakkað þær ætti aðeins að nota tilkynningar í tölvupósti fyrir mikilvægar upplýsingar á borð við persónuleg gagnabrot eða lokanir á netþjónum. explanation_html: 'Tölvupósturinn verður sendur til %{display_count} notenda. Eftirfarandi texti verður í meginmáli póstsins:' title: Forskoða tilkynninguna publish: Birta diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 1f6fc8f698..85a88be7bb 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -615,7 +615,7 @@ kab: filters: contexts: account: Imeɣna - notifications: Alɣuten + notifications: Ilɣa thread: Idiwenniyen edit: add_keyword: Rnu awal tasarut @@ -810,7 +810,7 @@ kab: import: Kter import_and_export: Taktert d usifeḍ migrate: Tunigin n umiḍan - notifications: Alɣuten s imayl + notifications: Ilɣa s imayl preferences: Imenyafen profile: Ameɣnu relationships: Imeḍfaṛen akked wid i teṭṭafaṛeḍ diff --git a/config/locales/ko.yml b/config/locales/ko.yml index c4ba1fa9e7..1ce5ef2da9 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -316,6 +316,7 @@ ko: create: 공지사항 생성 title: 새 공지사항 preview: + disclaimer: 사용자들은 수신설정을 끌 수 없기 때문에 이메일 알림은 개인정보 유출이나 서버 종료와 같은 중요한 공지사항에만 사용해야 합니다. explanation_html: "%{display_count} 명의 사용자에게 이메일이 발송됩니다. 다음 내용이 이메일에 포함됩니다:" title: 공지사항 알림 미리보기 publish: 게시 diff --git a/config/locales/lad.yml b/config/locales/lad.yml index 86dbc668c0..c1364fbb01 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -462,6 +462,18 @@ lad: new: title: Importa blokos de domeno no_file: Dinguna dosya tiene sido eskojida + fasp: + debug: + callbacks: + delete: Efasa + providers: + name: Nombre + registrations: + confirm: Konfirma + reject: Refuza + save: Guadra + sign_in: Konektate + status: Estado follow_recommendations: description_html: "Las rekomendasyones de kuentos ayudan a los muevos utilizadores a topar presto kontenido enteresante. Kuando un utilizador no tiene enteraktuado kon otros lo sufisiente komo para djenerar rekomendasyones personalizadas de kuentos a las ke segir, en sus lugar se le rekomiendan estes kuentos. Se rekalkulan diariamente a partir de una mikstura de kuentos kon el mayor numero de enteraksyones rezientes i kon el mayor numero de suivantes lokales kon una lingua determinada." language: Para la lingua diff --git a/config/locales/lv.yml b/config/locales/lv.yml index af65e9e02b..2bb5abf2de 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -19,10 +19,10 @@ lv: pin_errors: following: Tev ir jāseko personai, kuru vēlies atbalstīt posts: - one: Ziņa - other: Ziņas - zero: Ziņu - posts_tab_heading: Ziņas + one: Ieraksts + other: Ieraksti + zero: Ierakstu + posts_tab_heading: Ieraksti self_follow_error: Nav ļauts sekot savam kontam admin: account_actions: @@ -120,7 +120,7 @@ lv: public: Publisks push_subscription_expires: PuSH abonements beidzas redownload: Atsvaidzināt profilu - redownloaded_msg: "%{username} profils sekmīgi atsvaidzināts no izcelsmes" + redownloaded_msg: "%{username} profils sekmīgi atsvaidzināts no pirmavota" reject: Noraidīt rejected_msg: "%{username} reģistrēšanās pieteikums sekmīgi noraidīts" remote_suspension_irreversible: Šī konta dati ir neatgriezeniski dzēsti. @@ -152,7 +152,7 @@ lv: targeted_reports: Ziņojuši citi silence: Ierobežot silenced: Ierobežots - statuses: Ziņas + statuses: Ieraksti strikes: Iepriekšējie streiki subscribe: Abonēt suspend: Apturēt @@ -176,7 +176,7 @@ lv: whitelisted: Atļauts federācijai action_logs: action_types: - approve_appeal: Apstiprināt Apelāciju + approve_appeal: Apstiprināt pārsūdzību approve_user: Apstiprināt lietotāju assigned_to_self_report: Piešķirt Pārskatu change_email_user: Mainīt lietotāja e-pasta adresi @@ -218,11 +218,11 @@ lv: memorialize_account: Saglabāt Kontu Piemiņai promote_user: Izceltt Lietotāju publish_terms_of_service: Publicēt pakalpojuma izmantošanas noteikumus - reject_appeal: Noraidīt Apelāciju + reject_appeal: Noraidīt pārsūdzību reject_user: Noraidīt lietotāju remove_avatar_user: Noņemt profila attēlu reopen_report: Atkārtoti Atvērt Ziņojumu - resend_user: Atkārtoti nosūtīt Apstiprinājuma Pastu + resend_user: Atkārtoti nosūtīt apstiprinājuma e-pasta ziņojumu reset_password_user: Atiestatīt Paroli resolve_report: Atrisināt Ziņojumu sensitive_account: Uzspiesti atzimēt kontu kā jūtīgu @@ -241,7 +241,7 @@ lv: update_status: Atjaunināt ziņu update_user_role: Atjaunināt lomu actions: - approve_appeal_html: "%{name} apstiprināja satura pārraudzības lēmuma iebildumu no %{target}" + approve_appeal_html: "%{name} apstiprināja satura pārraudzības lēmuma pārsūdzību no %{target}" approve_user_html: "%{name} apstiprināja reģistrēšanos no %{target}" assigned_to_self_report_html: "%{name} piešķīra pārskatu %{target} sev" change_email_user_html: "%{name} nomainīja lietotāja %{target} e-pasta adresi" @@ -259,9 +259,11 @@ lv: create_user_role_html: "%{name} nomainīja %{target} lomu" demote_user_html: "%{name} pazemināja lietotāju %{target}" destroy_announcement_html: "%{name} izdzēsa paziņojumu %{target}" + destroy_canonical_email_block_html: "%{name} atcēla e-pasta adreses liegumu ar jaucējvērtību %{target}" destroy_custom_emoji_html: "%{name} izdzēsa emocijzīmi %{target}" destroy_domain_allow_html: "%{name} neatļāva federāciju ar domēnu %{target}" destroy_domain_block_html: "%{name} atbloķēja domēnu %{target}" + destroy_email_domain_block_html: "%{name} atcēla e-pasta domēna %{target} liegumu" destroy_instance_html: "%{name} attīrija domēnu %{target}" destroy_ip_block_html: "%{name} izdzēsa nosacījumu priekš IP %{target}" destroy_status_html: "%{name} noņēma ziņu %{target}" @@ -275,7 +277,7 @@ lv: memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiņas lapu" promote_user_html: "%{name} paaugstināja lietotāju %{target}" publish_terms_of_service_html: "%{name} padarīja pieejamus pakalpojuma izmantošanas noteikumu atjauninājumus" - reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma iebildumu no %{target}" + reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma pārsūdzību no %{target}" reject_user_html: "%{name} noraidīja reģistrēšanos no %{target}" remove_avatar_user_html: "%{name} noņēma %{target} profila attēlu" reopen_report_html: "%{name} atkārtoti atvēra ziņojumu %{target}" @@ -304,6 +306,7 @@ lv: title: Audita žurnāls unavailable_instance: "(domēna vārds nav pieejams)" announcements: + back: Atgriezties pie paziņojumiem destroyed_msg: Paziņojums sekmīgi izdzēsts. edit: title: Labot paziņojumu @@ -312,6 +315,9 @@ lv: new: create: Izveidot paziņojumu title: Jauns paziņojums + preview: + explanation_html: 'E-pasta ziņojums tiks nosūtīts %{display_count} lietotājiem. Šis teksts tiks iekļauts e-pasta ziņojumā:' + title: Priekšskatīt paziņojumu publish: Publicēt published_msg: Paziņojums sekmīgi publicēts. scheduled_for: Plānots uz %{time} @@ -404,7 +410,7 @@ lv: permanent_action: Apturēšanas atsaukšana neatjaunos nekādus datus vai attiecības. preamble_html: Tu gatavojies apturēt domēna %{domain} un tā apakšdomēnu darbību. remove_all_data: Tādējādi no tava servera tiks noņemts viss šī domēna kontu saturs, multivide un profila dati. - stop_communication: Jūsu serveris pārtrauks sazināties ar šiem serveriem. + stop_communication: Tavs serveris pārtrauks sazināties ar šiem serveriem. title: Apstiprināt domēna %{domain} bloķēšanu undo_relationships: Tādējādi tiks atsauktas jebkuras sekošanas attiecības starp šo un tavu serveru kontiem. created_msg: Domēna bloķēšana tagad tiek apstrādāta @@ -455,7 +461,9 @@ lv: create: Pievienot domēnu resolve: Atrisināt domēnu title: Liegt jaunu e-pasta domēnu + no_email_domain_block_selected: Neviens e-pasta domēna bloks netika mainīts, jo neviens netika atlasīts not_permitted: Nav atļauta + resolved_dns_records_hint_html: Domēna vārds saistās ar zemāk norādītajiem MX domēniem, kuri beigās ir atbildīgi par e-pasta pieņemšana. MX domēna liegšana liegs reģistrēšanos no jebkuras e-pasta adreses, kas izmanto to pašu MX domēnu, pat ja redzamais domēna vārds ir atšķirīgs. Jāuzmanās, lai neliegtu galvenos e-pasta pakalpojuma sniedzējus. resolved_through_html: Atrisināts, izmantojot %{domain} title: Bloķētie e-pasta domēni export_domain_allows: @@ -473,6 +481,35 @@ lv: new: title: Importēt bloķētos domēnus no_file: Nav atlasīts neviens fails + fasp: + debug: + callbacks: + created_at: Izveidots + delete: Izdzēst + ip: IP adrese + request_body: Pieprasījuma saturs + title: Atkļūdošanas atpakaļsaukumi + providers: + base_url: Pamata URL + callback: Atpakaļsaukums + delete: Izdzēst + edit: Labot nodrošinātāju + finish_registration: Pabeigt reģistrēšanos + name: Nosaukums + providers: Nodrošinātāji + public_key_fingerprint: Publiskās atslēgas pirkstu nospiedums + registration_requested: Pieprasīta reģistrēšanās + registrations: + confirm: Apstiprināt + description: Tu saņēmi reģistrēšanos no FĀPS. Tā ir jānoraida, ja to neveici. Ja veici, rūpīgi jāsalīdzina nosaukums un atslēgas pirkstu nospiedums, pirms reģistrēšanās apstiprināšanas. + reject: Noraidīt + title: Apstiprināt FĀPS reģistrēšanos + save: Saglabāt + select_capabilities: Atlasīt spējas + sign_in: Pieteikties + status: Stāvoklis + title: Fediverse ārējie pakalpojumu sniedzēji + title: FĀPS follow_recommendations: description_html: "Sekošanas ieteikumi palīdz jauniem lietotājiem ātri arast saistošu saturu. Kad lietotājs nav pietiekami mijiedarbojies ar citiem, lai veidotos pielāgoti sekošanas iteikumi, tiek ieteikti šie konti. Tie tiek pārskaitļoti ik dienas, izmantojot kontu, kuriem ir augstākās nesenās iesaistīšanās un lielākais vietējo sekotāju skaits norādītajā valodā." language: Valodai @@ -523,7 +560,7 @@ lv: instance_languages_dimension: Populārākās valodas instance_media_attachments_measure: saglabātie multivides pielikumi instance_reports_measure: ziņojumi par viņiem - instance_statuses_measure: saglabātās ziņas + instance_statuses_measure: saglabātie ieraksti delivery: all: Visas clear: Notīrīt piegādes kļūdas @@ -588,7 +625,7 @@ lv: disable: Atspējot disabled: Atspējots enable: Iespējot - enable_hint: Kad tas būs iespējots, tavs serveris abonēs visas publiskās ziņas no šī releja un sāks tam sūtīt šī servera publiskās ziņas. + enable_hint: Tiklīdz iespējots, serveris abonēs visus šī releja publiskos ierakstus un sāks tam sūtīt šī iservera publiskos ierakstus. enabled: Iespējots inbox_url: Releja URL pending: Gaida apstiprinājumu no releja @@ -619,6 +656,9 @@ lv: actions_description_remote_html: Izlem, kādas darbības jāveic, lai atrisinātu šo ziņojumu. Tas ietekmēs tikai to, kā tavs serveris sazinās ar šo attālo kontu un apstrādā tā saturu. actions_no_posts: Šim ziņojumam nav saistītu ierakstu, kurus izdzēst add_to_report: Pievienot varāk paziņošanai + already_suspended_badges: + local: Jau ir apturēts šajā serverī + remote: Jau ir apturēts viņu serverī are_you_sure: Vai esi pārliecināts? assign_to_self: Piešķirt man assigned: Piešķirtais satura pārraudzītājs @@ -669,7 +709,7 @@ lv: silence_html: 'Jūs gatavojaties ierobežot @%{acct} kontu. Tas:' suspend_html: 'Jūs gatavojaties apturēt @%{acct} kontu. Tas:' actions: - delete_html: Noņemt aizskarošās ziņas + delete_html: Noņemt aizskarošos ierakstus mark_as_sensitive_html: Atzīmēt aizskarošo ierakstu informācijas nesējus kā jūtīgus silence_html: Ievērojami ierobežo @%{acct} sasniedzamību, padarot viņa profilu un saturu redzamu tikai cilvēkiem, kas jau seko tam vai pašrocīgi uzmeklē profilu suspend_html: Apturēt @%{acct}, padarot viņu profilu un saturu nepieejamu un neiespējamu mijiedarbību ar @@ -678,8 +718,9 @@ lv: delete_data_html: Dzēsiet lietotāja @%{acct} profilu un saturu pēc 30 dienām, ja vien to darbība pa šo laiku netiks atcelta preview_preamble_html: "@%{acct} saņems brīdinājumu ar šādu saturu:" record_strike_html: Ierakstiet brīdinājumu pret @%{acct}, lai palīdzētu jums izvērst turpmākus pārkāpumus no šī konta + send_email_html: Nosūtīt @%{acct} brīdinājuma e-pasta ziņojumu warning_placeholder: Izvēles papildu pamatojums satura pārraudzības darbībai. - target_origin: Ziņotā konta izcelsme + target_origin: Konta, par kuru ziņots, izcelsme title: Ziņojumi unassign: Atsaukt unknown_action_msg: 'Nezināms konts: %{action}' @@ -719,6 +760,7 @@ lv: manage_appeals: Pārvaldīt Pārsūdzības manage_appeals_description: Ļauj lietotājiem pārskatīt iebildumus pret satura pārraudzības darbībām manage_blocks: Pārvaldīt Bloķus + manage_blocks_description: Ļauj lietotājiem liegt e-pasta pakalpojumu sniedzējus un IP adreses manage_custom_emojis: Pārvaldīt Pielāgotās Emocijzīmes manage_custom_emojis_description: Ļauj lietotājiem pārvaldīt pielāgotās emocijzīmes serverī manage_federation: Pārvaldīt Federāciju @@ -736,6 +778,7 @@ lv: manage_taxonomies: Pārvaldīt Taksonomijas manage_taxonomies_description: Ļauj lietotājiem pārskatīt aktuālāko saturu un atjaunināt tēmturu iestatījumus manage_user_access: Pārvaldīt Lietotāju Piekļuves + manage_user_access_description: Ļauj lietotājiem atspējot citu lietotāju divupakāpju autentificēšanos, mainīt savu e-pasta adresi un atiestatīt savu paroli manage_users: Pārvaldīt Lietotājus manage_users_description: Ļauj lietotājiem skatīt citu lietotāju informāciju un veikt pret viņiem satura pārraudzības darbības manage_webhooks: Pārvaldīt Tīmekļa Aizķeres @@ -810,6 +853,7 @@ lv: destroyed_msg: Vietnes augšupielāde sekmīgi izdzēsta. software_updates: critical_update: Kritiski - lūdzu, ātri atjaunini + description: Ir ieteicams uzturēt savu Mastodon serveri atjauninātu, lai gūtu labumu no jaunākajiem labojumiem un iespējām. Vēl jo vairāk, dažreiz ir ļoti svarīgi savlaicīgi atjaunināt Mastodon, lai izvairītos no drošības nepilnībām. Šo iemeslu dēļ Mastodon pārbauda atjauninājumus ik pēc 30 minūtēm, un paziņos atbilstoši e-pasta paziņojumu iestatījumiem. documentation_link: Uzzināt vairāk release_notes: Laidiena piezīmes title: Pieejamie atjauninājumi @@ -838,12 +882,12 @@ lv: title: Multivide metadata: Metadati no_history: Šis ieraksts nav bijis labots - no_status_selected: Neviena ziņa netika mainīta, jo neviena netika atlasīta + no_status_selected: Neviens ieraksts netika mainīts, jo nekas netika atlasīts open: Atvērt ziņu - original_status: Oriģinālā ziņa + original_status: Sākotnējais ieraksts reblogs: Reblogi replied_to_html: Atbildēja %{acct_link} - status_changed: Ziņa mainīta + status_changed: Ieraksts izmainīts status_title: Publicēja @%{name} title: Konta ieraksti - @%{name} trending: Aktuāli @@ -906,11 +950,13 @@ lv: message_html: "Tava objektu krātuve ir nepareizi konfigurēta. Tavu lietotāju privātums ir apdraudēts." tags: moderation: + not_trendable: Nav izplatīts not_usable: Nav izmantojams pending_review: Gaida pārskatīšanu review_requested: Pieprasīta pārskatīšana reviewed: Pārskatīts title: Stāvoklis + trendable: Izplatīts unreviewed: Nepārskatīts usable: Izmantojams name: Nosaukums @@ -1269,7 +1315,7 @@ lv: appeal_approved: Šis brīdinājums tika sekmīgi pārsūdzēts un vairs nav spēkā appeal_rejected: Apelācija ir noraidīta appeal_submitted_at: Apelācija iesniegta - appealed_msg: Jūsu apelācija ir iesniegta. Ja tā tiks apstiprināta, jums tiks paziņots. + appealed_msg: Tava pārsūdzība ir iesniegta. Ja tā tiks apstiprināta, Tev tiks paziņots. appeals: submit: Iesniegt apelāciju approve_appeal: Apstiprināt apelāciju @@ -1289,9 +1335,9 @@ lv: sensitive: Konta atzīmēšana kā jūtīgu silence: Konta ierobežošana suspend: Konta apturēšana - your_appeal_approved: Jūsu apelācija ir apstiprināta + your_appeal_approved: Tava pārsūdzība tika apstiprināta your_appeal_pending: Jūs esat iesniedzis apelāciju - your_appeal_rejected: Jūsu apelācija ir noraidīta + your_appeal_rejected: Tava pārsūdzība tika noraidīta edit_profile: basic_information: Pamata informācija hint_html: "Pielāgo, ko cilvēki redz Tavā publiskajā profilā un blakus Taviem ierakstiem. Ir lielāka iespējamība, ka citi clivēki sekos Tev un mijiedarbosies ar Tevi, ja Tev ir aizpildīts profils un profila attēls." @@ -1422,6 +1468,56 @@ lv: merge_long: Saglabāt esošos ierakstus un pievienot jaunus overwrite: Pārrakstīt overwrite_long: Nomainīt pašreizējos ierakstus ar jauniem + overwrite_preambles: + blocking_html: + one: Tu gatavojies aizstāt savu lieguma sarakstu ar līdz %{count} kontam no %{filename}. + other: Tu gatavojies aizstāt savu lieguma sarakstu ar līdz %{count} kontiem no %{filename}. + zero: Tu gatavojies aizstāt savu lieguma sarakstu ar līdz %{count} kontiem no %{filename}. + bookmarks_html: + one: Tu gatavojies aizstāt savas grāmatzīmes ar līdz %{count} ierakstam no %{filename}. + other: Tu gatavojies aizstāt savas grāmatzīmes ar līdz %{count} ierakstiem no %{filename}. + zero: Tu gatavojies aizstāt savas grāmatzīmes ar līdz %{count} ierakstiem no %{filename}. + domain_blocking_html: + one: Tu gatavojies aizstāt savu liegto domēnu sarakstu ar līdz %{count} domēnam no %{filename}. + other: Tu gatavojies aizstāt savu liegto domēnu sarakstu ar līdz %{count} domēniem no %{filename}. + zero: Tu gatavojies aizstāt savu liegto domēnu sarakstu ar līdz %{count} domēniem no %{filename}. + following_html: + one: Tu gatavojies sekot līdz %{count} kontam no %{filename} un pārtrauksi sekot citiem. + other: Tu gatavojies sekot līdz %{count} kontiem no %{filename} un pārtrauksi sekot citiem. + zero: Tu gatavojies sekot līdz %{count} kontiem no %{filename} un pārtrauksi sekot citiem. + lists_html: + one: Tu gatavojies aizstāt savus sarakstus ar %{filename} saturu. Līdz %{count} kontam tiks pievienoti jaunajos sarakstos. + other: Tu gatavojies aizstāt savus sarakstus ar %{filename} saturu. Līdz %{count} kontiem tiks pievienoti jaunajos sarakstos. + zero: Tu gatavojies aizstāt savus sarakstus ar %{filename} saturu. Līdz %{count} kontiem tiks pievienoti jaunajos sarakstos. + muting_html: + one: Tu gatavojies aizstāt savu apklusināto kontu sarakstu ar līdz %{count} kontam no %{filename}. + other: Tu gatavojies aizstāt savu apklusināto kontu sarakstu ar līdz %{count} kontiem no %{filename}. + zero: Tu gatavojies aizstāt savu apklusināto kontu sarakstu ar līdz %{count} kontiem no %{filename}. + preambles: + blocking_html: + one: Tu gatavojies liegt līdz %{count} kontam no %{filename}. + other: Tu gatavojies liegt līdz %{count} kontiem no %{filename}. + zero: Tu gatavojies liegt līdz %{count} kontiem no %{filename}. + bookmarks_html: + one: Tu gatavojies pievienot līdz %{count} ierakstam no %{filename} savām grāmatzīmēm. + other: Tu gatavojies pievienot līdz %{count} ierakstiem no %{filename} savām grāmatzīmēm. + zero: Tu gatavojies pievienot līdz %{count} ierakstiem no %{filename} savām grāmatzīmēm. + domain_blocking_html: + one: Tu gatavojies liegt līdz %{count} domēnam no %{filename}. + other: Tu gatavojies liegt līdz %{count} domēniem no %{filename}. + zero: Tu gatavojies liegt līdz %{count} domēniem no %{filename}. + following_html: + one: Tu gatavojies sekot līdz %{count} kontam no %{filename}. + other: Tu gatavojies sekot līdz %{count} kontiem no %{filename}. + zero: Tu gatavojies sekot līdz %{count} kontiem no %{filename}. + lists_html: + one: Tu gatavojies pievienot līdz pat %{count} kontam no %{filename} saviem sarakstiem. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot. + other: Tu gatavojies pievienot līdz pat %{count} kontiem no %{filename} saviem sarakstiem. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot. + zero: Tu gatavojies pievienot līdz pat %{count} kontiem no %{filename} saviem sarakstiem. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot. + muting_html: + one: Tu gatavojies apklusināt līdz pat %{count} kontam no %{filename}. + other: Tu gatavojies apklusināt līdz pat %{count} kontiem no %{filename}. + zero: Tu gatavojies apklusināt līdz pat %{count} kontiem no %{filename}. preface: Tu vari ievietot datus, kurus esi izguvis no cita servera, kā, piemēram, cilvēku sarakstu, kuriem Tu seko vai kurus bloķē. recent_imports: Nesen importēts states: @@ -1493,6 +1589,7 @@ lv: unsubscribe: action: Jā, atcelt abonēšanu complete: Anulēts + confirmation_html: Vai tiešām atteikties no %{type} saņemšanas savā e-pasta adresē %{email} par %{domain} esošo Mastodon? Vienmēr var abonēt no jauna savos e-pasta paziņojumu iestatījumos. emails: notification_emails: favourite: izlases paziņojumu e-pasta ziņojumi @@ -1500,10 +1597,13 @@ lv: follow_request: sekošanas pieprasījumu e-pasta ziņojumi mention: pieminēšanas paziņojumu e-pasta ziņojumi reblog: pastiprinājumu paziņojumu e-pasta ziņojumi + resubscribe_html: Ja abonements tika atcelts kļūdas dēļ, abonēt no jauna var savos e-pasta paziņojumu iestatījumos. + success_html: Tu vairs savā e-pasta adresē %{email} nesaņemsi %{type} par %{domain} esošo Mastodon. title: Atcelt abonēšanu media_attachments: validations: images_and_video: Nevar pievienot videoklipu tādai ziņai, kura jau satur attēlus + not_found: Informācijas nesējs %{ids} nav atrasts vai jau pievienots citam ierakstam not_ready: Nevar pievienot failus, kuru apstrāde nav pabeigta. Pēc brīža mēģini vēlreiz! too_many: Nevar pievienot vairāk kā 4 failus migrations: @@ -1581,6 +1681,7 @@ lv: subject: "%{name} laboja ierakstu" notifications: administration_emails: Pārvaldītāju e-pasta paziņojumi + email_events: E-pasta paziņojumu notikumi email_events_hint: 'Atlasi notikumus, par kuriem vēlies saņemt paziņojumus:' number: human: @@ -1637,6 +1738,9 @@ lv: errors: limit_reached: Sasniegts dažādu reakciju limits unrecognized_emoji: nav atpazīta emocijzīme + redirects: + prompt: Ja uzticies šai saitei, jāklikšķina uz tās, lai turpinātu. + title: Tu atstāj %{instance}. relationships: activity: Konta aktivitāte confirm_follow_selected_followers: Vai tiešām vēlies sekot atlasītajiem sekotājiem? @@ -1748,10 +1852,13 @@ lv: severed_relationships: download: Lejupielādēt (%{count}) event_type: + account_suspension: Konta apturēšana (%{target_name}) + domain_block: Servera apturēšana (%{target_name}) user_domain_block: Jūs bloķējāt %{target_name} lost_followers: Zaudētie sekotāji lost_follows: Zaudētie sekojumi preamble: Tu vari zaudēt sekojamos un sekotājus, kad liedz domēnu vai kad satura pārraudzītāji izlemj apturēt attālu serveri. Kad t as notiek, būs iespējams lejupielādēt sarakstus ar pārtrauktajām saiknēm, kurus tad var izpētīt un, iespējams, ievietot citā serverī. + purged: Informāciju par šo serveri notīrīja Tava servera pārvaldītāji. type: Notikums statuses: attached: @@ -1825,7 +1932,7 @@ lv: '7889238': 3 mēneši min_age_label: Vecuma slieksnis min_favs: Saglabāt ziņas izlsasē vismaz - min_favs_hint: Nedzēš nevienu jūsu ziņu, kas ir saņēmusi vismaz tik daudz izcēlumu. Atstājiet tukšu, lai dzēstu ziņas neatkarīgi no to izcēlumu skaita + min_favs_hint: Neizdzēš nevienu no Taviem ierakstiem, kas ir pievienoti šādā daudzumā izlašu. Atstāt tukšu, lai izdzēstu ierakstus neatkarīgi no tā, cik izlasēs tie ir pievienoti min_reblogs: Saglabāt ziņas izceltas vismaz min_reblogs_hint: Neizdzēš nevienu no tavām ziņām, kas ir izceltas vismaz tik reižu. Atstāj tukšu, lai dzēstu ziņas neatkarīgi no to izcēlumu skaita stream_entries: @@ -1841,6 +1948,7 @@ lv: contrast: Mastodon (Augsts kontrasts) default: Mastodon (Tumšs) mastodon-light: Mastodon (Gaišs) + system: Automātisks (ievēro sistēmas izskatu) time: formats: default: "%b %d, %Y, %H:%M" @@ -1867,18 +1975,32 @@ lv: recovery_instructions_html: Ja kādreiz zaudēsi piekļuvi savam tālrunim, vari izmantot kādu no zemāk norādītajiem atkopes kodiem, lai atgūtu piekļuvi savam kontam. Atkpes kodi jātur drošībā. Piemēram, tos var izdrukāt un glabāt kopā ar citiem svarīgiem dokumentiem. webauthn: Drošības atslēgas user_mailer: + announcement_published: + description: "%{domain} pārvaldītāji veic paziņojumu:" + subject: Pakalpojuma paziņojums + title: "%{domain} pakalpojuma paziņojums" appeal_approved: action: Konta iestatījumi - explanation: Apelācija par brīdinājumu jūsu kontam %{strike_date}, ko iesniedzāt %{appeal_date}, ir apstiprināta. Jūsu konts atkal ir labā stāvoklī. - subject: Jūsu %{date} apelācija ir apstiprināta + explanation: Pārsūdzība par brīdinājumu Tavam kontam %{strike_date}, ko iesniedzi %{appeal_date}, ir apstiprināta. Tavs konts atkal ir labā stāvoklī. + subject: Tava %{date} iesniegtā pārsūdzība tika apstiprināta + subtitle: Tavs konts atkal ir labā stāvoklī. title: Apelācija apstiprināta appeal_rejected: - explanation: Apelācija par brīdinājumu jūsu kontam %{strike_date}, ko iesniedzāt %{appeal_date}, ir noraidīta. - subject: Jūsu %{date} apelācija ir noraidīta + explanation: Pārsūdzība par brīdinājumu Tavam kontam %{strike_date}, ko iesniedzi %{appeal_date}, tika noraidīta. + subject: Tava %{date} iesniegta pārsūdzība tika noraidīta + subtitle: Tava pārsūdzība tika noraidīta. title: Apelācija noraidīta backup_ready: + explanation: Tu pieprasīji pilnu sava Mastodon konta rezerves kopiju. + extra: Tā tagad ir gatava lejupielādei. subject: Tavs arhīvs ir gatavs lejupielādei title: Arhīva līdzņemšana + failed_2fa: + details: 'Šeit ir informācija par pieteikšanās mēģinājumu:' + explanation: Kāds mēģināja pieteikties Tavā kontā, bet norādīja nederīgu otro autentificēšanās soli. + further_actions_html: Ja tas nebiji Tu, mēs iesakām nekavējoties %{action}, jo var būt noticis drošības pārkāpums. + subject: Otrās pakāpes autentificēšanās atteice + title: Neizdevās otrās pakāpes autentificēšanās suspicious_sign_in: change_password: mainīt paroli details: 'Šeit ir pieteikšanās izvērsums:' @@ -1889,6 +2011,7 @@ lv: terms_of_service_changed: agreement: Ar %{domain} izmantošanas tuprināšanu tiek piekrists šiem noteikumiem. Ja ir iebildumi pret atjauninātajiem noteikumiem, savu piekrišanu var atcelt jebkurā laikā ar sava konta izdzēšanu. changelog: 'Šeit īsumā ir aprakstīts, ko šis atjauninājums nozīmē:' + description: 'Šis e-pasta ziņojums tika saņemts, jo mēs veicam dažas izmaiņas savos pakalpojuma izmantošanas noteikumos %{domain}. Šie atjauninājumi stāsies spēkā %{date}. Mēs aicinām pārskatīt pilnus atjauninātos noteikumus šeit:' sign_off: "%{domain} komanda" subject: Mūsu pakalpojuma izmantošanas noteikumu atjauninājumi subtitle: Mainās %{domain} pakalpojuma izmantošanas noteikumi diff --git a/config/locales/ms.yml b/config/locales/ms.yml index 7fc8adcf41..68f4e872b6 100644 --- a/config/locales/ms.yml +++ b/config/locales/ms.yml @@ -28,6 +28,7 @@ ms: created_msg: Catatan penyederhanaan telah berjaya dicipta! destroyed_msg: Catatan penyederhanaan telah berjaya dipadam! accounts: + add_email_domain_block: Sekat domain e-mel approve: Luluskan approved_msg: Berjaya meluluskan permohonan pendaftaran %{username} are_you_sure: Adakah anda pasti? @@ -146,7 +147,7 @@ ms: suspension_irreversible: Data akaun ini telah dipadam secara kekal. Anda boleh nyahgantungkannya untuk membuatkan akaun ini boleh digunakan semula tetapi data lama tidak akan diperolehi. suspension_reversible_hint_html: Akaun ini telah digantung, dan datanya akan dibuang pada %{date}. Sebelum tarikh itu, akaun ini boleh diperoleh semula tanpa kesan buruk. Jika anda mahu memadamkan kesemua data akaun ini serta-merta, anda boleh melakukannya di bawah. title: Akaun - unblock_email: Menyahsekat alamat e-mel + unblock_email: Nyahsekat alamat e-mel unblocked_email_msg: Alamat e-mel %{username} berjaya dinyahsekat unconfirmed_email: E-mel belum disahkan undo_sensitized: Nyahtanda sensitif @@ -169,17 +170,21 @@ ms: confirm_user: Sahkan Pengguna create_account_warning: Cipta Amaran create_announcement: Cipta Pengumuman + create_canonical_email_block: Cipta Penyekatan E-mel create_custom_emoji: Cipta Emoji Tersendiri create_domain_allow: Cipta Pelepasan Domain create_domain_block: Cipta Penyekatan Domain + create_email_domain_block: Cipta Penyekatan Domain E-mel create_ip_block: Cipta peraturan alamat IP create_unavailable_domain: Cipta Domain Tidak Tersedia create_user_role: Cipta Peranan demote_user: Turunkan Taraf Pengguna destroy_announcement: Padam Pengumuman + destroy_canonical_email_block: Padam Penyekatan E-mel destroy_custom_emoji: Padam Emoji Tersendiri destroy_domain_allow: Padam Pelepasan Domain destroy_domain_block: Padam Penyekatan Domain + destroy_email_domain_block: Padam Penyekatan Domain E-mel destroy_instance: Padamkan Domain destroy_ip_block: Padam peraturan alamat IP destroy_status: Padam Hantaran @@ -203,7 +208,7 @@ ms: silence_account: Diamkan Akaun suspend_account: Gantungkan Akaun unassigned_report: Menyahtugaskan Laporan - unblock_email_account: Menyahsekat alamat e-mel + unblock_email_account: Nyahsekat alamat e-mel unsensitive_account: Nyahtanda media di akaun anda sebagai sensitif unsilence_account: Nyahdiamkan Akaun unsuspend_account: Nyahgantungkan Akaun @@ -1144,7 +1149,7 @@ ms: csv: CSV domain_blocks: Domain disekat lists: Senarai - mutes: Awak bisu + mutes: Redaman anda storage: Storan Media featured_tags: add_new: Tambah baharu @@ -1235,10 +1240,11 @@ ms: domain_blocking: Mengimport domain yang disekat following: Mengimport akaun diikuti lists: Mengimport senarai - muting: Mengimport akaun diredam + muting: Mengimport akaun teredam type: Jenis import type_groups: constructive: Ikutan & Penanda Halaman + destructive: Sekatan dan redaman types: blocking: Senarai menyekat bookmarks: Penanda buku diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 84608307d4..5acd93ca13 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -319,6 +319,7 @@ nl: create: Mededeling aanmaken title: Nieuwe mededeling preview: + disclaimer: Omdat gebruikers zich niet voor deze e-mails kunnen afmelden, moeten e-mailmeldingen worden beperkt tot belangrijke aankondigingen, zoals het lekken van gebruikersgegevens of meldingen over het sluiten van deze server. explanation_html: 'De e-mail wordt verzonden naar %{display_count} gebruikers. De volgende tekst wordt in het bericht opgenomen:' title: Voorbeeld van mededeling publish: Inschakelen diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 6a8fdda024..a2e142ab55 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -320,6 +320,7 @@ pt-BR: title: Novo anúncio preview: explanation_html: 'Esse e-mail será enviado a %{display_count} usuários. O texto a seguir será incluído ao e-mail:' + title: Visualizar anúncio publish: Publicar published_msg: Anúncio publicado! scheduled_for: Agendado para %{time} @@ -484,19 +485,30 @@ pt-BR: created_at: Criado em delete: Apagar ip: Endereço de IP + request_body: Corpo da solicitação + title: Depurar Callbacks providers: + active: Ativo base_url: URL Base + callback: Callback delete: Apagar + edit: Editar provedor finish_registration: Finalizar o cadastro name: Nome + providers: Provedores public_key_fingerprint: Impressão digital de chave pública registration_requested: Cadastro solicitado registrations: confirm: Confirmar + description: Você recebeu um registro de um FASP. Rejeite se você não tiver iniciado isso. Se você iniciou isso, compare cuidadosamente o nome e a impressão digital da chave antes de confirmar o registro. reject: Rejeitar + title: Confirmar o registro FASP save: Salvar + select_capabilities: Selecionar recursos sign_in: Entrar status: Estado + title: Provedores de serviços auxiliares do Fediverso + title: FASP follow_recommendations: description_html: "A recomendação de contas ajuda os novos usuários a encontrar rapidamente conteúdo interessante. Quando um usuário ainda não tiver interagido o suficiente para gerar recomendações de contas, essas contas serão recomendadas. Essas recomendações são recalculadas diariamente a partir de uma lista de contas com alto engajamento e maior número de seguidores locais em uma dada língua." language: Na língua @@ -1927,6 +1939,10 @@ pt-BR: recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para acessar a sua conta. Mantenha os códigos de recuperação em um local seguro. Por exemplo, você pode imprimi-los e guardá-los junto a outros documentos importantes. webauthn: Chaves de segurança user_mailer: + announcement_published: + description: 'Os administradores do %{domain} estão fazendo um anúncio:' + subject: Anúncio de serviço + title: Anúncio de serviço de %{domain} appeal_approved: action: Configurações da conta explanation: A revisão da punição na sua conta em %{strike_date} que você enviou em %{appeal_date} foi aprovada. Sua conta está novamente em situação regular. diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 0501e8cb54..1f01f622f5 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -495,6 +495,10 @@ ru: new: title: Импорт доменных блокировок no_file: Файл не выбран + fasp: + providers: + sign_in: + status: Пост follow_recommendations: description_html: "Следуйте рекомендациям, чтобы помочь новым пользователям быстро находить интересный контент. Если пользователь не взаимодействовал с другими в достаточной степени, чтобы сформировать персонализированные рекомендации, вместо этого рекомендуется использовать эти учетные записи. Они пересчитываются на ежедневной основе на основе комбинации аккаунтов с наибольшим количеством недавних взаимодействий и наибольшим количеством местных подписчиков для данного языка." language: Для языка diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 016ed4b25a..342a1dbe1c 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -84,7 +84,7 @@ de: backups_retention_period: Nutzer*innen haben die Möglichkeit, Archive ihrer Beiträge zu erstellen, die sie später herunterladen können. Wenn ein positiver Wert gesetzt ist, werden diese Archive nach der festgelegten Anzahl von Tagen automatisch aus deinem Speicher gelöscht. bootstrap_timeline_accounts: Diese Konten werden bei den Follower-Empfehlungen für neu registrierte Nutzer*innen oben angeheftet. closed_registrations_message: Wird angezeigt, wenn Registrierungen deaktiviert sind - content_cache_retention_period: Sämtliche Beiträge von anderen Servern (einschließlich geteilte Beiträge und Antworten) werden, unabhängig von der Interaktion der lokalen Nutzer*innen mit diesen Beiträgen, nach der festgelegten Anzahl von Tagen gelöscht. Das betrifft auch Beiträge, die von lokalen Nutzer*innen favorisiert oder als Lesezeichen gespeichert wurden. Private Erwähnungen zwischen Nutzer*innen von verschiedenen Servern werden ebenfalls verloren gehen und können nicht wiederhergestellt werden. Das Verwenden dieser Option richtet sich ausschließlich an Server für spezielle Zwecke und wird die allgemeine Nutzungserfahrung beeinträchtigen, wenn sie für den allgemeinen Gebrauch aktiviert ist. + content_cache_retention_period: Sämtliche Beiträge von anderen Servern (einschließlich geteilte Beiträge und Antworten) werden, unabhängig von der Interaktion der lokalen Nutzer*innen mit diesen Beiträgen, nach der festgelegten Anzahl von Tagen gelöscht. Das betrifft auch Beiträge, die von lokalen Nutzer*innen favorisiert oder als Lesezeichen gespeichert wurden. Private Erwähnungen zwischen Nutzer*innen von verschiedenen Servern werden ebenfalls verloren gehen und können nicht wiederhergestellt werden. Diese Option richtet sich ausschließlich an Server mit speziellen Zwecken und wird die allgemeine Nutzungserfahrung beeinträchtigen, wenn sie für den allgemeinen Gebrauch aktiviert ist. custom_css: Du kannst benutzerdefinierte Stile auf die Web-Version von Mastodon anwenden. favicon: WEBP, PNG, GIF oder JPG. Überschreibt das Standard-Mastodon-Favicon mit einem eigenen Symbol. mascot: Überschreibt die Abbildung in der erweiterten Weboberfläche. diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index b768c6551f..b2f7daf500 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -51,7 +51,7 @@ es-MX: inbox_url: Copia la URL de la página principal del relé que deseas usar irreversible: Las publicaciones filtradas desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante locale: El idioma de la interfaz de usuario, correos y notificaciones push - password: Utiliza al menos 8 caracteres + password: Usa al menos 8 caracteres phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una publicación scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente) @@ -68,10 +68,10 @@ es-MX: domain_allow: domain: Este dominio podrá obtener datos de este servidor y los datos entrantes serán procesados y archivados email_domain_block: - domain: Este puede ser el nombre de dominio que se muestra en al dirección de correo o el registro MX que utiliza. Se comprobarán al registrarse. + domain: Este puede ser el nombre de dominio que se muestra en la dirección de correo o el registro MX que utiliza. Se comprobarán al registrarse. with_dns_records: Se hará un intento de resolver los registros DNS del dominio dado y los resultados serán también puestos en lista negra featured_tag: - name: 'Aquí están algunas de las etiquetas que más has utilizado recientemente:' + name: 'Aquí están algunas de las etiquetas que más has usado recientemente:' filters: action: Elegir qué acción realizar cuando una publicación coincide con el filtro actions: @@ -90,14 +90,14 @@ es-MX: mascot: Reemplaza la ilustración en la interfaz web avanzada. media_cache_retention_period: Los archivos multimedia de las publicaciones realizadas por usuarios remotos se almacenan en caché en su servidor. Si se establece en un valor positivo, los archivos multimedia se eliminarán tras el número de días especificado. Si los datos multimedia se solicitan después de haber sido eliminados, se volverán a descargar, si el contenido de origen sigue estando disponible. Debido a las restricciones sobre la frecuencia con la que las tarjetas de previsualización de enlaces sondean sitios de terceros, se recomienda establecer este valor en al menos 14 días, o las tarjetas de previsualización de enlaces no se actualizarán bajo demanda antes de ese tiempo. min_age: Se pedirá a los usuarios que confirmen su fecha de nacimiento al registrarse - peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el fediverso. Aquí no se incluye ningún dato sobre si usted federa con un servidor determinado, sólo que su servidor lo sabe. Esto es utilizado por los servicios que recopilan estadísticas sobre la federación en un sentido general. + peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el fediverso. Aquí no se incluye ningún dato sobre si usted federa con un servidor determinado, solamente que su servidor lo sabe. Esto es usado por los servicios que recopilan estadísticas sobre la federación en un sentido general. profile_directory: El directorio de perfiles lista a todos los usuarios que han optado por que su cuenta pueda ser descubierta. require_invite_text: Cuando los registros requieren aprobación manual, hace obligatoria la entrada de texto "¿Por qué quieres unirte?" en lugar de opcional site_contact_email: Cómo la gente puede ponerse en contacto contigo para consultas legales o de ayuda. site_contact_username: Cómo puede contactarte la gente en Mastodon. site_extended_description: Cualquier información adicional que pueda ser útil para los visitantes y sus usuarios. Se puede estructurar con formato Markdown. site_short_description: Una breve descripción para ayudar a identificar su servidor de forma única. ¿Quién lo administra, a quién va dirigido? - site_terms: Utiliza tu propia política de privacidad o déjala en blanco para usar la predeterminada Puede estructurarse con formato Markdown. + site_terms: Usa tu propia política de privacidad o déjala en blanco para usar la predeterminada Puede estructurarse con formato Markdown. site_title: Cómo puede referirse la gente a tu servidor además de por el nombre de dominio. status_page_url: URL de una página donde las personas pueden ver el estado de este servidor durante una interrupción theme: El tema que los visitantes no registrados y los nuevos usuarios ven. @@ -138,11 +138,11 @@ es-MX: text: Puede estructurarse con la sintaxis Markdown. terms_of_service_generator: admin_email: Las notificaciones legales incluyen contraavisos, órdenes judiciales, solicitudes de retirada y solicitudes de aplicación de la ley. - arbitration_address: Puede ser la misma que la dirección física anterior, o "N/A" si utiliza correo electrónico. - arbitration_website: Puede ser un formulario web, o “N/A” si utiliza correo electrónico. + arbitration_address: Puede ser la misma que la dirección física anterior, o "N/A" si usa correo electrónico. + arbitration_website: Puede ser un formulario web, o “N/A” si usa correo electrónico. choice_of_law: Ciudad, región, territorio o estado de las leyes de sustancia interna de las que se regirán todas y cada una de las reclamaciones. dmca_address: Para los operadores de EE. UU., utilice la dirección registrada en el Directorio de Agentes Designados de la DMCA. Un listado de apartados de correos está disponible bajo petición directa, utilice la Solicitud de Renuncia de Apartado de Correos de Agente Designado de la DMCA para enviar un correo electrónico a la Oficina de Derechos de Autor y describa que usted es un moderador de contenidos desde su casa que teme venganza o represalias por sus acciones y necesita utilizar un apartado de correos para eliminar su dirección particular de la vista del público. - dmca_email: Puede ser el mismo correo electrónico utilizado para "Dirección de correo electrónico para avisos legales" de arriba. + dmca_email: Puede ser el mismo correo electrónico usado para “Dirección de correo electrónico para avisos legales” de arriba. domain: Identificación única del servicio en línea que presta. jurisdiction: Indique el país de residencia de quien paga las facturas. Si se trata de una empresa u otra entidad, indique el país en el que está constituida y la ciudad, región, territorio o estado, según proceda. min_age: No debe ser menor de la edad mínima exigida por las leyes de su jurisdicción. diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index 520b684847..9f46cdec7d 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -349,6 +349,9 @@ fa: jurisdiction: صلاحیت قانونی min_age: کمینهٔ زمان user: + date_of_birth_1i: روز + date_of_birth_2i: ماه + date_of_birth_3i: سال role: نقش time_zone: منطقهٔ زمانی user_role: diff --git a/config/locales/simple_form.ga.yml b/config/locales/simple_form.ga.yml index af75401d3f..bd8dac2a36 100644 --- a/config/locales/simple_form.ga.yml +++ b/config/locales/simple_form.ga.yml @@ -75,6 +75,7 @@ ga: filters: action: Roghnaigh an gníomh ba cheart a dhéanamh nuair a mheaitseálann postáil an scagaire actions: + blur: Folaigh na meáin taobh thiar de rabhadh, gan an téacs féin a cheilt hide: Cuir an t-ábhar scagtha i bhfolach go hiomlán, ag iompar amhail is nach raibh sé ann warn: Folaigh an t-ábhar scagtha taobh thiar de rabhadh a luann teideal an scagaire form_admin_settings: @@ -88,6 +89,7 @@ ga: favicon: WEBP, PNG, GIF nó JPG. Sáraíonn sé an favicon Mastodon réamhshocraithe le deilbhín saincheaptha. mascot: Sáraíonn sé an léaráid san ardchomhéadan gréasáin. media_cache_retention_period: Déantar comhaid meán ó phoist a dhéanann cianúsáideoirí a thaisceadh ar do fhreastalaí. Nuair a bheidh luach dearfach socraithe, scriosfar na meáin tar éis an líon sonraithe laethanta. Má iarrtar na sonraí meán tar éis é a scriosadh, déanfar é a ath-íoslódáil, má tá an t-ábhar foinse fós ar fáil. Mar gheall ar shrianta ar cé chomh minic is atá cártaí réamhamhairc ag vótaíocht do shuíomhanna tríú páirtí, moltar an luach seo a shocrú go 14 lá ar a laghad, nó ní dhéanfar cártaí réamhamhairc naisc a nuashonrú ar éileamh roimh an am sin. + min_age: Iarrfar ar úsáideoirí a ndáta breithe a dhearbhú le linn clárúcháin peers_api_enabled: Liosta de na hainmneacha fearainn ar tháinig an freastalaí seo orthu sa choinbhleacht. Níl aon sonraí san áireamh anseo faoi cé acu an ndéanann tú cónascadh le freastalaí ar leith, díreach go bhfuil a fhios ag do fhreastalaí faoi. Úsáideann seirbhísí a bhailíonn staitisticí ar chónaidhm go ginearálta é seo. profile_directory: Liostaíonn an t-eolaire próifíle na húsáideoirí go léir a roghnaigh isteach le bheith in-aimsithe. require_invite_text: Nuair a bhíonn faomhadh láimhe ag teastáil le haghaidh clárúcháin, déan an "Cén fáth ar mhaith leat a bheith páirteach?" ionchur téacs éigeantach seachas roghnach @@ -146,6 +148,7 @@ ga: min_age: Níor chóir go mbeidís faoi bhun na haoise íosta a éilíonn dlíthe do dhlínse. user: chosen_languages: Nuair a dhéantar iad a sheiceáil, ní thaispeánfar ach postálacha i dteangacha roghnaithe in amlínte poiblí + date_of_birth: Ní mór dúinn a chinntiú go bhfuil tú ar a laghad %{age} chun Mastodon a úsáid. Ní stórálfaimid é seo. role: Rialaíonn an ról na ceadanna atá ag an úsáideoir. user_role: color: Dath le húsáid don ról ar fud an Chomhéadain, mar RGB i bhformáid heicsidheachúlach @@ -258,6 +261,7 @@ ga: name: Haischlib filters: actions: + blur: Folaigh na meáin le rabhadh hide: Cuir i bhfolach go hiomlán warn: Cuir i bhfolach le rabhadh form_admin_settings: @@ -271,6 +275,7 @@ ga: favicon: Favicon mascot: Mascóg saincheaptha (oidhreacht) media_cache_retention_period: Tréimhse choinneála taisce meán + min_age: Riachtanas aoise íosta peers_api_enabled: Foilsigh liosta de na freastalaithe aimsithe san API profile_directory: Cumasaigh eolaire próifíle registrations_mode: Cé atá in ann clárú @@ -349,6 +354,9 @@ ga: jurisdiction: Dlínse dhlíthiúil min_age: Aois íosta user: + date_of_birth_1i: Lá + date_of_birth_2i: Mí + date_of_birth_3i: Bliain role: Ról time_zone: Crios ama user_role: diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index c14f6376a0..c0ff7e598e 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -18,7 +18,7 @@ kab: bot: Smekti-d wiyaḍ dakken amiḍan-a ixeddem s wudem amezwer tigawin tiwurmanin yernu ur yezmir ara ad yettwaɛass email: Ad n-teṭṭfeḍ imayl i usentem irreversible: Tisuffaɣ i tessazedgeḍ ad ttwakksent i lebda, ula ma tekkseḍ imsizdeg-nni ar zdat - locale: Tutlayt n ugrudem, imaylen d walɣuten yettudemren + locale: Tutlayt n ugrudem, imaylen d yilɣa yettudemren password: Seqdec ma drus 8 n yisekkilen setting_always_send_emails: S umata, ilɣa s yimayl ur d-ttwaceyyεen ara mi ara tesseqdaceḍ Mastodon s wudem urmid setting_display_media_default: Ffer imidyaten yettwacreḍ d infariyen @@ -115,8 +115,8 @@ kab: theme: Asentel amezwer thumbnail: Tanfult n uqeddac interactions: - must_be_follower: Ssewḥel alɣuten sɣur wid akked tid ur yellin ara d imeḍfaren-ik·im - must_be_following: Ssewḥel alɣuten sɣur wid akked tid ur tettḍafareḍ ara + must_be_follower: Ssewḥel ilɣa sɣur wid akk d tid ur yellin ara d imeḍfaren-ik·im + must_be_following: Ssewḥel ilɣa sɣur wid akked tid ur tettḍafareḍ ara must_be_following_dm: Sewḥel iznan usriden sɣur wid akked tid ur tettḍafareḍ ara invite: comment: Awennit diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index a823604584..661028fff0 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -146,6 +146,7 @@ ko: min_age: 관할지역의 법률에서 요구하는 최저 연령보다 작으면 안 됩니다. user: chosen_languages: 체크하면, 선택 된 언어로 작성된 게시물들만 공개 타임라인에 보여집니다 + date_of_birth: 마스토돈을 사용하려면 %{age}세 이상임을 확인해야 합니다. 이 정보는 저장되지 않습니다. role: 역할은 사용자가 어떤 권한을 가지게 될 지 결정합니다. user_role: color: 색상은 사용자 인터페이스에서 역할을 나타내기 위해 사용되며, RGB 16진수 형식입니다 diff --git a/config/locales/simple_form.lad.yml b/config/locales/simple_form.lad.yml index 6fef9f7422..a3f70bd361 100644 --- a/config/locales/simple_form.lad.yml +++ b/config/locales/simple_form.lad.yml @@ -311,6 +311,9 @@ lad: terms_of_service_generator: domain: Domeno user: + date_of_birth_1i: Diya + date_of_birth_2i: Mez + date_of_birth_3i: Anyo role: Rolo time_zone: Zona de tiempo user_role: diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 0c362b0a30..287ce36a5d 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -25,7 +25,7 @@ lv: type_html: Izvēlies, ko darīt ar %{acct} types: disable: Neļauj lietotājam izmantot savu kontu, bet neizdzēs vai neslēp tā saturu. - none: Izmanto šo, lai nosūtītu lietotājam brīdinājumu, neradot nekādas citas darbības. + none: Šis ir izmantojams, lai nosūtītu lietotājam brīdinājumu bez jebkādu citu darbību izraisīšanas. sensitive: Visus šī lietotāja informācijas nesēju pielikumus uzspiesti atzīmēt kā jūtīgus. silence: Neļaut lietotājam veikt ierakstus ar publisku redzamību, paslēpt viņa ierakstus un paziņojumus no cilvēkiem, kas tam neseko. Tiek aizvērti visi ziņojumi par šo kontu. suspend: Novērs jebkādu mijiedarbību no šī konta vai uz to un dzēs tā saturu. Atgriežams 30 dienu laikā. Tiek aizvērti visi šī konta pārskati. @@ -45,8 +45,8 @@ lv: context: Viens vai vairāki konteksti, kur jāpiemēro filtrs current_password: Drošības nolūkos, lūdzu, ievadi pašreizējā konta paroli current_username: Lai apstiprinātu, lūdzu, ievadi pašreizējā konta paroli - digest: Tiek nosūtīts tikai pēc ilgstošas bezdarbības un tikai tad, ja savas prombūtnes laikā esi saņēmis jebkādas personīgas ziņas - email: Tev tiks nosūtīts apstiprinājuma e-pasts + digest: Tiek nosūtīts tikai pēc ilgstošas bezdarbības un tikai tad, ja savas prombūtnes laikā saņēmi jebkādas personīgas ziņas + email: Tev tiks nosūtīts apstiprinājuma e-pasta ziņojums header: WEBP, PNG, GIF vai JPG. Ne vairāk kā %{size}. Tiks samazināts līdz %{dimensions}px inbox_url: Nokopē URL no tā releja sākumlapas, kuru vēlies izmantot irreversible: Filtrētās ziņas neatgriezeniski pazudīs, pat ja filtrs vēlāk tiks noņemts @@ -125,7 +125,7 @@ lv: hint: Izvēles. Sniedz vairāk informācijas par noteikumu text: Jāapraksta nosacījums vai prasība šī servera lietotājiem. Jāmēģina to veidot īsu un vienkāršu sessions: - otp: 'Jāievada tālruņa lietotnes izveidots divpakāpju kods vai jāizmanto viens no saviem atkopes kodie:' + otp: 'Jāievada tālruņa lietotnes izveidots divpakāpju kods vai jāizmanto viens no saviem atkopes kodiem:' webauthn: Ja tā ir USB atslēga, noteikti ievieto to un, ja nepieciešams, pieskaries tai. settings: indexable: Tava profila lapa var tikt parādīta Google, Bing un citu meklēšanas dzinēju rezultātos. @@ -213,7 +213,7 @@ lv: max_uses: Maksimālais lietojumu skaits new_password: Jauna parole note: Par sevi - otp_attempt: Divfaktoru kods + otp_attempt: Divpakāpju kods password: Parole phrase: Atslēgvārds vai frāze setting_advanced_layout: Iespējot paplašināto tīmekļa saskarni diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml index dfaa61e0cc..530bd60ad1 100644 --- a/config/locales/simple_form.th.yml +++ b/config/locales/simple_form.th.yml @@ -324,6 +324,9 @@ th: terms_of_service_generator: domain: โดเมน user: + date_of_birth_1i: วัน + date_of_birth_2i: เดือน + date_of_birth_3i: ปี role: บทบาท time_zone: โซนเวลา user_role: diff --git a/config/locales/sq.yml b/config/locales/sq.yml index dd96ddb88d..01884c10e9 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -319,6 +319,7 @@ sq: create: Krijoni lajmërim title: Lajmërim i ri preview: + disclaimer: Ngaqë përdoruesit s’mund të zgjedhin lënien jashtë tyre, njoftimet me email do të kufizohen te njoftime të rëndësishme, të tilla si cenim të dhënash personale, ose njoftime mbylljesh shërbyesish. explanation_html: 'Email-i do të dërgohet te %{display_count} përdorues. Te email-i do të përfshihet teksti vijues:' title: Bëni paraparje të shënimit për njoftimin publish: Publikoje diff --git a/config/locales/th.yml b/config/locales/th.yml index 9b7ae7897d..19f9e4da1f 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -466,6 +466,12 @@ th: new: title: นำเข้าการปิดกั้นโดเมน no_file: ไม่ได้เลือกไฟล์ + fasp: + debug: + callbacks: + created_at: สร้างเมื่อ + delete: ลบ + ip: ที่อยู่ IP follow_recommendations: description_html: "คำแนะนำการติดตามช่วยให้ผู้ใช้ใหม่ค้นหาเนื้อหาที่น่าสนใจได้อย่างรวดเร็ว เมื่อผู้ใช้ไม่ได้โต้ตอบกับผู้อื่นมากพอที่จะสร้างคำแนะนำการติดตามเฉพาะบุคคล จะแนะนำบัญชีเหล่านี้แทน จะคำนวณคำแนะนำใหม่เป็นประจำทุกวันจากบัญชีต่าง ๆ ที่มีการมีส่วนร่วมล่าสุดสูงสุดและจำนวนผู้ติดตามในเซิร์ฟเวอร์สูงสุดสำหรับภาษาที่กำหนด" language: สำหรับภาษา @@ -1636,7 +1642,7 @@ th: last_active: ใช้งานล่าสุด most_recent: ล่าสุด moved: ย้ายแล้ว - mutual: ร่วมกัน + mutual: คนที่มีร่วมกัน primary: หลัก relationship: ความสัมพันธ์ remove_selected_domains: เอาผู้ติดตามทั้งหมดออกจากโดเมนที่เลือก diff --git a/config/locales/tr.yml b/config/locales/tr.yml index d943462d0c..751e6ae22f 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -319,6 +319,7 @@ tr: create: Duyuru oluştur title: Yeni duyuru preview: + disclaimer: Kullanıcılar bu bildirimleri almayı iptal edemediği için, e-posta bildirimleri kişisel veri ihlali veya sunucu kapatma bildirimleri gibi önemli duyurularla sınırlandırılmalıdır. explanation_html: 'E-posta, %{display_count} kullanıcıya gönderilecektir. E-posta içerisinde aşağıdaki metin yer alacaktır:' title: Duyuru bildiriminin önizlemesine bak publish: Yayınla diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 788c84fb27..0256773d32 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -325,6 +325,7 @@ uk: create: Створити оголошення title: Нове оголошення preview: + disclaimer: Оскільки користувачі не можуть відмовитися від них, сповіщення по електронній пошті повинні обмежуватися важливими оголошеннями, такими як порушення особистих даних або повідомлення про закриття серверу. explanation_html: 'Електронний лист буде надіслано %{display_count} користувачам. До електронного листа буде включено такий текст:' title: Попередній перегляд сповіщення publish: Опублікувати diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 25f423d37a..a255973a10 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -316,6 +316,7 @@ vi: create: Tạo thông báo title: Tạo thông báo mới preview: + disclaimer: Vì người dùng không thể chọn không nhận thông báo qua email nên thông báo qua email chỉ nên giới hạn ở những thông báo quan trọng như thông báo vi phạm dữ liệu cá nhân hoặc thông báo đóng máy chủ. explanation_html: 'Gửi email tới %{display_count} thành viên. Nội dung sau đây sẽ được đưa vào email:' title: Xem trước thông báo sẽ gửi publish: Đăng diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 5542975143..3b87654041 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -316,6 +316,7 @@ zh-CN: create: 创建公告 title: 新公告 preview: + disclaimer: 由于用户无法选择退出,电子邮件通知应仅限于重要公告,例如个人数据泄露或服务器关闭通知。 explanation_html: 此电子邮件将发送给 %{display_count} 用户。电子邮件将包含以下文本: title: 预览公告通知 publish: 发布 @@ -471,6 +472,30 @@ zh-CN: new: title: 导入域名列表 no_file: 没有选择文件 + fasp: + debug: + callbacks: + created_at: 创建于 + delete: 刪除 + ip: IP 地址 + request_body: 请求正文 + title: 调试回调 + providers: + active: 有效 + base_url: 基础 URL + callback: 回调 + delete: 刪除 + edit: 编辑提供商 + finish_registration: 完成注册 + name: 名称 + providers: 提供商 + public_key_fingerprint: 公钥指纹 + registrations: + confirm: 确认 + reject: 拒绝 + save: 保存 + sign_in: 登录 + status: 状态 follow_recommendations: description_html: "“关注推荐”可帮助新用户快速找到有趣的内容。 当用户与他人的互动不足以形成个性化的建议时,就会推荐关注这些账号。推荐会每日更新,基于选定语言的近期最高互动数和最多本站关注者数综合评估得出。" language: 选择语言 diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 7278058dce..43f4f3ecba 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -38,7 +38,7 @@ zh-HK: current_email: 現時電郵 label: 更改電郵 new_email: 新的電郵 - submit: 改變電郵 + submit: 更改電郵 title: 改變 %{username} 的電郵 change_role: changed_msg: 成功更改身份! @@ -182,6 +182,7 @@ zh-HK: destroy_domain_block: 刪除已封鎖的域名 destroy_instance: 清除網域 destroy_ip_block: 刪除 IP 規則 + destroy_relay: 刪除中繼 destroy_status: 刪除文章 destroy_unavailable_domain: 刪除無效域名 destroy_user_role: 刪除身份 @@ -425,6 +426,12 @@ zh-HK: new: title: 匯入封鎖的網域 no_file: 未選擇檔案 + fasp: + debug: + callbacks: + delete: 刪除 + providers: + delete: 刪除 follow_recommendations: description_html: "跟隨建議幫助新使用者快速找到有趣內容。 當使用者尚未和其他帳號足夠多的互動以產生個人化建議時,以下帳號將被推荐。這些是一句指定語言的近期參與度和本地粉絲數最高之帳戶組合每日重新計算。" language: 按語言 @@ -1549,6 +1556,7 @@ zh-HK: import: 匯入 import_and_export: 匯入及匯出 migrate: 帳戶遷移 + notifications: 電郵通知 preferences: 偏好設定 profile: 個人資料 relationships: 關注及追隨者 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 029aa33214..fae08b3216 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -316,6 +316,7 @@ zh-TW: create: 新增公告 title: 新增公告 preview: + disclaimer: 由於使用者無法選擇退出,電子郵件通知應僅限於重要公告,例如個人資料洩露或伺服器關閉通知。 explanation_html: 此 email 將寄至 %{display_count} 名使用者。以下文字將被包含於 e-mail 中: title: 預覽公告通知 publish: 發布 diff --git a/config/routes.rb b/config/routes.rb index ab6eeee73c..1f97ddaaa4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -133,6 +133,7 @@ Rails.application.routes.draw do constraints(username: %r{[^@/.]+}) do with_options to: 'accounts#show' do get '/@:username', as: :short_account + get '/@:username/featured' get '/@:username/with_replies', as: :short_account_with_replies get '/@:username/media', as: :short_account_media get '/@:username/tagged/:tag', as: :short_account_tag diff --git a/config/routes/api.rb b/config/routes/api.rb index 59a0445db1..c8233bb01d 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -205,6 +205,7 @@ namespace :api, format: false do resources :circles, only: :index resources :identity_proofs, only: :index resources :featured_tags, only: :index + resources :endorsements, only: :index end member do @@ -218,8 +219,10 @@ namespace :api, format: false do end scope module: :accounts do - resource :pin, only: :create - post :unpin, to: 'pins#destroy' + post :pin, to: 'endorsements#create' + post :endorse, to: 'endorsements#create' + post :unpin, to: 'endorsements#destroy' + post :unendorse, to: 'endorsements#destroy' resource :note, only: :create end end diff --git a/config/themes.yml b/config/themes.yml index 4f4fbedb39..7ba74bbd35 100644 --- a/config/themes.yml +++ b/config/themes.yml @@ -1,10 +1,4 @@ default: styles/application.scss contrast: styles/contrast.scss mastodon-light: styles/mastodon-light.scss -modern-dark: styles/modern-dark.scss -modern-light: styles/modern-light.scss -modern-contrast: styles/modern-contrast.scss full-dark: styles/full-dark.scss - - - diff --git a/db/migrate/20250410144908_drop_imports.rb b/db/migrate/20250410144908_drop_imports.rb new file mode 100644 index 0000000000..7be9daf750 --- /dev/null +++ b/db/migrate/20250410144908_drop_imports.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class DropImports < ActiveRecord::Migration[7.1] + def up + drop_table :imports + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index 172800edce..d9a082eb24 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do +ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -739,19 +739,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do t.index ["user_id"], name: "index_identities_on_user_id" end - create_table "imports", force: :cascade do |t| - t.integer "type", null: false - t.boolean "approved", default: false, null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.string "data_file_name" - t.string "data_content_type" - t.datetime "data_updated_at", precision: nil - t.bigint "account_id", null: false - t.boolean "overwrite", default: false, null: false - t.integer "data_file_size" - end - create_table "instance_infos", force: :cascade do |t| t.string "domain", default: "", null: false t.string "software", default: "", null: false @@ -1720,7 +1707,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade add_foreign_key "generated_annual_reports", "accounts" add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade - add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade add_foreign_key "invites", "users", on_delete: :cascade add_foreign_key "list_accounts", "accounts", on_delete: :cascade add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade diff --git a/docker-compose.yml b/docker-compose.yml index b88ea761dc..0ec0c43bb6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: web: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes build: . - image: kmyblue:18.1 + image: kmyblue:18.0-dev restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: build: dockerfile: ./streaming/Dockerfile context: . - image: kmyblue-streaming:18.1 + image: kmyblue-streaming:18.0-dev restart: always env_file: .env.production command: node ./streaming/index.js @@ -101,7 +101,7 @@ services: sidekiq: build: . - image: kmyblue:18.1 + image: kmyblue:18.0-dev restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/cli/media.rb b/lib/mastodon/cli/media.rb index 84ec13eaab..1059eb6066 100644 --- a/lib/mastodon/cli/media.rb +++ b/lib/mastodon/cli/media.rb @@ -293,7 +293,6 @@ module Mastodon::CLI Account Backup CustomEmoji - Import MediaAttachment PreviewCard SiteUpload @@ -309,7 +308,6 @@ module Mastodon::CLI [:headers, Account.sum(:header_file_size), Account.local.sum(:header_file_size)], [:preview_cards, PreviewCard.sum(:image_file_size), nil], [:backups, Backup.sum(:dump_file_size), nil], - [:imports, Import.sum(:data_file_size), nil], [:settings, SiteUpload.sum(:file_file_size), nil], ].map { |label, total, local| [label.to_s.titleize, number_to_human_size(total), local.present? ? number_to_human_size(local) : nil] } end diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index e8eca96a71..acf7a4e79a 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,13 +13,13 @@ module Mastodon end def kmyblue_minor - 1 + 0 end def kmyblue_flag # 'LTS' - # 'dev' - nil + 'dev' + # nil end def major @@ -35,7 +35,7 @@ module Mastodon end def default_prerelease - 'alpha.5' + 'alpha.4' end def prerelease diff --git a/package.json b/package.json index 56f32db369..b111350da0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mastodon/mastodon", "license": "AGPL-3.0-or-later", - "packageManager": "yarn@4.8.1", + "packageManager": "yarn@4.9.1", "engines": { "node": ">=18" }, diff --git a/spec/fabricators/import_fabricator.rb b/spec/fabricators/import_fabricator.rb deleted file mode 100644 index 4951bb9a4d..0000000000 --- a/spec/fabricators/import_fabricator.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:import) do - account - type :following - data { attachment_fixture('imports.txt') } -end diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb deleted file mode 100644 index 587e0a9d26..0000000000 --- a/spec/models/import_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Import do - describe 'Validations' do - it { is_expected.to validate_presence_of(:type) } - it { is_expected.to validate_presence_of(:data) } - end -end diff --git a/spec/requests/api/v1/accounts/pins_spec.rb b/spec/requests/api/v1/accounts/endorsements_spec.rb similarity index 57% rename from spec/requests/api/v1/accounts/pins_spec.rb rename to spec/requests/api/v1/accounts/endorsements_spec.rb index 8ebcb27d28..6e0996a1f1 100644 --- a/spec/requests/api/v1/accounts/pins_spec.rb +++ b/spec/requests/api/v1/accounts/endorsements_spec.rb @@ -13,8 +13,30 @@ RSpec.describe 'Accounts Pins API' do kevin.account.followers << user.account end - describe 'POST /api/v1/accounts/:account_id/pin' do - subject { post "/api/v1/accounts/#{kevin.account.id}/pin", headers: headers } + describe 'GET /api/v1/accounts/:account_id/endorsements' do + subject { get "/api/v1/accounts/#{user.account.id}/endorsements", headers: headers } + + let(:scopes) { 'read:accounts' } + + before do + user.account.endorsed_accounts << kevin.account + end + + it 'returns the expected accounts', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) + .to contain_exactly( + hash_including(id: kevin.account_id.to_s) + ) + end + end + + describe 'POST /api/v1/accounts/:account_id/endorse' do + subject { post "/api/v1/accounts/#{kevin.account.id}/endorse", headers: headers } it 'creates account_pin', :aggregate_failures do expect do @@ -26,8 +48,8 @@ RSpec.describe 'Accounts Pins API' do end end - describe 'POST /api/v1/accounts/:account_id/unpin' do - subject { post "/api/v1/accounts/#{kevin.account.id}/unpin", headers: headers } + describe 'POST /api/v1/accounts/:account_id/unendorse' do + subject { post "/api/v1/accounts/#{kevin.account.id}/unendorse", headers: headers } before do Fabricate(:account_pin, account: user.account, target_account: kevin.account) diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb index 51cd797408..13a954b9ac 100644 --- a/spec/requests/api/v1/lists_spec.rb +++ b/spec/requests/api/v1/lists_spec.rb @@ -140,9 +140,12 @@ RSpec.describe 'Lists' do it 'returns http unprocessable entity' do subject - expect(response).to have_http_status(422) + expect(response) + .to have_http_status(422) expect(response.content_type) .to start_with('application/json') + expect(response.parsed_body) + .to include(error: /Replies policy is not included/) end end end diff --git a/spec/requests/api/v2/filters_spec.rb b/spec/requests/api/v2/filters_spec.rb index cb378d662a..ddf5743346 100644 --- a/spec/requests/api/v2/filters_spec.rb +++ b/spec/requests/api/v2/filters_spec.rb @@ -141,6 +141,21 @@ RSpec.describe 'Filters' do .to start_with('application/json') end end + + context 'when the given filter_action value is invalid' do + let(:params) { { title: 'magic', filter_action: 'imaginary_value', keywords_attributes: [keyword: 'magic'] } } + + it 'returns http unprocessable entity' do + subject + + expect(response) + .to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) + .to include(error: /Action is not included/) + end + end end describe 'GET /api/v2/filters/:id' do diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb deleted file mode 100644 index 2e1358c635..0000000000 --- a/spec/services/import_service_spec.rb +++ /dev/null @@ -1,242 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ImportService, :inline_jobs do - include RoutingHelper - - let!(:account) { Fabricate(:account, locked: false) } - let!(:bob) { Fabricate(:account, username: 'bob', locked: false) } - let!(:eve) { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false, protocol: :activitypub, inbox_url: 'https://example.com/inbox') } - - before do - stub_request(:post, 'https://example.com/inbox').to_return(status: 200) - end - - context 'when importing old-style list of muted users' do - subject { described_class.new } - - let(:csv) { attachment_fixture('mute-imports.txt') } - - describe 'when no accounts are muted' do - let(:import) { Import.create(account: account, type: 'muting', data: csv) } - - it 'mutes the listed accounts, including notifications' do - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - end - end - - describe 'when some accounts are muted and overwrite is not set' do - let(:import) { Import.create(account: account, type: 'muting', data: csv) } - - it 'mutes the listed accounts, including notifications' do - account.mute!(bob, notifications: false) - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - end - end - - describe 'when some accounts are muted and overwrite is set' do - let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) } - - it 'mutes the listed accounts, including notifications' do - account.mute!(bob, notifications: false) - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - end - end - end - - context 'when importing new-style list of muted users' do - subject { described_class.new } - - let(:csv) { attachment_fixture('new-mute-imports.txt') } - - describe 'when no accounts are muted' do - let(:import) { Import.create(account: account, type: 'muting', data: csv) } - - it 'mutes the listed accounts, respecting notifications' do - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false - end - end - - describe 'when some accounts are muted and overwrite is not set' do - let(:import) { Import.create(account: account, type: 'muting', data: csv) } - - it 'mutes the listed accounts, respecting notifications' do - account.mute!(bob, notifications: true) - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false - end - end - - describe 'when some accounts are muted and overwrite is set' do - let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) } - - it 'mutes the listed accounts, respecting notifications' do - account.mute!(bob, notifications: true) - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false - end - end - end - - context 'when importing old-style list of followed users' do - subject { described_class.new } - - let(:csv) { attachment_fixture('mute-imports.txt') } - - describe 'when no accounts are followed' do - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - it 'follows the listed accounts, including boosts' do - subject.call(import) - - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - end - end - - describe 'when some accounts are already followed and overwrite is not set' do - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - it 'follows the listed accounts, including notifications' do - account.follow!(bob, reblogs: false) - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - end - end - - describe 'when some accounts are already followed and overwrite is set' do - let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) } - - it 'mutes the listed accounts, including notifications' do - account.follow!(bob, reblogs: false) - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - end - end - end - - context 'when importing new-style list of followed users' do - subject { described_class.new } - - let(:csv) { attachment_fixture('new-following-imports.txt') } - - describe 'when no accounts are followed' do - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - it 'follows the listed accounts, respecting boosts' do - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false - end - end - - describe 'when some accounts are already followed and overwrite is not set' do - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - it 'mutes the listed accounts, respecting notifications' do - account.follow!(bob, reblogs: true) - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false - end - end - - describe 'when some accounts are already followed and overwrite is set' do - let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) } - - it 'mutes the listed accounts, respecting notifications' do - account.follow!(bob, reblogs: true) - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false - end - end - end - - # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users - # - # https://github.com/mastodon/mastodon/issues/20571 - context 'with a utf-8 encoded domains' do - subject { described_class.new } - - let!(:nare) { Fabricate(:account, username: 'nare', domain: 'թութ.հայ', locked: false, protocol: :activitypub, inbox_url: 'https://թութ.հայ/inbox') } - let(:csv) { attachment_fixture('utf8-followers.txt') } - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - # Make sure to not actually go to the remote server - before do - stub_request(:post, nare.inbox_url).to_return(status: 200) - end - - it 'follows the listed account' do - expect(account.follow_requests.count).to eq 0 - subject.call(import) - expect(account.follow_requests.count).to eq 1 - end - end - - context 'when importing bookmarks' do - subject { described_class.new } - - let(:csv) { attachment_fixture('bookmark-imports.txt') } - let(:local_account) { Fabricate(:account, username: 'foo', domain: nil) } - let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') } - let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) } - - around do |example| - local_before = Rails.configuration.x.local_domain - web_before = Rails.configuration.x.web_domain - Rails.configuration.x.local_domain = 'local.com' - Rails.configuration.x.web_domain = 'local.com' - example.run - Rails.configuration.x.web_domain = web_before - Rails.configuration.x.local_domain = local_before - end - - before do - service = instance_double(ActivityPub::FetchRemoteStatusService) - allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service) - allow(service).to receive(:call).with('https://unknown-remote.com/users/bar/statuses/1') do - Fabricate(:status, uri: 'https://unknown-remote.com/users/bar/statuses/1') - end - end - - describe 'when no bookmarks are set' do - let(:import) { Import.create(account: account, type: 'bookmarks', data: csv) } - - it 'adds the toots the user has access to to bookmarks' do - local_status = Fabricate(:status, account: local_account, uri: 'https://local.com/users/foo/statuses/42', id: 42, local: true) - subject.call(import) - expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(local_status.id) - expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(remote_status.id) - expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to_not include(direct_status.id) - expect(account.bookmarks.count).to eq 3 - end - end - end -end diff --git a/spec/support/system_helpers.rb b/spec/support/system_helpers.rb index ffbba177b3..44bbc64a59 100644 --- a/spec/support/system_helpers.rb +++ b/spec/support/system_helpers.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module SystemHelpers + FRONTEND_TRANSLATIONS = JSON.parse Rails.root.join('app', 'javascript', 'mastodon', 'locales', 'en.json').read + def submit_button I18n.t('generic.save_changes') end @@ -16,4 +18,8 @@ module SystemHelpers def css_id(record) "##{dom_id(record)}" end + + def frontend_translations(key) + FRONTEND_TRANSLATIONS[key] + end end diff --git a/spec/system/account_notes_spec.rb b/spec/system/account_notes_spec.rb index c4054f204e..1d125e1984 100644 --- a/spec/system/account_notes_spec.rb +++ b/spec/system/account_notes_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'Account notes', :inline_jobs, :js, :streaming do visit_profile(other_account) note_text = 'This is a personal note' - fill_in 'Click to add note', with: note_text + fill_in frontend_translations('account_note.placeholder'), with: note_text # This is a bit awkward since there is no button to save the change # The easiest way is to send ctrl+enter ourselves diff --git a/spec/system/log_out_spec.rb b/spec/system/log_out_spec.rb index 2e52254ca0..ebbf5a5772 100644 --- a/spec/system/log_out_spec.rb +++ b/spec/system/log_out_spec.rb @@ -17,8 +17,9 @@ RSpec.describe 'Log out' do click_on 'Logout' end - expect(page).to have_title(I18n.t('auth.login')) - expect(page).to have_current_path('/auth/sign_in') + expect(page) + .to have_title(I18n.t('auth.login')) + .and have_current_path('/auth/sign_in') end end @@ -28,6 +29,8 @@ RSpec.describe 'Log out' do ignore_js_error(/Failed to load resource: the server responded with a status of 422/) visit root_path + expect(page) + .to have_css('body', class: 'app-body') within '.navigation-bar' do click_on 'Menu' @@ -39,8 +42,9 @@ RSpec.describe 'Log out' do click_on 'Log out' - expect(page).to have_title(I18n.t('auth.login')) - expect(page).to have_current_path('/auth/sign_in') + expect(page) + .to have_title(I18n.t('auth.login')) + .and have_current_path('/auth/sign_in') end end end diff --git a/spec/system/new_statuses_spec.rb b/spec/system/new_statuses_spec.rb index 01b1500bc3..87b29004b2 100644 --- a/spec/system/new_statuses_spec.rb +++ b/spec/system/new_statuses_spec.rb @@ -20,20 +20,7 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do status_text = 'This is a new status!' within('.compose-form') do - fill_in "What's on your mind?", with: status_text - click_on 'Post' - end - - expect(page) - .to have_css('.status__content__text', text: status_text) - end - - it 'can be posted again' do - visit_homepage - status_text = 'This is a second status!' - - within('.compose-form') do - fill_in "What's on your mind?", with: status_text + fill_in frontend_translations('compose_form.placeholder'), with: status_text click_on 'Post' end diff --git a/spec/system/share_entrypoint_spec.rb b/spec/system/share_entrypoint_spec.rb index 7ccfee599a..b55ea31657 100644 --- a/spec/system/share_entrypoint_spec.rb +++ b/spec/system/share_entrypoint_spec.rb @@ -23,24 +23,14 @@ RSpec.describe 'Share page', :js, :streaming do fill_in_form expect(page) - .to have_css('.notification-bar-message', text: translations['compose.published.body']) + .to have_css('.notification-bar-message', text: frontend_translations('compose.published.body')) end def fill_in_form within('.compose-form') do - fill_in translations['compose_form.placeholder'], + fill_in frontend_translations('compose_form.placeholder'), with: 'This is a new status!' - click_on translations['compose_form.publish'] + click_on frontend_translations('compose_form.publish') end end - - def translations - # TODO: Extract to system spec helper for re-use? - JSON.parse( - Rails - .root - .join('app', 'javascript', 'mastodon', 'locales', 'en.json') - .read - ) - end end diff --git a/spec/workers/import_worker_spec.rb b/spec/workers/import_worker_spec.rb deleted file mode 100644 index 1d34aafe86..0000000000 --- a/spec/workers/import_worker_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ImportWorker do - let(:worker) { described_class.new } - let(:service) { instance_double(ImportService, call: true) } - - describe '#perform' do - before do - allow(ImportService).to receive(:new).and_return(service) - end - - let(:import) { Fabricate(:import) } - - it 'sends the import to the service' do - worker.perform(import.id) - - expect(service).to have_received(:call).with(import) - expect { import.reload }.to raise_error(ActiveRecord::RecordNotFound) - end - end -end diff --git a/spec/workers/unfilter_notifications_worker_spec.rb b/spec/workers/unfilter_notifications_worker_spec.rb index 464a4520ff..2fd130301f 100644 --- a/spec/workers/unfilter_notifications_worker_spec.rb +++ b/spec/workers/unfilter_notifications_worker_spec.rb @@ -5,6 +5,7 @@ require 'rails_helper' RSpec.describe UnfilterNotificationsWorker do let(:recipient) { Fabricate(:account) } let(:sender) { Fabricate(:account) } + let(:worker) { described_class.new } before do # Populate multiple kinds of filtered notifications @@ -67,23 +68,22 @@ RSpec.describe UnfilterNotificationsWorker do end describe '#perform' do - context 'with single argument (prerelease behavior)' do - subject { described_class.new.perform(notification_request.id) } - - let(:notification_request) { Fabricate(:notification_request, from_account: sender, account: recipient) } + context 'with recipient and sender' do + subject { worker.perform(recipient.id, sender.id) } it_behaves_like 'shared behavior' - - it 'destroys the notification request' do - expect { subject } - .to change { NotificationRequest.exists?(notification_request.id) }.to(false) - end end - context 'with two arguments' do - subject { described_class.new.perform(recipient.id, sender.id) } + context 'with missing records' do + it 'runs without error for missing sender' do + expect { worker.perform(recipient.id, nil) } + .to_not raise_error + end - it_behaves_like 'shared behavior' + it 'runs without error for missing recipient' do + expect { worker.perform(nil, sender.id) } + .to_not raise_error + end end end end diff --git a/streaming/package.json b/streaming/package.json index f376b89c3a..fa33b575db 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -1,7 +1,7 @@ { "name": "@mastodon/streaming", "license": "AGPL-3.0-or-later", - "packageManager": "yarn@4.8.1", + "packageManager": "yarn@4.9.1", "engines": { "node": ">=18" }, diff --git a/yarn.lock b/yarn.lock index 6acbf03bf1..fb750e3e11 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2235,14 +2235,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0": - version: 4.10.0 - resolution: "@eslint-community/regexpp@npm:4.10.0" - checksum: 10c0/c5f60ef1f1ea7649fa7af0e80a5a79f64b55a8a8fa5086de4727eb4c86c652aedee407a9c143b8995d2c0b2d75c1222bec9ba5d73dbfc1f314550554f0979ef4 - languageName: node - linkType: hard - -"@eslint-community/regexpp@npm:^4.12.1": +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": version: 4.12.1 resolution: "@eslint-community/regexpp@npm:4.12.1" checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 @@ -7385,10 +7378,10 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:^10.4.2, decimal.js@npm:^10.4.3": - version: 10.4.3 - resolution: "decimal.js@npm:10.4.3" - checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee +"decimal.js@npm:^10.4.2, decimal.js@npm:^10.4.3, decimal.js@npm:^10.5.0": + version: 10.5.0 + resolution: "decimal.js@npm:10.5.0" + checksum: 10c0/785c35279df32762143914668df35948920b6c1c259b933e0519a69b7003fc0a5ed2a766b1e1dda02574450c566b21738a45f15e274b47c2ac02072c0d1f3ac3 languageName: node linkType: hard @@ -9118,7 +9111,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0, form-data@npm:^4.0.1": +"form-data@npm:^4.0.0": version: 4.0.1 resolution: "form-data@npm:4.0.1" dependencies: @@ -10214,8 +10207,8 @@ __metadata: linkType: hard "ioredis@npm:^5.3.2": - version: 5.6.0 - resolution: "ioredis@npm:5.6.0" + version: 5.6.1 + resolution: "ioredis@npm:5.6.1" dependencies: "@ioredis/commands": "npm:^1.1.1" cluster-key-slot: "npm:^1.1.0" @@ -10226,7 +10219,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10c0/a885e5146640fc448706871290ef424ffa39af561f7ee3cf1590085209a509f85e99082bdaaf3cd32fa66758aea3fc2055d1109648ddca96fac4944bf2092c30 + checksum: 10c0/26ae49cf448e807e454a9bdea5a9dfdcf669e2fdbf2df341900a0fb693c5662fea7e39db3227ce8972d1bda0ba7da9b7410e5163b12d8878a579548d847220ac languageName: node linkType: hard @@ -11495,13 +11488,12 @@ __metadata: linkType: hard "jsdom@npm:^26.0.0": - version: 26.0.0 - resolution: "jsdom@npm:26.0.0" + version: 26.1.0 + resolution: "jsdom@npm:26.1.0" dependencies: cssstyle: "npm:^4.2.1" data-urls: "npm:^5.0.0" - decimal.js: "npm:^10.4.3" - form-data: "npm:^4.0.1" + decimal.js: "npm:^10.5.0" html-encoding-sniffer: "npm:^4.0.0" http-proxy-agent: "npm:^7.0.2" https-proxy-agent: "npm:^7.0.6" @@ -11511,12 +11503,12 @@ __metadata: rrweb-cssom: "npm:^0.8.0" saxes: "npm:^6.0.0" symbol-tree: "npm:^3.2.4" - tough-cookie: "npm:^5.0.0" + tough-cookie: "npm:^5.1.1" w3c-xmlserializer: "npm:^5.0.0" webidl-conversions: "npm:^7.0.0" whatwg-encoding: "npm:^3.1.1" whatwg-mimetype: "npm:^4.0.0" - whatwg-url: "npm:^14.1.0" + whatwg-url: "npm:^14.1.1" ws: "npm:^8.18.0" xml-name-validator: "npm:^5.0.0" peerDependencies: @@ -11524,7 +11516,7 @@ __metadata: peerDependenciesMeta: canvas: optional: true - checksum: 10c0/e48725ba4027edcfc9bca5799eaec72c6561ecffe3675a8ff87fe9c3541ca4ff9f82b4eff5b3d9c527302da0d859b2f60e9364347a5d42b77f5c76c436c569dc + checksum: 10c0/5b14a5bc32ce077a06fb42d1ab95b1191afa5cbbce8859e3b96831c5143becbbcbf0511d4d4934e922d2901443ced2cdc3b734c1cf30b5f73b3e067ce457d0f4 languageName: node linkType: hard @@ -12123,9 +12115,9 @@ __metadata: linkType: hard "marky@npm:^1.2.5": - version: 1.2.5 - resolution: "marky@npm:1.2.5" - checksum: 10c0/ca8a011f287dab1ac3291df720fc32b366c4cd767347b63722966650405ce71ec6566f71d1e22e1768bf6461a7fd689b9038e7df0fcfb62eacf3a5a6dcac249e + version: 1.3.0 + resolution: "marky@npm:1.3.0" + checksum: 10c0/6619cdb132fdc4f7cd3e2bed6eebf81a38e50ff4b426bbfb354db68731e4adfebf35ebfd7c8e5a6e846cbf9b872588c4f76db25782caee8c1529ec9d483bf98b languageName: node linkType: hard @@ -17589,12 +17581,12 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^5.0.0": - version: 5.0.0 - resolution: "tough-cookie@npm:5.0.0" +"tough-cookie@npm:^5.1.1": + version: 5.1.2 + resolution: "tough-cookie@npm:5.1.2" dependencies: tldts: "npm:^6.1.32" - checksum: 10c0/4a69c885bf6f45c5a64e60262af99e8c0d58a33bd3d0ce5da62121eeb9c00996d0128a72df8fc4614cbde59cc8b70aa3e21e4c3c98c2bbde137d7aba7fa00124 + checksum: 10c0/5f95023a47de0f30a902bba951664b359725597d8adeabc66a0b93a931c3af801e1e697dae4b8c21a012056c0ea88bd2bf4dfe66b2adcf8e2f42cd9796fe0626 languageName: node linkType: hard @@ -17623,12 +17615,12 @@ __metadata: languageName: node linkType: hard -"tr46@npm:^5.0.0": - version: 5.0.0 - resolution: "tr46@npm:5.0.0" +"tr46@npm:^5.1.0": + version: 5.1.0 + resolution: "tr46@npm:5.1.0" dependencies: punycode: "npm:^2.3.1" - checksum: 10c0/1521b6e7bbc8adc825c4561480f9fe48eb2276c81335eed9fa610aa4c44a48a3221f78b10e5f18b875769eb3413e30efbf209ed556a17a42aa8d690df44b7bee + checksum: 10c0/d761f7144e0cb296187674ef245c74f761e334d7cf25ca73ef60e4c72c097c75051031c093fa1a2fee04b904977b316716a7915854bcae8fb1a371746513cbe8 languageName: node linkType: hard @@ -18672,13 +18664,13 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^14.0.0, whatwg-url@npm:^14.1.0": - version: 14.1.0 - resolution: "whatwg-url@npm:14.1.0" +"whatwg-url@npm:^14.0.0, whatwg-url@npm:^14.1.1": + version: 14.2.0 + resolution: "whatwg-url@npm:14.2.0" dependencies: - tr46: "npm:^5.0.0" + tr46: "npm:^5.1.0" webidl-conversions: "npm:^7.0.0" - checksum: 10c0/f00104f1c67ce086ba8ffedab529cbbd9aefd8c0a6555320026de7aeff31f91c38680f95818b140a7c9cc657cde3781e567835dda552ddb1e2b8faaba0ac3cb6 + checksum: 10c0/f746fc2f4c906607d09537de1227b13f9494c171141e5427ed7d2c0dd0b6a48b43d8e71abaae57d368d0c06b673fd8ec63550b32ad5ed64990c7b0266c2b4272 languageName: node linkType: hard